Feat: hatch boundary grip editing — all edge types supported
Polyline, Line, CircularArc, EllipticArc, and Spline boundary edges all get diamond grips at their control vertices. Dragging a grip updates the underlying DXF boundary vertex and immediately rebuilds the GPU hatch model via Scene::apply_grip. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
36d51286d4
commit
749f3b99dc
3 changed files with 158 additions and 3 deletions
|
|
@ -1,9 +1,10 @@
|
|||
use acadrust::entities::{BoundaryEdge, Hatch};
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro};
|
||||
use crate::entities::traits::{PropertyEditable, Transformable};
|
||||
use crate::scene::object::{PropSection, PropValue, Property};
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
|
||||
fn properties(h: &Hatch) -> PropSection {
|
||||
let pattern_type = match h.pattern_type {
|
||||
|
|
@ -167,3 +168,146 @@ impl Transformable for Hatch {
|
|||
apply_transform(self, t);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Grip editing ───────────────────────────────────────────────────────────
|
||||
|
||||
/// Assign sequential grip IDs across all boundary paths and edges.
|
||||
/// Exposed control points per edge type:
|
||||
/// Polyline → each vertex (x, y)
|
||||
/// Line → start, end
|
||||
/// CircularArc → center
|
||||
/// EllipticArc → center
|
||||
/// Spline → fit points if present, else control points (x, y)
|
||||
impl Grippable for Hatch {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
let elev = self.elevation as f32;
|
||||
let mut out = Vec::new();
|
||||
let mut id = 0usize;
|
||||
for path in &self.paths {
|
||||
for edge in &path.edges {
|
||||
match edge {
|
||||
BoundaryEdge::Polyline(p) => {
|
||||
for v in &p.vertices {
|
||||
out.push(diamond_grip(id, Vec3::new(v.x as f32, v.y as f32, elev)));
|
||||
id += 1;
|
||||
}
|
||||
}
|
||||
BoundaryEdge::Line(l) => {
|
||||
out.push(diamond_grip(id, Vec3::new(l.start.x as f32, l.start.y as f32, elev)));
|
||||
id += 1;
|
||||
out.push(diamond_grip(id, Vec3::new(l.end.x as f32, l.end.y as f32, elev)));
|
||||
id += 1;
|
||||
}
|
||||
BoundaryEdge::CircularArc(a) => {
|
||||
out.push(diamond_grip(id, Vec3::new(a.center.x as f32, a.center.y as f32, elev)));
|
||||
id += 1;
|
||||
}
|
||||
BoundaryEdge::EllipticArc(e) => {
|
||||
out.push(diamond_grip(id, Vec3::new(e.center.x as f32, e.center.y as f32, elev)));
|
||||
id += 1;
|
||||
}
|
||||
BoundaryEdge::Spline(s) => {
|
||||
let pts: Vec<[f64; 2]> = if !s.fit_points.is_empty() {
|
||||
s.fit_points.iter().map(|p| [p.x, p.y]).collect()
|
||||
} else {
|
||||
s.control_points.iter().map(|p| [p.x, p.y]).collect()
|
||||
};
|
||||
for [x, y] in pts {
|
||||
out.push(diamond_grip(id, Vec3::new(x as f32, y as f32, elev)));
|
||||
id += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
let elev = self.elevation as f32;
|
||||
let mut id = 0usize;
|
||||
|
||||
fn resolve(apply: &GripApply, cur: Vec3) -> (f64, f64) {
|
||||
let p = match apply {
|
||||
GripApply::Absolute(p) => *p,
|
||||
GripApply::Translate(d) => cur + *d,
|
||||
};
|
||||
(p.x as f64, p.y as f64)
|
||||
}
|
||||
|
||||
'outer: for path in &mut self.paths {
|
||||
for edge in &mut path.edges {
|
||||
match edge {
|
||||
BoundaryEdge::Polyline(p) => {
|
||||
for v in &mut p.vertices {
|
||||
if id == grip_id {
|
||||
let (nx, ny) = resolve(&apply, Vec3::new(v.x as f32, v.y as f32, elev));
|
||||
v.x = nx;
|
||||
v.y = ny;
|
||||
break 'outer;
|
||||
}
|
||||
id += 1;
|
||||
}
|
||||
}
|
||||
BoundaryEdge::Line(l) => {
|
||||
if id == grip_id {
|
||||
let (nx, ny) = resolve(&apply, Vec3::new(l.start.x as f32, l.start.y as f32, elev));
|
||||
l.start.x = nx;
|
||||
l.start.y = ny;
|
||||
break 'outer;
|
||||
}
|
||||
id += 1;
|
||||
if id == grip_id {
|
||||
let (nx, ny) = resolve(&apply, Vec3::new(l.end.x as f32, l.end.y as f32, elev));
|
||||
l.end.x = nx;
|
||||
l.end.y = ny;
|
||||
break 'outer;
|
||||
}
|
||||
id += 1;
|
||||
}
|
||||
BoundaryEdge::CircularArc(a) => {
|
||||
if id == grip_id {
|
||||
let (nx, ny) = resolve(&apply, Vec3::new(a.center.x as f32, a.center.y as f32, elev));
|
||||
a.center.x = nx;
|
||||
a.center.y = ny;
|
||||
break 'outer;
|
||||
}
|
||||
id += 1;
|
||||
}
|
||||
BoundaryEdge::EllipticArc(e) => {
|
||||
if id == grip_id {
|
||||
let (nx, ny) = resolve(&apply, Vec3::new(e.center.x as f32, e.center.y as f32, elev));
|
||||
e.center.x = nx;
|
||||
e.center.y = ny;
|
||||
break 'outer;
|
||||
}
|
||||
id += 1;
|
||||
}
|
||||
BoundaryEdge::Spline(s) => {
|
||||
if !s.fit_points.is_empty() {
|
||||
for fp in &mut s.fit_points {
|
||||
if id == grip_id {
|
||||
let (nx, ny) = resolve(&apply, Vec3::new(fp.x as f32, fp.y as f32, elev));
|
||||
fp.x = nx;
|
||||
fp.y = ny;
|
||||
break 'outer;
|
||||
}
|
||||
id += 1;
|
||||
}
|
||||
} else {
|
||||
for cp in &mut s.control_points {
|
||||
if id == grip_id {
|
||||
let (nx, ny) = resolve(&apply, Vec3::new(cp.x as f32, cp.y as f32, elev));
|
||||
cp.x = nx;
|
||||
cp.y = ny;
|
||||
break 'outer;
|
||||
}
|
||||
id += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ impl EntityTypeOps for EntityType {
|
|||
EntityType::Leader(leader) => Grippable::grips(leader),
|
||||
EntityType::MultiLeader(ml) => Grippable::grips(ml),
|
||||
EntityType::Dimension(dim) => Grippable::grips(dim),
|
||||
EntityType::Hatch(hatch) => Grippable::grips(hatch),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
|
@ -290,6 +291,7 @@ impl EntityTypeOps for EntityType {
|
|||
EntityType::Leader(leader) => Grippable::apply_grip(leader, grip_id, apply),
|
||||
EntityType::MultiLeader(ml) => Grippable::apply_grip(ml, grip_id, apply),
|
||||
EntityType::Dimension(dim) => Grippable::apply_grip(dim, grip_id, apply),
|
||||
EntityType::Hatch(hatch) => Grippable::apply_grip(hatch, grip_id, apply),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1660,6 +1660,15 @@ impl Scene {
|
|||
if let Some(entity) = self.document.get_entity_mut(handle) {
|
||||
dispatch::apply_grip(entity, grip_id, apply);
|
||||
}
|
||||
// Rebuild the GPU hatch model when a hatch boundary vertex moves.
|
||||
if let Some(EntityType::Hatch(dxf)) = self.document.get_entity(handle) {
|
||||
let color = tessellate::aci_to_rgba(&dxf.common.color);
|
||||
if let Some(model) = Self::hatch_model_from_dxf(dxf, color) {
|
||||
self.hatches.insert(handle, model);
|
||||
} else {
|
||||
self.hatches.remove(&handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Hit-test convenience: wire name → Handle ──────────────────────────
|
||||
|
|
|
|||
Loading…
Reference in a new issue