forked from 1b-agent/opencadstudio-upstream
feat(properties): navigate polyline vertices from the panel
The Current Vertex row is now a ◀ / ▶ stepper (new PropValue::Stepper) showing "i / N". Stepping it moves the panel's focus through the polyline's vertices — Vertex X/Y and the per-vertex start/end widths show and edit the focused vertex instead of always the first — and the focused vertex's grip is drawn hot (filled) in the drawing so it's visible while navigating. Focus wraps around and resets to the first vertex when the selection changes. Adds Area and Length to the LwPolyline Geometry group (Polyline2D already had them). Applies to LwPolyline and Polyline2D. The focused vertex is threaded to the per-entity property builder/editor through a dispatch thread-local (like the curve-tolerance override), so the PropertyEditable trait signatures are unchanged. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
a4f7ee376f
commit
b3c8c0a2f1
12 files changed
+208
-22
No files matched your search
@@ -181,6 +181,23 @@ pub fn ro_prop(label: &'static str, field: &'static str, value: impl Into<String
|
||||
}
|
||||
}
|
||||
|
||||
/// A ◀ / ▶ index navigator row (e.g. a polyline's Current Vertex). `display` is
|
||||
/// the label shown between the arrows (e.g. "2 / 7").
|
||||
pub fn stepper_prop(
|
||||
label: &'static str,
|
||||
field: &'static str,
|
||||
display: impl Into<String>,
|
||||
) -> Property {
|
||||
Property {
|
||||
label: label.into(),
|
||||
field,
|
||||
value: PropValue::Stepper {
|
||||
field,
|
||||
display: display.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_f64(value: &str) -> Option<f64> {
|
||||
value.trim().parse::<f64>().ok()
|
||||
}
|
||||
|
||||
+33
-10
@@ -4,6 +4,7 @@ use truck_modeling::{builder, Edge, Point3, Wire};
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{
|
||||
edit_prop as edit, parse_f64, rectangle_grip, ro_prop as ro, square_grip,
|
||||
stepper_prop as stepper,
|
||||
};
|
||||
use crate::entities::traits::TruckConvertible;
|
||||
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
|
||||
@@ -323,22 +324,37 @@ fn grips(pline: &LwPolyline) -> Vec<GripDef> {
|
||||
}
|
||||
|
||||
fn properties(pline: &LwPolyline) -> Vec<PropSection> {
|
||||
let v0 = pline.vertices.first();
|
||||
let vx = v0.map_or(0.0, |v| v.location.x);
|
||||
let vy = v0.map_or(0.0, |v| v.location.y);
|
||||
let start_w = v0.map_or(0.0, |v| v.start_width);
|
||||
let end_w = v0.map_or(0.0, |v| v.end_width);
|
||||
let n = pline.vertices.len();
|
||||
// The panel's Current Vertex focus, clamped to this polyline's range.
|
||||
let vi = if n == 0 {
|
||||
0
|
||||
} else {
|
||||
crate::scene::view::dispatch::prop_current_vertex().min(n - 1)
|
||||
};
|
||||
let v = pline.vertices.get(vi);
|
||||
let vx = v.map_or(0.0, |v| v.location.x);
|
||||
let vy = v.map_or(0.0, |v| v.location.y);
|
||||
let start_w = v.map_or(0.0, |v| v.start_width);
|
||||
let end_w = v.map_or(0.0, |v| v.end_width);
|
||||
let mp = <LwPolyline as crate::entities::traits::MassPropsCalc>::mass_props(pline);
|
||||
let vertex_label = if n == 0 {
|
||||
"—".to_string()
|
||||
} else {
|
||||
format!("{} / {}", vi + 1, n)
|
||||
};
|
||||
vec![
|
||||
PropSection {
|
||||
title: "Geometry".into(),
|
||||
props: vec![
|
||||
ro("Current Vertex", "current_vertex", String::new()),
|
||||
stepper("Current Vertex", "current_vertex", vertex_label),
|
||||
edit("Vertex X", "vertex_x", vx),
|
||||
edit("Vertex Y", "vertex_y", vy),
|
||||
edit("Start segment width", "start_width", start_w),
|
||||
edit("End segment width", "end_width", end_w),
|
||||
edit("Global width", "global_width", pline.constant_width),
|
||||
edit("Elevation", "elevation", pline.elevation),
|
||||
ro("Area", "area", format!("{:.4}", mp.area)),
|
||||
ro("Length", "length", format!("{:.4}", mp.perimeter)),
|
||||
],
|
||||
},
|
||||
PropSection {
|
||||
@@ -388,26 +404,33 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
||||
let Some(v) = parse_f64(value) else {
|
||||
return;
|
||||
};
|
||||
// Per-vertex edits target the vertex the panel is focused on.
|
||||
let n = pline.vertices.len();
|
||||
let vi = if n == 0 {
|
||||
0
|
||||
} else {
|
||||
crate::scene::view::dispatch::prop_current_vertex().min(n - 1)
|
||||
};
|
||||
match field {
|
||||
"elevation" => pline.elevation = v,
|
||||
"global_width" => pline.constant_width = v,
|
||||
"vertex_x" => {
|
||||
if let Some(vtx) = pline.vertices.first_mut() {
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.location.x = v;
|
||||
}
|
||||
}
|
||||
"vertex_y" => {
|
||||
if let Some(vtx) = pline.vertices.first_mut() {
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.location.y = v;
|
||||
}
|
||||
}
|
||||
"start_width" => {
|
||||
if let Some(vtx) = pline.vertices.first_mut() {
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.start_width = v;
|
||||
}
|
||||
}
|
||||
"end_width" => {
|
||||
if let Some(vtx) = pline.vertices.first_mut() {
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.end_width = v;
|
||||
}
|
||||
}
|
||||
|
||||
+30
-11
@@ -2,7 +2,9 @@ use acadrust::entities::{Polyline, Polyline2D, Polyline3D};
|
||||
use truck_modeling::{builder, Edge, Point3, Wire};
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
|
||||
use crate::entities::common::{
|
||||
edit_prop as edit, parse_f64, ro_prop as ro, square_grip, stepper_prop as stepper,
|
||||
};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
@@ -399,17 +401,27 @@ impl PropertyEditable for Polyline2D {
|
||||
}
|
||||
area = (area * 0.5).abs();
|
||||
|
||||
let v0 = self.vertices.first();
|
||||
let vertex_x = v0.map(|v| v.location.x).unwrap_or_default();
|
||||
let vertex_y = v0.map(|v| v.location.y).unwrap_or_default();
|
||||
let seg_start_w = v0.map(|v| v.start_width).unwrap_or_default();
|
||||
let seg_end_w = v0.map(|v| v.end_width).unwrap_or_default();
|
||||
let vi = if n == 0 {
|
||||
0
|
||||
} else {
|
||||
crate::scene::view::dispatch::prop_current_vertex().min(n - 1)
|
||||
};
|
||||
let v = self.vertices.get(vi);
|
||||
let vertex_x = v.map(|v| v.location.x).unwrap_or_default();
|
||||
let vertex_y = v.map(|v| v.location.y).unwrap_or_default();
|
||||
let seg_start_w = v.map(|v| v.start_width).unwrap_or_default();
|
||||
let seg_end_w = v.map(|v| v.end_width).unwrap_or_default();
|
||||
let vertex_label = if n == 0 {
|
||||
"—".to_string()
|
||||
} else {
|
||||
format!("{} / {}", vi + 1, n)
|
||||
};
|
||||
|
||||
vec![
|
||||
PropSection {
|
||||
title: "Geometry".into(),
|
||||
props: vec![
|
||||
ro("Current Vertex", "pl2_current_vertex", if n > 0 { "1" } else { "" }),
|
||||
stepper("Current Vertex", "pl2_current_vertex", vertex_label),
|
||||
edit("Vertex X", "pl2_vertex_x", vertex_x),
|
||||
edit("Vertex Y", "pl2_vertex_y", vertex_y),
|
||||
edit("Start segment width", "pl2_seg_start_w", seg_start_w),
|
||||
@@ -445,6 +457,13 @@ impl PropertyEditable for Polyline2D {
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
// Per-vertex edits target the vertex the panel is focused on.
|
||||
let n = self.vertices.len();
|
||||
let vi = if n == 0 {
|
||||
0
|
||||
} else {
|
||||
crate::scene::view::dispatch::prop_current_vertex().min(n - 1)
|
||||
};
|
||||
match field {
|
||||
"pl2_closed" => {
|
||||
let closed = if value == "toggle" {
|
||||
@@ -472,24 +491,24 @@ impl PropertyEditable for Polyline2D {
|
||||
}
|
||||
}
|
||||
"pl2_vertex_x" => {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.get_mut(vi)) {
|
||||
vert.location.x = v;
|
||||
}
|
||||
}
|
||||
"pl2_vertex_y" => {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.get_mut(vi)) {
|
||||
vert.location.y = v;
|
||||
}
|
||||
}
|
||||
"pl2_seg_start_w" => {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.get_mut(vi)) {
|
||||
if v >= 0.0 {
|
||||
vert.start_width = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
"pl2_seg_end_w" => {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.get_mut(vi)) {
|
||||
if v >= 0.0 {
|
||||
vert.end_width = v;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user