feat(properties): audit editable vs read-only across all entities

Reclassify every property row to match standard palette behaviour: a row
is editable only when its value maps directly to a stored field AND its
apply_geom_prop handler is wired; computed/derived/count/handle/
cross-object values stay read-only.

Newly editable (all apply-wired): attribute tag/prompt; ellipse major
radius; hatch Double; lwpolyline closed/linetype-gen; mline style;
multileader annotative/landing/background-fill; polyline 2D+3D vertex
coords/segment widths/linetype-gen; shape style; spline control/fit
point coords, weight, fit tolerance, tangents; table break-option flags;
text alignment point, upside-down, backward; underlay display flags.

Corrections: spline Degree -> read-only (editing desynced the knot
vector); removed a duplicate leader "Arrowhead" row.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-03 08:06:00 +03:00
1 parent 35468004e6
commit 4d2471cafd
13 files changed
+464 -143

No files matched your search

+27 -3
View File
@@ -386,8 +386,16 @@ impl Grippable for AttributeDefinition {
impl PropertyEditable for AttributeDefinition {
fn geometry_properties(&self, text_style_names: &[String]) -> Vec<PropSection> {
let mut text_props = vec![
ro("Tag", "att_tag", self.tag.clone()),
ro("Prompt", "att_prompt", self.prompt.clone()),
Property {
label: "Tag".into(),
field: "att_tag",
value: PropValue::EditText(self.tag.clone()),
},
Property {
label: "Prompt".into(),
field: "att_prompt",
value: PropValue::EditText(self.prompt.clone()),
},
Property {
label: "Value".into(),
field: "att_default",
@@ -501,6 +509,14 @@ impl PropertyEditable for AttributeDefinition {
fn apply_geom_prop(&mut self, field: &str, value: &str) {
// String / enum fields first.
if field == "att_tag" {
self.tag = value.to_string();
return;
}
if field == "att_prompt" {
self.prompt = value.to_string();
return;
}
if field == "att_default" {
if !self.flags.constant {
self.default_value = value.to_string();
@@ -645,7 +661,11 @@ impl PropertyEditable for AttributeEntity {
PropSection {
title: "Text".into(),
props: vec![
ro("Tag", "atte_tag", self.tag.clone()),
Property {
label: "Tag".into(),
field: "atte_tag",
value: PropValue::EditText(self.tag.clone()),
},
ro("Prompt", "atte_prompt", String::new()),
Property {
label: "Value".into(),
@@ -746,6 +766,10 @@ impl PropertyEditable for AttributeEntity {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
if field == "atte_tag" {
self.tag = value.to_string();
return;
}
if field == "atte_val" {
if !self.flags.constant {
self.value = value.to_string();
+10 -1
View File
@@ -197,7 +197,7 @@ fn properties(ell: &Ellipse) -> Vec<PropSection> {
ro("End X", "end_x", format!("{:.4}", end.x)),
ro("End Y", "end_y", format!("{:.4}", end.y)),
ro("End Z", "end_z", format!("{:.4}", end.z)),
ro("Major radius", "major_r", format!("{r_major:.4}")),
edit("Major radius", "major_r", r_major),
ro("Minor radius", "minor_r", format!("{r_minor:.4}")),
edit("Radius ratio", "ratio", ell.minor_axis_ratio),
ro("Start angle", "start_angle", format!("{start_angle:.2}")),
@@ -227,6 +227,15 @@ fn apply_geom_prop(ell: &mut Ellipse, field: &str, value: &str) {
"center_x" => ell.center.x = v,
"center_y" => ell.center.y = v,
"center_z" => ell.center.z = v,
"major_r" => {
let cur = ell.major_axis_length();
if cur > 1e-12 && v > 0.0 {
let s = v / cur;
ell.major_axis.x *= s;
ell.major_axis.y *= s;
ell.major_axis.z *= s;
}
}
"ratio" => ell.minor_axis_ratio = v,
"start_param" => ell.start_parameter = v,
"end_param" => ell.end_parameter = v,
+16 -1
View File
@@ -149,7 +149,14 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
if h.is_associative { "Yes" } else { "No" },
),
ro("Layer override", "layer_override", String::new()),
ro("Double", "double", if h.is_double { "Yes" } else { "No" }),
Property {
label: "Double".into(),
field: "double",
value: PropValue::BoolToggle {
field: "double",
value: h.is_double,
},
},
ro(
"Spacing",
"spacing",
@@ -186,6 +193,14 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
}
fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
if field == "double" {
h.is_double = if value == "toggle" {
!h.is_double
} else {
value == "true"
};
return;
}
let Some(v) = parse_f64(value) else {
return;
};
-1
View File
@@ -209,7 +209,6 @@ fn properties(leader: &Leader) -> Vec<PropSection> {
creation_type_str(&leader.creation_type),
&["With Text", "With Tolerance", "With Block", "No Annotation"],
),
bool_toggle("Arrowhead", "arrow_enabled", leader.arrow_enabled),
Property {
label: "Dim style".into(),
field: "dimension_style",
+36 -11
View File
@@ -7,7 +7,7 @@ use crate::entities::common::{
};
use crate::entities::traits::TruckConvertible;
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::model::object::{GripApply, GripDef, PropSection};
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::model::wire_model::TangentGeom;
const TAU: f64 = std::f64::consts::TAU;
@@ -344,22 +344,47 @@ fn properties(pline: &LwPolyline) -> Vec<PropSection> {
PropSection {
title: "Misc".into(),
props: vec![
ro(
"Closed",
"closed",
if pline.is_closed { "Yes" } else { "No" },
),
ro(
"Linetype generation",
"plinegen",
if pline.plinegen { "Yes" } else { "No" },
),
Property {
label: "Closed".into(),
field: "closed",
value: PropValue::BoolToggle {
field: "closed",
value: pline.is_closed,
},
},
Property {
label: "Linetype generation".into(),
field: "plinegen",
value: PropValue::BoolToggle {
field: "plinegen",
value: pline.plinegen,
},
},
],
},
]
}
fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
match field {
"closed" => {
pline.is_closed = if value == "toggle" {
!pline.is_closed
} else {
value == "true"
};
return;
}
"plinegen" => {
pline.plinegen = if value == "toggle" {
!pline.plinegen
} else {
value == "true"
};
return;
}
_ => {}
}
let Some(v) = parse_f64(value) else {
return;
};
+9 -1
View File
@@ -211,7 +211,11 @@ impl PropertyEditable for MLine {
PropSection {
title: "Misc".into(),
props: vec![
ro("Style", "ml_style", self.style_name.clone()),
Property {
label: "Style".into(),
field: "ml_style",
value: PropValue::EditText(self.style_name.clone()),
},
Property {
label: "Style justification".into(),
field: "ml_justification",
@@ -249,6 +253,10 @@ impl PropertyEditable for MLine {
};
return;
}
"ml_style" => {
self.style_name = value.to_string();
return;
}
_ => {}
}
let Ok(v) = value.trim().parse::<f64>() else {
+12 -13
View File
@@ -543,10 +543,10 @@ fn properties(ml: &MultiLeader) -> Vec<PropSection> {
&["None", "MText", "Block", "Tolerance"],
),
edit("Overall scale", "scale_factor", ml.scale_factor),
ro(
bool_toggle(
"Annotative",
"enable_annotation_scale",
if ml.enable_annotation_scale { "Yes" } else { "No" },
ml.enable_annotation_scale,
),
// No stored annotative-scale name on the entity.
ro("Annotative scale", "annotative_scale", String::new()),
@@ -588,11 +588,7 @@ fn properties(ml: &MultiLeader) -> Vec<PropSection> {
ro("Maximum leader points", "max_leader_points", String::new()),
ro("First segment angle", "first_segment_angle", String::new()),
ro("Second segment angle", "second_segment_angle", String::new()),
ro(
"Landing",
"enable_landing",
if ml.enable_landing { "Yes" } else { "No" },
),
bool_toggle("Landing", "enable_landing", ml.enable_landing),
edit(
"Landing distance",
"landing_distance",
@@ -652,14 +648,10 @@ fn properties(ml: &MultiLeader) -> Vec<PropSection> {
format!("{:?}", ml.text_attachment_direction),
),
edit("Landing gap", "landing_gap", ctx.landing_gap),
ro(
bool_toggle(
"Background mask",
"background_fill_enabled",
if ctx.background_fill_enabled {
"Yes"
} else {
"No"
},
ctx.background_fill_enabled,
),
],
};
@@ -771,6 +763,13 @@ fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) {
value == "true"
}
}
"background_fill_enabled" => {
ml.context.background_fill_enabled = if value == "toggle" {
!ml.context.background_fill_enabled
} else {
value == "true"
}
}
"dogleg_length" => {
if let Some(v) = f64(value) {
ml.dogleg_length = v;
+87 -29
View File
@@ -400,20 +400,20 @@ impl PropertyEditable for Polyline2D {
area = (area * 0.5).abs();
let v0 = self.vertices.first();
let vertex_x = v0.map(|v| format!("{:.4}", v.location.x)).unwrap_or_default();
let vertex_y = v0.map(|v| format!("{:.4}", v.location.y)).unwrap_or_default();
let seg_start_w = v0.map(|v| format!("{:.4}", v.start_width)).unwrap_or_default();
let seg_end_w = v0.map(|v| format!("{:.4}", v.end_width)).unwrap_or_default();
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();
vec![
PropSection {
title: "Geometry".into(),
props: vec![
ro("Current Vertex", "pl2_current_vertex", if n > 0 { "1" } else { "" }),
ro("Vertex X", "pl2_vertex_x", vertex_x),
ro("Vertex Y", "pl2_vertex_y", vertex_y),
ro("Start segment width", "pl2_seg_start_w", seg_start_w),
ro("End segment width", "pl2_seg_end_w", seg_end_w),
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),
edit("End segment width", "pl2_seg_end_w", seg_end_w),
edit("Global width", "pl2_start_w", self.start_width),
edit("Elevation", "pl2_elevation", self.elevation),
ro("Area", "pl2_area", format!("{area:.4}")),
@@ -431,11 +431,14 @@ impl PropertyEditable for Polyline2D {
value: self.is_closed(),
},
},
ro(
"Linetype generation",
"pl2_ltype_gen",
if self.flags.bits() & 128 != 0 { "Enabled" } else { "Disabled" },
),
Property {
label: "Linetype generation".into(),
field: "pl2_ltype_gen",
value: PropValue::BoolToggle {
field: "pl2_ltype_gen",
value: self.flags.bits() & 128 != 0,
},
},
],
},
]
@@ -468,6 +471,43 @@ impl PropertyEditable for Polyline2D {
}
}
}
"pl2_vertex_x" => {
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
vert.location.x = v;
}
}
"pl2_vertex_y" => {
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
vert.location.y = v;
}
}
"pl2_seg_start_w" => {
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
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 v >= 0.0 {
vert.end_width = v;
}
}
}
"pl2_ltype_gen" => {
let on = if value == "toggle" {
self.flags.bits() & 128 == 0
} else {
value == "true"
};
let bits = if on {
self.flags.bits() | 128
} else {
self.flags.bits() & !128
};
self.flags = acadrust::entities::polyline::PolylineFlags::from_bits(bits);
}
_ => {}
}
}
@@ -616,9 +656,9 @@ impl PropertyEditable for Polyline3D {
use acadrust::entities::polyline3d::SmoothSurfaceType as SST;
let n = self.vertices.len();
let v0 = self.vertices.first();
let vertex_x = v0.map(|v| format!("{:.4}", v.position.x)).unwrap_or_default();
let vertex_y = v0.map(|v| format!("{:.4}", v.position.y)).unwrap_or_default();
let vertex_z = v0.map(|v| format!("{:.4}", v.position.z)).unwrap_or_default();
let vertex_x = v0.map(|v| v.position.x).unwrap_or_default();
let vertex_y = v0.map(|v| v.position.y).unwrap_or_default();
let vertex_z = v0.map(|v| v.position.z).unwrap_or_default();
let fit_smooth = match self.smooth_type {
SST::None => "None",
SST::QuadraticBSpline => "Quadratic",
@@ -631,9 +671,9 @@ impl PropertyEditable for Polyline3D {
title: "Geometry".into(),
props: vec![
ro("Vertex", "pl3_vertex", if n > 0 { "1" } else { "" }),
ro("Vertex X", "pl3_vertex_x", vertex_x),
ro("Vertex Y", "pl3_vertex_y", vertex_y),
ro("Vertex Z", "pl3_vertex_z", vertex_z),
edit("Vertex X", "pl3_vertex_x", vertex_x),
edit("Vertex Y", "pl3_vertex_y", vertex_y),
edit("Vertex Z", "pl3_vertex_z", vertex_z),
],
},
PropSection {
@@ -654,17 +694,35 @@ impl PropertyEditable for Polyline3D {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
if field == "pl3_closed" {
let closed = if value == "toggle" {
!self.is_closed()
} else {
value == "true"
};
if closed {
self.close();
} else {
self.open();
match field {
"pl3_closed" => {
let closed = if value == "toggle" {
!self.is_closed()
} else {
value == "true"
};
if closed {
self.close();
} else {
self.open();
}
}
"pl3_vertex_x" => {
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
vert.position.x = v;
}
}
"pl3_vertex_y" => {
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
vert.position.y = v;
}
}
"pl3_vertex_z" => {
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
vert.position.z = v;
}
}
_ => {}
}
}
}
+14 -3
View File
@@ -11,7 +11,7 @@ use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip};
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};
use crate::scene::model::object::{GripApply, GripDef, PropSection, Property, PropValue};
use crate::scene::view::transform;
use crate::scene::model::wire_model::SnapHint;
@@ -118,7 +118,7 @@ impl Grippable for Shape {
// ── PropertyEditable ──────────────────────────────────────────────────────────
impl PropertyEditable for Shape {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
fn geometry_properties(&self, text_style_names: &[String]) -> Vec<PropSection> {
let style_handle_display = match self.style_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
@@ -128,7 +128,14 @@ impl PropertyEditable for Shape {
props: vec![
ro("Name", "shp_name", self.shape_name.clone()),
ro("Number", "shp_number", self.shape_number.to_string()),
ro("Style", "shp_style", self.style_name.clone()),
Property {
label: "Style".into(),
field: "shp_style",
value: PropValue::Choice {
selected: self.style_name.clone(),
options: text_style_names.to_vec(),
},
},
ro("Style Handle", "shp_style_handle", style_handle_display),
edit("Insert X", "shp_ix", self.insertion_point.x),
edit("Insert Y", "shp_iy", self.insertion_point.y),
@@ -146,6 +153,10 @@ impl PropertyEditable for Shape {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
if field == "shp_style" {
self.style_name = value.to_string();
return;
}
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
+65 -24
View File
@@ -162,29 +162,29 @@ fn properties(spline: &Spline) -> Vec<PropSection> {
title: "Data Points".into(),
props: vec![
ro("Show", "show", show),
edit("Degree", "degree", spline.degree as f64),
ro("Degree", "degree", spline.degree.to_string()),
ro(
"Control Point Count",
"ctrl_pt_count",
spline.control_points.len().to_string(),
),
ro("Control Point", "ctrl_pt_index", "0"),
ro(
edit(
"Control Point X",
"ctrl_pt_x",
format!("{:.4}", cp0.map(|p| p.x).unwrap_or(0.0)),
cp0.map(|p| p.x).unwrap_or(0.0),
),
ro(
edit(
"Control Point Y",
"ctrl_pt_y",
format!("{:.4}", cp0.map(|p| p.y).unwrap_or(0.0)),
cp0.map(|p| p.y).unwrap_or(0.0),
),
ro(
edit(
"Control Point Z",
"ctrl_pt_z",
format!("{:.4}", cp0.map(|p| p.z).unwrap_or(0.0)),
cp0.map(|p| p.z).unwrap_or(0.0),
),
ro("Weight", "weight", format!("{:.4}", w0.unwrap_or(1.0))),
edit("Weight", "weight", w0.unwrap_or(1.0)),
ro("Knot Parameterization", "knot_param", knot_param),
ro(
"Fit Point Count",
@@ -192,28 +192,28 @@ fn properties(spline: &Spline) -> Vec<PropSection> {
spline.fit_points.len().to_string(),
),
ro("Fit Point", "fit_pt_index", "0"),
ro(
edit(
"Fit Point X",
"fit_pt_x",
format!("{:.4}", fp0.map(|p| p.x).unwrap_or(0.0)),
fp0.map(|p| p.x).unwrap_or(0.0),
),
ro(
edit(
"Fit Point Y",
"fit_pt_y",
format!("{:.4}", fp0.map(|p| p.y).unwrap_or(0.0)),
fp0.map(|p| p.y).unwrap_or(0.0),
),
ro(
edit(
"Fit Point Z",
"fit_pt_z",
format!("{:.4}", fp0.map(|p| p.z).unwrap_or(0.0)),
fp0.map(|p| p.z).unwrap_or(0.0),
),
ro("Fit Tolerance", "fit_tolerance", format!("{:.6}", spline.fit_tolerance)),
ro("Start Tangent X", "start_tan_x", format!("{:.4}", spline.begin_tangent.x)),
ro("Start Tangent Y", "start_tan_y", format!("{:.4}", spline.begin_tangent.y)),
ro("Start Tangent Z", "start_tan_z", format!("{:.4}", spline.begin_tangent.z)),
ro("End Tangent X", "end_tan_x", format!("{:.4}", spline.end_tangent.x)),
ro("End Tangent Y", "end_tan_y", format!("{:.4}", spline.end_tangent.y)),
ro("End Tangent Z", "end_tan_z", format!("{:.4}", spline.end_tangent.z)),
edit("Fit Tolerance", "fit_tolerance", spline.fit_tolerance),
edit("Start Tangent X", "start_tan_x", spline.begin_tangent.x),
edit("Start Tangent Y", "start_tan_y", spline.begin_tangent.y),
edit("Start Tangent Z", "start_tan_z", spline.begin_tangent.z),
edit("End Tangent X", "end_tan_x", spline.end_tangent.x),
edit("End Tangent Y", "end_tan_y", spline.end_tangent.y),
edit("End Tangent Z", "end_tan_z", spline.end_tangent.z),
],
},
PropSection {
@@ -229,10 +229,51 @@ fn properties(spline: &Spline) -> Vec<PropSection> {
}
fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) {
if field == "degree" {
if let Some(v) = parse_f64(value) {
spline.degree = v.round() as i32;
let Some(v) = parse_f64(value) else { return };
match field {
"ctrl_pt_x" => {
if let Some(cp) = spline.control_points.first_mut() {
cp.x = v;
}
}
"ctrl_pt_y" => {
if let Some(cp) = spline.control_points.first_mut() {
cp.y = v;
}
}
"ctrl_pt_z" => {
if let Some(cp) = spline.control_points.first_mut() {
cp.z = v;
}
}
"weight" => {
if let Some(w) = spline.weights.first_mut() {
*w = v;
}
}
"fit_pt_x" => {
if let Some(fp) = spline.fit_points.first_mut() {
fp.x = v;
}
}
"fit_pt_y" => {
if let Some(fp) = spline.fit_points.first_mut() {
fp.y = v;
}
}
"fit_pt_z" => {
if let Some(fp) = spline.fit_points.first_mut() {
fp.z = v;
}
}
"fit_tolerance" => spline.fit_tolerance = v,
"start_tan_x" => spline.begin_tangent.x = v,
"start_tan_y" => spline.begin_tangent.y = v,
"start_tan_z" => spline.begin_tangent.z = v,
"end_tan_x" => spline.end_tangent.x = v,
"end_tan_y" => spline.end_tangent.y = v,
"end_tan_z" => spline.end_tangent.z = v,
_ => {}
}
}
+39 -25
View File
@@ -8,7 +8,7 @@ use crate::entities::text_support::{
};
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};
use crate::scene::model::object::{GripApply, GripDef, PropSection, Property, PropValue};
use crate::scene::view::transform;
use crate::scene::model::wire_model::SnapHint;
@@ -828,8 +828,12 @@ impl PropertyEditable for Table {
_ => "(none)".to_string(),
}
};
let yes_no = |b: bool| -> String {
if b { "Yes".into() } else { "No".into() }
let toggle = |label: &'static str, field: &'static str, b: bool| -> Property {
Property {
label: label.into(),
field,
value: PropValue::BoolToggle { field, value: b },
}
};
// Direction = angle of the horizontal direction vector in the XY plane.
let direction_deg =
@@ -862,47 +866,39 @@ impl PropertyEditable for Table {
PropSection {
title: "Table Breaks".into(),
props: vec![
ro(
toggle(
"Enabled",
"tbl_break_enabled",
yes_no(self.break_options.contains(BreakOptionFlags::ENABLE_BREAKS)),
self.break_options.contains(BreakOptionFlags::ENABLE_BREAKS),
),
ro(
"Direction",
"tbl_break_direction",
format!("{:?}", self.break_flow_direction),
),
ro(
toggle(
"Repeat top labels",
"tbl_break_repeat_top",
yes_no(
self.break_options
.contains(BreakOptionFlags::REPEAT_TOP_LABELS),
),
self.break_options
.contains(BreakOptionFlags::REPEAT_TOP_LABELS),
),
ro(
toggle(
"Repeat bottom labels",
"tbl_break_repeat_bottom",
yes_no(
self.break_options
.contains(BreakOptionFlags::REPEAT_BOTTOM_LABELS),
),
self.break_options
.contains(BreakOptionFlags::REPEAT_BOTTOM_LABELS),
),
ro(
toggle(
"Manual positions",
"tbl_break_manual_positions",
yes_no(
self.break_options
.contains(BreakOptionFlags::ALLOW_MANUAL_POSITIONS),
),
self.break_options
.contains(BreakOptionFlags::ALLOW_MANUAL_POSITIONS),
),
ro(
toggle(
"Manual heights",
"tbl_break_manual_heights",
yes_no(
self.break_options
.contains(BreakOptionFlags::ALLOW_MANUAL_HEIGHTS),
),
self.break_options
.contains(BreakOptionFlags::ALLOW_MANUAL_HEIGHTS),
),
ro("Break height", "tbl_break_height", String::new()),
edit("Spacing", "tbl_break_spacing", self.break_spacing),
@@ -913,6 +909,24 @@ impl PropertyEditable for Table {
fn apply_geom_prop(&mut self, field: &str, value: &str) {
use crate::entities::common::parse_f64;
use acadrust::entities::table::BreakOptionFlags;
let flag = match field {
"tbl_break_enabled" => Some(BreakOptionFlags::ENABLE_BREAKS),
"tbl_break_repeat_top" => Some(BreakOptionFlags::REPEAT_TOP_LABELS),
"tbl_break_repeat_bottom" => Some(BreakOptionFlags::REPEAT_BOTTOM_LABELS),
"tbl_break_manual_positions" => Some(BreakOptionFlags::ALLOW_MANUAL_POSITIONS),
"tbl_break_manual_heights" => Some(BreakOptionFlags::ALLOW_MANUAL_HEIGHTS),
_ => None,
};
if let Some(flag) = flag {
let on = if value == "toggle" {
!self.break_options.contains(flag)
} else {
value == "true"
};
self.break_options.set(flag, on);
return;
}
if field == "tbl_break_spacing" {
if let Some(v) = parse_f64(value) {
self.break_spacing = v;
+52 -19
View File
@@ -222,9 +222,9 @@ fn properties(t: &Text, text_style_names: &[String]) -> Vec<PropSection> {
edit("Rotation", "rotation", t.rotation.to_degrees()),
edit("Width factor", "width_factor", t.width_factor),
edit("Obliquing", "oblique_angle", t.oblique_angle.to_degrees()),
ro("Text alignment X", "align_x", format!("{:.4}", ap.x)),
ro("Text alignment Y", "align_y", format!("{:.4}", ap.y)),
ro("Text alignment Z", "align_z", format!("{:.4}", ap.z)),
edit("Text alignment X", "align_x", ap.x),
edit("Text alignment Y", "align_y", ap.y),
edit("Text alignment Z", "align_z", ap.z),
],
},
PropSection {
@@ -238,24 +238,22 @@ fn properties(t: &Text, text_style_names: &[String]) -> Vec<PropSection> {
PropSection {
title: "Misc".into(),
props: vec![
ro(
"Upside down",
"upside_down",
if t.generation_flags & 0x4 != 0 {
"Yes"
} else {
"No"
Property {
label: "Upside down".into(),
field: "upside_down",
value: PropValue::BoolToggle {
field: "upside_down",
value: t.generation_flags & 0x4 != 0,
},
),
ro(
"Backward",
"backward",
if t.generation_flags & 0x2 != 0 {
"Yes"
} else {
"No"
},
Property {
label: "Backward".into(),
field: "backward",
value: PropValue::BoolToggle {
field: "backward",
value: t.generation_flags & 0x2 != 0,
},
),
},
],
},
]
@@ -295,6 +293,32 @@ fn apply_geom_prop(t: &mut Text, field: &str, value: &str) {
sync_text_alignment_point(t);
return;
}
"upside_down" => {
let set = if value == "toggle" {
t.generation_flags & 0x4 == 0
} else {
value == "true"
};
if set {
t.generation_flags |= 0x4;
} else {
t.generation_flags &= !0x4;
}
return;
}
"backward" => {
let set = if value == "toggle" {
t.generation_flags & 0x2 == 0
} else {
value == "true"
};
if set {
t.generation_flags |= 0x2;
} else {
t.generation_flags &= !0x2;
}
return;
}
_ => {}
}
let Some(v) = parse_f64(value) else {
@@ -304,6 +328,15 @@ fn apply_geom_prop(t: &mut Text, field: &str, value: &str) {
"ins_x" => t.insertion_point.x = v,
"ins_y" => t.insertion_point.y = v,
"ins_z" => t.insertion_point.z = v,
"align_x" | "align_y" | "align_z" => {
let ins = t.insertion_point;
let ap = t.alignment_point.get_or_insert(ins);
match field {
"align_x" => ap.x = v,
"align_y" => ap.y = v,
_ => ap.z = v,
}
}
"height" if v > 0.0 => t.height = v,
"rotation" => t.rotation = v.to_radians(),
"width_factor" if v > 0.0 => t.width_factor = v,
+97 -12
View File
@@ -10,7 +10,7 @@ use crate::command::EntityTransform;
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
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};
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::model::wire_model::SnapHint;
// ── Helpers ───────────────────────────────────────────────────────────────────
@@ -147,8 +147,6 @@ impl Grippable for Underlay {
impl PropertyEditable for Underlay {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
let yes_no = |b: bool| if b { "Yes" } else { "No" };
// Width / Height derived from the world-space clip boundary bounds
// (the only in-entity source of the placed footprint size).
let (width, height) = if self.clip_boundary_vertices.is_empty() {
@@ -193,12 +191,22 @@ impl PropertyEditable for Underlay {
props: vec![
edit("Contrast", "ul_contrast", self.contrast as f64),
edit("Fade", "ul_fade", self.fade as f64),
ro("Monochrome", "ul_mono", yes_no(monochrome)),
ro(
"Adjust Colors for Background",
"ul_adjust_bg",
yes_no(adjust_bg),
),
Property {
label: "Monochrome".into(),
field: "ul_mono",
value: PropValue::BoolToggle {
field: "ul_mono",
value: monochrome,
},
},
Property {
label: "Adjust Colors for Background".into(),
field: "ul_adjust_bg",
value: PropValue::BoolToggle {
field: "ul_adjust_bg",
value: adjust_bg,
},
},
],
},
PropSection {
@@ -208,9 +216,30 @@ impl PropertyEditable for Underlay {
// UnderlayDefinition object, not reachable from the entity.
ro("Underlay name", "ul_name", String::new()),
ro("Underlay path", "ul_path", String::new()),
ro("Show underlay", "ul_on", yes_no(show)),
ro("Clipping", "ul_clip", yes_no(clipping)),
ro("Show clipped", "ul_clip_inverted", yes_no(self.clip_inverted)),
Property {
label: "Show underlay".into(),
field: "ul_on",
value: PropValue::BoolToggle {
field: "ul_on",
value: show,
},
},
Property {
label: "Clipping".into(),
field: "ul_clip",
value: PropValue::BoolToggle {
field: "ul_clip",
value: clipping,
},
},
Property {
label: "Show clipped".into(),
field: "ul_clip_inverted",
value: PropValue::BoolToggle {
field: "ul_clip_inverted",
value: self.clip_inverted,
},
},
ro("Underlay layers", "ul_layers", String::new()),
],
},
@@ -218,6 +247,62 @@ impl PropertyEditable for Underlay {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
match field {
"ul_mono" => {
let on = if value == "toggle" {
!self.flags.contains(UnderlayDisplayFlags::MONOCHROME)
} else {
value == "true"
};
self.set_monochrome(on);
return;
}
"ul_adjust_bg" => {
let on = if value == "toggle" {
!self.flags.contains(UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND)
} else {
value == "true"
};
if on {
self.flags |= UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND;
} else {
self.flags -= UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND;
}
return;
}
"ul_on" => {
let on = if value == "toggle" {
!self.flags.contains(UnderlayDisplayFlags::ON)
} else {
value == "true"
};
self.set_on(on);
return;
}
"ul_clip" => {
let on = if value == "toggle" {
!self.flags.contains(UnderlayDisplayFlags::CLIPPING)
} else {
value == "true"
};
if on {
self.flags |= UnderlayDisplayFlags::CLIPPING;
} else {
self.flags -= UnderlayDisplayFlags::CLIPPING;
}
return;
}
"ul_clip_inverted" => {
let on = if value == "toggle" {
!self.clip_inverted
} else {
value == "true"
};
self.clip_inverted = on;
return;
}
_ => {}
}
if let Ok(v) = value.trim().parse::<f64>() {
match field {
"ul_ix" => self.insertion_point.x = v,