feat(properties): conform entity property groups to PROPERTIES.md

Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.

Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-02 23:03:18 +03:00
1 parent b2b2bfb246
commit b9cebab951
26 files changed
+1917 -1558

No files matched your search

+47 -7
View File
@@ -2,7 +2,9 @@ use acadrust::entities::Arc;
use truck_modeling::{builder, Point3};
use crate::command::EntityTransform;
use crate::entities::common::{center_grip, edit_prop as edit, parse_f64, square_grip};
use crate::entities::common::{
center_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
};
use crate::entities::traits::TruckConvertible;
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::model::object::{GripApply, GripDef, PropSection};
@@ -122,6 +124,31 @@ fn grips(arc: &Arc) -> Vec<GripDef> {
}
fn properties(arc: &Arc) -> Vec<PropSection> {
let r = arc.radius;
let sa = arc.start_angle;
let ea = arc.end_angle;
let sweep = (ea - sa).rem_euclid(TAU);
let total_angle = sweep.to_degrees();
let arc_length = r * sweep;
let area = 0.5 * r * r * sweep;
let normal = (arc.normal.x, arc.normal.y, arc.normal.z);
let (ax, ay) = crate::scene::view::transform::ocs_axes(normal);
let (cwx, cwy, cwz) = crate::scene::view::transform::ocs_point_to_wcs(
(arc.center.x, arc.center.y, arc.center.z),
normal,
);
let arc_pt = |a: f64| {
let (c, s) = (a.cos(), a.sin());
(
cwx + r * c * ax.0 + r * s * ay.0,
cwy + r * c * ax.1 + r * s * ay.1,
cwz + r * c * ax.2 + r * s * ay.2,
)
};
let (sx, sy, sz) = arc_pt(sa);
let (ex, ey, ez) = arc_pt(ea);
vec![PropSection {
title: "Geometry".into(),
props: vec![
@@ -129,12 +156,21 @@ fn properties(arc: &Arc) -> Vec<PropSection> {
edit("Center Y", "center_y", arc.center.y),
edit("Center Z", "center_z", arc.center.z),
edit("Radius", "radius", arc.radius),
edit(
"Start Angle (deg)",
"start_angle",
arc.start_angle.to_degrees(),
),
edit("End Angle (deg)", "end_angle", arc.end_angle.to_degrees()),
edit("Start angle", "start_angle", sa.to_degrees()),
edit("End angle", "end_angle", ea.to_degrees()),
ro("Total angle", "total_angle", format!("{total_angle:.2}")),
ro("Arc length", "arc_length", format!("{arc_length:.4}")),
ro("Area", "area", format!("{area:.4}")),
ro("Start X", "start_x", format!("{sx:.4}")),
ro("Start Y", "start_y", format!("{sy:.4}")),
ro("Start Z", "start_z", format!("{sz:.4}")),
ro("End X", "end_x", format!("{ex:.4}")),
ro("End Y", "end_y", format!("{ey:.4}")),
ro("End Z", "end_z", format!("{ez:.4}")),
edit("Normal X", "normal_x", arc.normal.x),
edit("Normal Y", "normal_y", arc.normal.y),
edit("Normal Z", "normal_z", arc.normal.z),
edit("Thickness", "thickness", arc.thickness),
],
}]
}
@@ -150,6 +186,10 @@ fn apply_geom_prop(arc: &mut Arc, field: &str, value: &str) {
"radius" if v > 0.0 => arc.radius = v,
"start_angle" => arc.start_angle = v.to_radians(),
"end_angle" => arc.end_angle = v.to_radians(),
"normal_x" => arc.normal.x = v,
"normal_y" => arc.normal.y = v,
"normal_z" => arc.normal.z = v,
"thickness" => arc.thickness = v,
_ => {}
}
}
+171 -149
View File
@@ -385,26 +385,33 @@ impl Grippable for AttributeDefinition {
impl PropertyEditable for AttributeDefinition {
fn geometry_properties(&self, text_style_names: &[String]) -> Vec<PropSection> {
let mut props = vec![
let mut text_props = vec![
ro("Tag", "att_tag", self.tag.clone()),
ro("Prompt", "att_prompt", self.prompt.clone()),
Property {
label: "Default".into(),
label: "Value".into(),
field: "att_default",
value: PropValue::EditText(self.default_value.clone()),
},
edit("Insert X", "att_ix", self.insertion_point.x),
edit("Insert Y", "att_iy", self.insertion_point.y),
edit("Insert Z", "att_iz", self.insertion_point.z),
edit("Align X", "att_ax", self.alignment_point.x),
edit("Align Y", "att_ay", self.alignment_point.y),
edit("Align Z", "att_az", self.alignment_point.z),
edit("Height", "att_h", self.height),
edit("Rotation", "att_rot", self.rotation.to_degrees()),
edit("Width Factor", "att_wf", self.width_factor),
edit("Oblique Angle", "att_ob", self.oblique_angle.to_degrees()),
Property {
label: "H-Align".into(),
label: "Style".into(),
field: "att_style",
value: PropValue::Choice {
selected: if self.text_style.trim().is_empty() {
"Standard".into()
} else {
self.text_style.clone()
},
options: text_style_names.to_vec(),
},
},
ro(
"Annotative",
"att_annotative",
bool_yn(self.flags.annotative),
),
Property {
label: "Justify".into(),
field: "att_halign",
value: PropValue::Choice {
selected: halign_str(self.horizontal_alignment).to_string(),
@@ -425,63 +432,71 @@ impl PropertyEditable for AttributeDefinition {
.collect(),
},
},
Property {
label: "Style".into(),
field: "att_style",
value: PropValue::Choice {
selected: if self.text_style.trim().is_empty() {
"Standard".into()
} else {
self.text_style.clone()
},
options: text_style_names.to_vec(),
},
},
edit("Height", "att_h", self.height),
edit("Rotation", "att_rot", self.rotation.to_degrees()),
edit("Width factor", "att_wf", self.width_factor),
edit("Obliquing", "att_ob", self.oblique_angle.to_degrees()),
edit("Text alignment X", "att_ax", self.alignment_point.x),
edit("Text alignment Y", "att_ay", self.alignment_point.y),
edit("Text alignment Z", "att_az", self.alignment_point.z),
ro(
"Generation",
"att_gen_flags",
format!("{:#06b}", self.text_generation_flags & 0xff),
),
ro(
"Field Length",
"Boundary width",
"att_field_len",
self.field_length.to_string(),
),
Property {
label: "MText Mode".into(),
field: "att_mtext_flag",
value: PropValue::Choice {
selected: mtext_flag_str(self.mtext_flag).to_string(),
options: ["SingleLine", "MultiLine", "ConstantMultiLine"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
ro("Multiline", "att_is_multiline", bool_yn(self.is_multiline)),
ro("Line Count", "att_line_count", self.line_count.to_string()),
ro("Lock Position", "att_lock_pos", bool_yn(self.lock_position)),
ro("Invisible", "att_invisible", bool_yn(self.flags.invisible)),
ro("Constant", "att_constant", bool_yn(self.flags.constant)),
ro("Verify", "att_verify", bool_yn(self.flags.verify)),
ro("Preset", "att_preset", bool_yn(self.flags.preset)),
ro(
"Annotative",
"att_annotative",
bool_yn(self.flags.annotative),
"Upside down",
"att_upside_down",
bool_yn(self.text_generation_flags & 0x4 != 0),
),
ro(
"Backward",
"att_backward",
bool_yn(self.text_generation_flags & 0x2 != 0),
),
];
// Constant attributes can't be edited at insert time — surface that
// by marking the Default field read-only.
// by marking the Value field read-only.
if self.flags.constant {
if let Some(p) = props.iter_mut().find(|p| p.field == "att_default") {
if let Some(p) = text_props.iter_mut().find(|p| p.field == "att_default") {
p.value = PropValue::ReadOnly(self.default_value.clone());
}
}
vec![PropSection {
title: "Geometry".into(),
props,
}]
vec![
PropSection {
title: "Text".into(),
props: text_props,
},
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "att_ix", self.insertion_point.x),
edit("Position Y", "att_iy", self.insertion_point.y),
edit("Position Z", "att_iz", self.insertion_point.z),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro("Invisible", "att_invisible", bool_yn(self.flags.invisible)),
ro("Constant", "att_constant", bool_yn(self.flags.constant)),
ro("Verify", "att_verify", bool_yn(self.flags.verify)),
ro("Preset", "att_preset", bool_yn(self.flags.preset)),
ro("Lock position", "att_lock_pos", bool_yn(self.lock_position)),
Property {
label: "Multiple lines".into(),
field: "att_mtext_flag",
value: PropValue::Choice {
selected: mtext_flag_str(self.mtext_flag).to_string(),
options: ["SingleLine", "MultiLine", "ConstantMultiLine"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
@@ -626,101 +641,108 @@ impl Grippable for AttributeEntity {
impl PropertyEditable for AttributeEntity {
fn geometry_properties(&self, text_style_names: &[String]) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Tag", "atte_tag", self.tag.clone()),
Property {
label: "Value".into(),
field: "atte_val",
value: PropValue::EditText(self.value.clone()),
},
edit("Insert X", "atte_ix", self.insertion_point.x),
edit("Insert Y", "atte_iy", self.insertion_point.y),
edit("Insert Z", "atte_iz", self.insertion_point.z),
edit("Align X", "atte_ax", self.alignment_point.x),
edit("Align Y", "atte_ay", self.alignment_point.y),
edit("Align Z", "atte_az", self.alignment_point.z),
edit("Height", "atte_h", self.height),
edit("Rotation", "atte_rot", self.rotation.to_degrees()),
edit("Width Factor", "atte_wf", self.width_factor),
edit("Oblique Angle", "atte_ob", self.oblique_angle.to_degrees()),
Property {
label: "H-Align".into(),
field: "atte_halign",
value: PropValue::Choice {
selected: halign_str(self.horizontal_alignment).to_string(),
options: ["Left", "Center", "Right", "Aligned", "Middle", "Fit"]
.into_iter()
.map(str::to_string)
.collect(),
vec![
PropSection {
title: "Text".into(),
props: vec![
ro("Tag", "atte_tag", self.tag.clone()),
ro("Prompt", "atte_prompt", String::new()),
Property {
label: "Value".into(),
field: "atte_val",
value: PropValue::EditText(self.value.clone()),
},
},
Property {
label: "V-Align".into(),
field: "atte_valign",
value: PropValue::Choice {
selected: valign_str(self.vertical_alignment).to_string(),
options: ["Baseline", "Bottom", "Middle", "Top"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
Property {
label: "Style".into(),
field: "atte_style",
value: PropValue::Choice {
selected: if self.text_style.trim().is_empty() {
"Standard".into()
} else {
self.text_style.clone()
Property {
label: "Style".into(),
field: "atte_style",
value: PropValue::Choice {
selected: if self.text_style.trim().is_empty() {
"Standard".into()
} else {
self.text_style.clone()
},
options: text_style_names.to_vec(),
},
options: text_style_names.to_vec(),
},
},
ro(
"Generation",
"atte_gen_flags",
format!("{:#06b}", self.text_generation_flags & 0xff),
),
ro(
"Field Length",
"atte_field_len",
self.field_length.to_string(),
),
ro(
"MText Mode",
"atte_mtext_flag",
mtext_flag_str(self.mtext_flag),
),
ro("Multiline", "atte_is_multiline", bool_yn(self.is_multiline)),
ro("Line Count", "atte_line_count", self.line_count.to_string()),
ro(
"Lock Position",
"atte_lock_pos",
bool_yn(self.lock_position),
),
ro(
"Definition",
"atte_attdef",
if self.attdef_handle.is_null() {
"(none)".into()
} else {
format!("{:X}", self.attdef_handle.value())
Property {
label: "Justify".into(),
field: "atte_halign",
value: PropValue::Choice {
selected: halign_str(self.horizontal_alignment).to_string(),
options: ["Left", "Center", "Right", "Aligned", "Middle", "Fit"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
),
ro("Invisible", "atte_invisible", bool_yn(self.flags.invisible)),
ro("Constant", "atte_constant", bool_yn(self.flags.constant)),
ro("Verify", "atte_verify", bool_yn(self.flags.verify)),
ro("Preset", "atte_preset", bool_yn(self.flags.preset)),
ro(
"Annotative",
"atte_annotative",
bool_yn(self.flags.annotative),
),
],
}]
Property {
label: "V-Align".into(),
field: "atte_valign",
value: PropValue::Choice {
selected: valign_str(self.vertical_alignment).to_string(),
options: ["Baseline", "Bottom", "Middle", "Top"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
ro(
"Annotative",
"atte_annotative",
bool_yn(self.flags.annotative),
),
edit("Height", "atte_h", self.height),
edit("Rotation", "atte_rot", self.rotation.to_degrees()),
edit("Width factor", "atte_wf", self.width_factor),
edit("Obliquing", "atte_ob", self.oblique_angle.to_degrees()),
edit("Text alignment X", "atte_ax", self.alignment_point.x),
edit("Text alignment Y", "atte_ay", self.alignment_point.y),
edit("Text alignment Z", "atte_az", self.alignment_point.z),
ro(
"Boundary width",
"atte_field_len",
self.field_length.to_string(),
),
],
},
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "atte_ix", self.insertion_point.x),
edit("Position Y", "atte_iy", self.insertion_point.y),
edit("Position Z", "atte_iz", self.insertion_point.z),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro(
"Upside down",
"atte_upside_down",
bool_yn(self.text_generation_flags & 0x4 != 0),
),
ro(
"Backward",
"atte_backward",
bool_yn(self.text_generation_flags & 0x2 != 0),
),
ro("Invisible", "atte_invisible", bool_yn(self.flags.invisible)),
ro(
"Multiple lines",
"atte_mtext_flag",
mtext_flag_str(self.mtext_flag),
),
ro("Constant", "atte_constant", bool_yn(self.flags.constant)),
ro("Verify", "atte_verify", bool_yn(self.flags.verify)),
ro("Preset", "atte_preset", bool_yn(self.flags.preset)),
ro(
"Lock position",
"atte_lock_pos",
bool_yn(self.lock_position),
),
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
+14 -7
View File
@@ -119,23 +119,26 @@ fn grips(circle: &Circle) -> Vec<GripDef> {
}
fn properties(circle: &Circle) -> Vec<PropSection> {
use std::f64::consts::PI;
let r = circle.radius;
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("Center X", "center_x", circle.center.x),
edit("Center Y", "center_y", circle.center.y),
edit("Center Z", "center_z", circle.center.z),
edit("Radius", "radius", circle.radius),
ro(
"Diameter",
"diameter",
format!("{:.4}", circle.radius * 2.0),
),
edit("Radius", "radius", r),
ro("Diameter", "diameter", format!("{:.4}", r * 2.0)),
ro(
"Circumference",
"circumference",
format!("{:.4}", circle.radius * 2.0 * std::f64::consts::PI),
format!("{:.4}", 2.0 * PI * r),
),
ro("Area", "area", format!("{:.4}", PI * r * r)),
edit("Normal X", "normal_x", circle.normal.x),
edit("Normal Y", "normal_y", circle.normal.y),
edit("Normal Z", "normal_z", circle.normal.z),
edit("Thickness", "thickness", circle.thickness),
],
}]
}
@@ -149,6 +152,10 @@ fn apply_geom_prop(circle: &mut Circle, field: &str, value: &str) {
"center_y" => circle.center.y = v,
"center_z" => circle.center.z = v,
"radius" if v > 0.0 => circle.radius = v,
"normal_x" => circle.normal.x = v,
"normal_y" => circle.normal.y = v,
"normal_z" => circle.normal.z = v,
"thickness" => circle.thickness = v,
_ => {}
}
}
+76 -25
View File
@@ -2,7 +2,9 @@ use acadrust::entities::Ellipse;
use truck_modeling::{builder, BSplineCurve, Curve, Edge, KnotVec, Point3, Wire};
use crate::command::EntityTransform;
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
use crate::entities::common::{
center_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
};
use crate::entities::traits::TruckConvertible;
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::model::object::{GripApply, GripDef, PropSection};
@@ -159,32 +161,81 @@ fn grips(ell: &Ellipse) -> Vec<GripDef> {
}
fn properties(ell: &Ellipse) -> Vec<PropSection> {
let r_major = (ell.major_axis.x * ell.major_axis.x
+ ell.major_axis.y * ell.major_axis.y
+ ell.major_axis.z * ell.major_axis.z)
.sqrt();
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("Center X", "center_x", ell.center.x),
edit("Center Y", "center_y", ell.center.y),
edit("Center Z", "center_z", ell.center.z),
ro("Major Radius", "major_r", format!("{r_major:.4}")),
ro(
"Minor Radius",
"minor_r",
format!("{:.4}", r_major * ell.minor_axis_ratio),
),
ro(
"Minor/Major",
"ratio",
format!("{:.4}", ell.minor_axis_ratio),
),
],
}]
use crate::entities::traits::MassPropsCalc;
let r_major = ell.major_axis_length();
let r_minor = ell.minor_axis_length();
// Axis frame in WCS: major-axis unit vector `u`, minor direction `v = normal × u`.
let u = glam::DVec3::new(ell.major_axis.x, ell.major_axis.y, ell.major_axis.z);
let u = if r_major > 1e-12 {
u / r_major
} else {
glam::DVec3::X
};
let n = glam::DVec3::new(ell.normal.x, ell.normal.y, ell.normal.z);
let v = n.cross(u);
// Parametric end point in WCS: center + r_major·cos(t1)·u + r_minor·sin(t1)·v.
let t1 = ell.end_parameter;
let end = glam::DVec3::new(ell.center.x, ell.center.y, ell.center.z)
+ u * (r_major * t1.cos())
+ v * (r_minor * t1.sin());
let start_angle = ell.start_parameter.to_degrees().rem_euclid(360.0);
let end_angle = ell.end_parameter.to_degrees().rem_euclid(360.0);
let props = ell.mass_props();
vec![
PropSection {
title: "Geometry".into(),
props: vec![
edit("Center X", "center_x", ell.center.x),
edit("Center Y", "center_y", ell.center.y),
edit("Center Z", "center_z", ell.center.z),
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}")),
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}")),
ro("End angle", "end_angle", format!("{end_angle:.2}")),
edit("Start parameter", "start_param", ell.start_parameter),
edit("End parameter", "end_param", ell.end_parameter),
ro("Area", "area", format!("{:.4}", props.area)),
ro("Length", "length", format!("{:.4}", props.perimeter)),
],
},
PropSection {
title: "Misc".into(),
props: vec![
edit("Normal X", "normal_x", ell.normal.x),
edit("Normal Y", "normal_y", ell.normal.y),
edit("Normal Z", "normal_z", ell.normal.z),
],
},
]
}
fn apply_geom_prop(_ell: &mut Ellipse, _field: &str, _value: &str) {}
fn apply_geom_prop(ell: &mut Ellipse, field: &str, value: &str) {
let Some(v) = parse_f64(value) else {
return;
};
match field {
"center_x" => ell.center.x = v,
"center_y" => ell.center.y = v,
"center_z" => ell.center.z = v,
"ratio" => ell.minor_axis_ratio = v,
"start_param" => ell.start_parameter = v,
"end_param" => ell.end_parameter = v,
"normal_x" => ell.normal.x = v,
"normal_y" => ell.normal.y = v,
"normal_z" => ell.normal.z = v,
_ => {}
}
}
fn apply_grip(ell: &mut Ellipse, grip_id: usize, apply: GripApply) {
match (grip_id, apply) {
+156 -54
View File
@@ -8,6 +8,59 @@ use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Pr
use crate::scene::convert::tess_util::{arc_segments, arc_signed_span, wire_chord_tol, FallbackGeometry};
use crate::scene::model::wire_model::SnapHint;
/// Signed area of every boundary path, summed via the shoelace formula over
/// the OCS boundary vertices (bulge/curved edges approximated by their end
/// points). Returns the absolute total area.
fn boundary_area(h: &Hatch) -> f64 {
let mut area = 0.0;
for path in &h.paths {
let mut ring: Vec<[f64; 2]> = Vec::new();
for edge in &path.edges {
match edge {
BoundaryEdge::Polyline(poly) => {
for v in &poly.vertices {
ring.push([v.x, v.y]);
}
}
BoundaryEdge::Line(l) => {
ring.push([l.start.x, l.start.y]);
ring.push([l.end.x, l.end.y]);
}
BoundaryEdge::CircularArc(a) => ring.push([a.center.x, a.center.y]),
BoundaryEdge::EllipticArc(e) => ring.push([e.center.x, e.center.y]),
BoundaryEdge::Spline(s) => {
let src = if !s.fit_points.is_empty() {
s.fit_points.iter().map(|p| [p.x, p.y]).collect::<Vec<_>>()
} else {
s.control_points
.iter()
.map(|p| [p.x, p.y])
.collect::<Vec<_>>()
};
ring.extend(src);
}
}
}
let n = ring.len();
if n < 3 {
continue;
}
let mut acc = 0.0;
for i in 0..n {
let a = ring[i];
let b = ring[(i + 1) % n];
acc += a[0] * b[1] - b[0] * a[1];
}
area += acc.abs() * 0.5;
}
area
}
/// Format a color for a read-only row (`ByLayer` / `ByBlock` / index / RGB).
fn color_str(c: acadrust::types::Color) -> String {
c.to_string()
}
fn properties(h: &Hatch) -> Vec<PropSection> {
let pattern_type = match h.pattern_type {
acadrust::entities::HatchPatternType::Predefined => "Predefined",
@@ -19,61 +72,109 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
acadrust::entities::HatchStyleType::Outer => "Outer",
acadrust::entities::HatchStyleType::Ignore => "Ignore",
};
let fill_type = if h.gradient_color.enabled {
format!("Gradient ({})", h.gradient_color.name)
} else if h.is_solid {
"Solid".into()
} else {
format!("Pattern ({})", h.pattern.name)
};
let boundary_count: usize = h
.paths
.iter()
.map(|p| {
p.edges
.iter()
.map(|e| match e {
BoundaryEdge::Polyline(poly) => poly.vertices.len(),
_ => 1,
})
.sum::<usize>()
})
.sum();
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Fill Type", "fill_type", fill_type),
Property {
label: "Pattern Name".into(),
field: "pattern_name",
value: PropValue::HatchPatternChoice(h.pattern.name.clone()),
let area = boundary_area(h);
let g = &h.gradient_color;
let grad_c1 = g.colors.first().map(|e| color_str(e.color)).unwrap_or_default();
let grad_c2 = g.colors.get(1).map(|e| color_str(e.color)).unwrap_or_default();
if g.enabled {
// ── Gradient fill ──────────────────────────────────────────────────
let grad_type = if g.is_single_color { "One color" } else { "Two color" };
let centered = if g.shift.abs() < 1e-9 { "Yes" } else { "No" };
return vec![
PropSection {
title: "Pattern".into(),
props: vec![
ro("Type", "fill_type", grad_type),
ro("Gradient name", "gradient_name", g.name.clone()),
ro("Color 1", "gradient_color_1", grad_c1),
ro("Color 2", "gradient_color_2", grad_c2),
edit("Angle", "pattern_angle", g.angle.to_degrees()),
ro("Centered", "gradient_centered", centered),
],
},
ro("Pattern Type", "pattern_type", pattern_type),
edit(
"Pattern Angle",
"pattern_angle",
h.pattern_angle.to_degrees(),
),
edit("Pattern Scale", "pattern_scale", h.pattern_scale),
ro("Style", "style", style),
ro("Boundary Paths", "path_count", h.paths.len().to_string()),
ro("Boundary Verts", "vert_count", boundary_count.to_string()),
ro("Double", "double", if h.is_double { "Yes" } else { "No" }),
ro(
"Associative",
"associative",
if h.is_associative { "Yes" } else { "No" },
),
edit("Elevation", "elevation", h.elevation),
ro("Seed Points", "seed_count", h.seed_points.len().to_string()),
ro("Pixel Size", "pixel_size", format!("{:.6}", h.pixel_size)),
ro(
"Normal",
"normal",
format!("{:.3}, {:.3}, {:.3}", h.normal.x, h.normal.y, h.normal.z),
),
],
}]
PropSection {
title: "Geometry".into(),
props: vec![
edit("Elevation", "elevation", h.elevation),
ro("Origin X", "origin_x", String::new()),
ro("Origin Y", "origin_y", String::new()),
ro("Area", "area", format!("{:.4}", area)),
ro("Cumulative area", "cumulative_area", format!("{:.4}", area)),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro(
"Associative",
"associative",
if h.is_associative { "Yes" } else { "No" },
),
ro("Annotative", "annotative", String::new()),
ro("Island detection style", "style", style),
ro("Background color", "background_color", String::new()),
],
},
];
}
// ── Hatch (pattern / solid) ────────────────────────────────────────────
let hatch_type = if h.is_solid { "Solid" } else { "Pattern" };
vec![
PropSection {
title: "Pattern".into(),
props: vec![
ro("Type", "pattern_type_label", hatch_type),
Property {
label: "Pattern name".into(),
field: "pattern_name",
value: PropValue::HatchPatternChoice(h.pattern.name.clone()),
},
ro("Color", "color", color_str(h.common.color)),
ro("Background color", "background_color", String::new()),
ro(
"Transparency",
"transparency",
format!("{}%", (h.common.transparency.as_percent() * 100.0).round() as i32),
),
edit("Angle", "pattern_angle", h.pattern_angle.to_degrees()),
edit("Scale", "pattern_scale", h.pattern_scale),
ro("Origin X", "origin_x", String::new()),
ro("Origin Y", "origin_y", String::new()),
ro("Annotative", "annotative", String::new()),
ro(
"Associative",
"associative",
if h.is_associative { "Yes" } else { "No" },
),
ro("Layer override", "layer_override", String::new()),
ro("Double", "double", if h.is_double { "Yes" } else { "No" }),
ro("Spacing", "spacing", String::new()),
ro("ISO pen width", "iso_pen_width", String::new()),
ro("Gradient colors", "gradient_colors", g.colors.len().to_string()),
ro("Gradient tint", "gradient_tint", format!("{:.4}", g.color_tint)),
ro("Gradient color 1", "gradient_color_1", grad_c1),
ro("Gradient color 2", "gradient_color_2", grad_c2),
ro("Pattern type", "pattern_type", pattern_type),
],
},
PropSection {
title: "Geometry".into(),
props: vec![
edit("Elevation", "elevation", h.elevation),
ro("Area", "area", format!("{:.4}", area)),
ro("Cumulative area", "cumulative_area", format!("{:.4}", area)),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro("Boundary retention", "boundary_retention", String::new()),
ro("Island detection style", "style", style),
],
},
]
}
fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
@@ -81,6 +182,7 @@ fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
return;
};
match field {
"pattern_angle" if h.gradient_color.enabled => h.gradient_color.angle = v.to_radians(),
"pattern_angle" => h.pattern_angle = v.to_radians(),
"pattern_scale" if v > 0.0 => h.pattern_scale = v,
"elevation" => h.elevation = v,
+53 -14
View File
@@ -6,7 +6,7 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
use crate::scene::model::object::{GripApply, GripDef, PropSection};
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::model::wire_model::WireModel;
use crate::scene::cache::block_cache;
use crate::scene::convert::tessellate;
@@ -20,19 +20,58 @@ fn grips(ins: &Insert) -> Vec<GripDef> {
}
fn properties(ins: &Insert) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("Insert X", "ins_x", ins.insert_point.x),
edit("Insert Y", "ins_y", ins.insert_point.y),
edit("Insert Z", "ins_z", ins.insert_point.z),
edit("Scale X", "x_scale", ins.x_scale()),
edit("Scale Y", "y_scale", ins.y_scale()),
edit("Scale Z", "z_scale", ins.z_scale()),
edit("Rotation", "rotation", ins.rotation.to_degrees()),
ro("Block", "block", ins.block_name.clone()),
],
}]
let annotative = ins
.common
.extended_data
.get_record("AcAnnotativeData")
.is_some();
let attrs: Vec<Property> = ins
.attributes
.iter()
.map(|a| Property {
label: a.tag.clone(),
field: "attr",
value: PropValue::AttrText {
tag: a.tag.clone(),
value: a.value.clone(),
},
})
.collect();
vec![
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "ins_x", ins.insert_point.x),
edit("Position Y", "ins_y", ins.insert_point.y),
edit("Position Z", "ins_z", ins.insert_point.z),
edit("Scale X", "x_scale", ins.x_scale()),
edit("Scale Y", "y_scale", ins.y_scale()),
edit("Scale Z", "z_scale", ins.z_scale()),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro("Name", "block", ins.block_name.clone()),
edit("Rotation", "rotation", ins.rotation.to_degrees()),
ro("Block Unit", "block_unit", String::new()),
ro("Unit factor", "unit_factor", String::new()),
ro(
"Annotative",
"annotative",
if annotative { "Yes" } else { "No" },
),
],
},
PropSection {
title: "Attributes".into(),
props: attrs,
},
PropSection {
title: "Custom".into(),
props: Vec::new(),
},
]
}
fn apply_geom_prop(ins: &mut Insert, field: &str, value: &str) {
+74 -106
View File
@@ -191,102 +191,94 @@ fn hookline_dir_str(hd: &HooklineDirection) -> &'static str {
}
}
fn color_str(c: &acadrust::types::Color) -> String {
match c.rgb() {
Some((r, g, b)) => format!("RGB({},{},{})", r, g, b),
None => format!("{:?}", c),
}
}
fn properties(leader: &Leader) -> Vec<PropSection> {
let n = leader.vertices.len();
let mut props = vec![
// Style
Property {
label: "Dim Style".into(),
field: "dimension_style",
value: PropValue::EditText(leader.dimension_style.clone()),
},
// Path type
let arrow_size = leader.text_height.max(1.0);
let misc = vec![
choice_prop(
"Path Type",
"Type",
"path_type",
path_type_str(&leader.path_type),
&["Straight", "Spline"],
),
// Creation type
choice_prop(
"Creation Type",
"Annotation type",
"creation_type",
creation_type_str(&leader.creation_type),
&["With Text", "With Tolerance", "With Block", "No Annotation"],
),
// Arrow
bool_toggle("Arrow", "arrow_enabled", leader.arrow_enabled),
// Hookline
bool_toggle("Hookline", "hookline_enabled", leader.hookline_enabled),
choice_prop(
"Hookline Dir",
"hookline_direction",
hookline_dir_str(&leader.hookline_direction),
&["Opposite", "Same"],
),
// Text dims
edit("Text Height", "text_height", leader.text_height),
edit("Text Width", "text_width", leader.text_width),
// Normal
edit("Normal X", "normal_x", leader.normal.x),
edit("Normal Y", "normal_y", leader.normal.y),
edit("Normal Z", "normal_z", leader.normal.z),
// Horizontal direction
edit("H Dir X", "h_dir_x", leader.horizontal_direction.x),
edit("H Dir Y", "h_dir_y", leader.horizontal_direction.y),
edit("H Dir Z", "h_dir_z", leader.horizontal_direction.z),
// Block offset
edit("Block Offset X", "block_offset_x", leader.block_offset.x),
edit("Block Offset Y", "block_offset_y", leader.block_offset.y),
edit("Block Offset Z", "block_offset_z", leader.block_offset.z),
// Annotation offset
edit("Ann Offset X", "ann_offset_x", leader.annotation_offset.x),
edit("Ann Offset Y", "ann_offset_y", leader.annotation_offset.y),
edit("Ann Offset Z", "ann_offset_z", leader.annotation_offset.z),
// Stats
ro("Vertices", "vertex_count", n.to_string()),
ro("Length", "length", format!("{:.4}", leader.length())),
// Annotation reference + dim-style colour override (read-only —
// they are written by the file and survive a round-trip).
ro(
"Annotation",
"annotation_handle",
if leader.annotation_handle.is_null() {
"(none)".to_string()
} else {
format!("{:X}", leader.annotation_handle.value())
},
),
ro(
"Override Color",
"override_color",
match leader.override_color.rgb() {
Some((r, g, b)) => format!("RGB({},{},{})", r, g, b),
None => format!("{:?}", leader.override_color),
},
),
bool_toggle("Arrowhead", "arrow_enabled", leader.arrow_enabled),
Property {
label: "Dim style".into(),
field: "dimension_style",
value: PropValue::EditText(leader.dimension_style.clone()),
},
];
// Arrow point (vertex[0])
let lines_arrows = vec![
ro("Dim line color", "override_color", color_str(&leader.override_color)),
ro(
"Dim line lineweight",
"line_weight",
format!("{:?}", leader.common.line_weight),
),
ro("Dim line linetype", "linetype", leader.common.linetype.clone()),
bool_toggle("Arrowhead", "arrow_enabled", leader.arrow_enabled),
ro("Arrow size", "arrow_size", format!("{:.4}", arrow_size)),
];
let text = vec![
edit("Text height", "text_height", leader.text_height),
ro("Text offset", "text_offset", String::new()),
ro("Text style", "text_style", String::new()),
ro("Text color", "text_color", color_str(&leader.override_color)),
ro("Text position vert", "text_pos_vert", String::new()),
];
let fit = vec![ro("Dim scale overall", "dim_scale_overall", String::new())];
let mut geometry = vec![ro("Vertex", "vertex_count", n.to_string())];
if let Some(a) = leader.arrow_point() {
props.push(edit("Arrow X", "arrow_x", a.x));
props.push(edit("Arrow Y", "arrow_y", a.y));
props.push(edit("Arrow Z", "arrow_z", a.z));
geometry.push(edit("Vertex X", "vertex_x", a.x));
geometry.push(edit("Vertex Y", "vertex_y", a.y));
geometry.push(edit("Vertex Z", "vertex_z", a.z));
} else {
geometry.push(ro("Vertex X", "vertex_x", String::new()));
geometry.push(ro("Vertex Y", "vertex_y", String::new()));
geometry.push(ro("Vertex Z", "vertex_z", String::new()));
}
// End point (last vertex)
if n >= 2 {
if let Some(e) = leader.end_point() {
props.push(edit("End X", "end_x", e.x));
props.push(edit("End Y", "end_y", e.y));
props.push(edit("End Z", "end_z", e.z));
}
}
vec![PropSection {
title: "Geometry".into(),
props,
}]
vec![
PropSection {
title: "Misc".into(),
props: misc,
},
PropSection {
title: "Lines & Arrows".into(),
props: lines_arrows,
},
PropSection {
title: "Text".into(),
props: text,
},
PropSection {
title: "Fit".into(),
props: fit,
},
PropSection {
title: "Geometry".into(),
props: geometry,
},
]
}
fn apply_geom_prop(leader: &mut Leader, field: &str, value: &str) {
@@ -398,45 +390,21 @@ fn apply_geom_prop(leader: &mut Leader, field: &str, value: &str) {
leader.annotation_offset.z = v;
}
}
"arrow_x" => {
"vertex_x" => {
if let (Some(v), Some(vert)) = (f64(value), leader.vertices.get_mut(0)) {
vert.x = v;
}
}
"arrow_y" => {
"vertex_y" => {
if let (Some(v), Some(vert)) = (f64(value), leader.vertices.get_mut(0)) {
vert.y = v;
}
}
"arrow_z" => {
"vertex_z" => {
if let (Some(v), Some(vert)) = (f64(value), leader.vertices.get_mut(0)) {
vert.z = v;
}
}
"end_x" => {
let last = leader.vertices.len().saturating_sub(1);
if last > 0 {
if let (Some(v), Some(vert)) = (f64(value), leader.vertices.get_mut(last)) {
vert.x = v;
}
}
}
"end_y" => {
let last = leader.vertices.len().saturating_sub(1);
if last > 0 {
if let (Some(v), Some(vert)) = (f64(value), leader.vertices.get_mut(last)) {
vert.y = v;
}
}
}
"end_z" => {
let last = leader.vertices.len().saturating_sub(1);
if last > 0 {
if let (Some(v), Some(vert)) = (f64(value), leader.vertices.get_mut(last)) {
vert.z = v;
}
}
}
_ => {}
}
}
+8
View File
@@ -73,6 +73,10 @@ fn grips(line: &Line) -> Vec<GripDef> {
}
fn properties(line: &Line) -> Vec<PropSection> {
let dx = line.end.x - line.start.x;
let dy = line.end.y - line.start.y;
let dz = line.end.z - line.start.z;
let angle = dy.atan2(dx).to_degrees().rem_euclid(360.0);
vec![PropSection {
title: "Geometry".into(),
props: vec![
@@ -82,7 +86,11 @@ fn properties(line: &Line) -> Vec<PropSection> {
edit("End X", "end_x", line.end.x),
edit("End Y", "end_y", line.end.y),
edit("End Z", "end_z", line.end.z),
ro("Delta X", "delta_x", format!("{dx:.4}")),
ro("Delta Y", "delta_y", format!("{dy:.4}")),
ro("Delta Z", "delta_z", format!("{dz:.4}")),
ro("Length", "length", format!("{:.4}", line.length())),
ro("Angle", "angle", format!("{angle:.2}")),
],
}]
}
+58 -17
View File
@@ -323,21 +323,40 @@ fn grips(pline: &LwPolyline) -> Vec<GripDef> {
}
fn properties(pline: &LwPolyline) -> Vec<PropSection> {
let (length, area) = length_and_area(pline);
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Vertices", "vertices", pline.vertices.len().to_string()),
ro(
"Closed",
"closed",
if pline.is_closed { "Yes" } else { "No" },
),
edit("Elevation", "elevation", pline.elevation),
ro("Length", "length", format!("{length:.4}")),
ro("Area", "area", format!("{area:.4}")),
],
}]
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);
vec![
PropSection {
title: "Geometry".into(),
props: vec![
ro("Current Vertex", "current_vertex", String::new()),
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),
],
},
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" },
),
],
},
]
}
/// Path length and enclosed area of a polyline, accounting for arc
@@ -382,8 +401,30 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
let Some(v) = parse_f64(value) else {
return;
};
if field == "elevation" {
pline.elevation = v;
match field {
"elevation" => pline.elevation = v,
"global_width" => pline.constant_width = v,
"vertex_x" => {
if let Some(vtx) = pline.vertices.first_mut() {
vtx.location.x = v;
}
}
"vertex_y" => {
if let Some(vtx) = pline.vertices.first_mut() {
vtx.location.y = v;
}
}
"start_width" => {
if let Some(vtx) = pline.vertices.first_mut() {
vtx.start_width = v;
}
}
"end_width" => {
if let Some(vtx) = pline.vertices.first_mut() {
vtx.end_width = v;
}
}
_ => {}
}
}
+119 -86
View File
@@ -117,21 +117,28 @@ impl Grippable for Face3D {
impl PropertyEditable for Face3D {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
use crate::entities::common::edit_prop as edit;
let inv = self.invisible_edges;
let edge = |hidden: bool| if hidden { "Invisible" } else { "Visible" };
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("P1 X", "f3_p1x", self.first_corner.x),
edit("P1 Y", "f3_p1y", self.first_corner.y),
edit("P1 Z", "f3_p1z", self.first_corner.z),
edit("P2 X", "f3_p2x", self.second_corner.x),
edit("P2 Y", "f3_p2y", self.second_corner.y),
edit("P2 Z", "f3_p2z", self.second_corner.z),
edit("P3 X", "f3_p3x", self.third_corner.x),
edit("P3 Y", "f3_p3y", self.third_corner.y),
edit("P3 Z", "f3_p3z", self.third_corner.z),
edit("P4 X", "f3_p4x", self.fourth_corner.x),
edit("P4 Y", "f3_p4y", self.fourth_corner.y),
edit("P4 Z", "f3_p4z", self.fourth_corner.z),
ro("Current vertex", "f3_current", String::new()),
edit("Vertex 1 X", "f3_p1x", self.first_corner.x),
edit("Vertex 1 Y", "f3_p1y", self.first_corner.y),
edit("Vertex 1 Z", "f3_p1z", self.first_corner.z),
edit("Vertex 2 X", "f3_p2x", self.second_corner.x),
edit("Vertex 2 Y", "f3_p2y", self.second_corner.y),
edit("Vertex 2 Z", "f3_p2z", self.second_corner.z),
edit("Vertex 3 X", "f3_p3x", self.third_corner.x),
edit("Vertex 3 Y", "f3_p3y", self.third_corner.y),
edit("Vertex 3 Z", "f3_p3z", self.third_corner.z),
edit("Vertex 4 X", "f3_p4x", self.fourth_corner.x),
edit("Vertex 4 Y", "f3_p4y", self.fourth_corner.y),
edit("Vertex 4 Z", "f3_p4z", self.fourth_corner.z),
ro("Edge 1", "f3_edge1", edge(inv.is_first_invisible())),
ro("Edge 2", "f3_edge2", edge(inv.is_second_invisible())),
ro("Edge 3", "f3_edge3", edge(inv.is_third_invisible())),
ro("Edge 4", "f3_edge4", edge(inv.is_fourth_invisible())),
],
}]
}
@@ -283,38 +290,49 @@ impl PropertyEditable for PolygonMesh {
acadrust::entities::polygon_mesh::SurfaceSmoothType::Cubic => "Cubic",
acadrust::entities::polygon_mesh::SurfaceSmoothType::Bezier => "Bezier",
};
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("M count", "pm_m", self.m_vertex_count.to_string()),
ro("N count", "pm_n", self.n_vertex_count.to_string()),
ro("Vertices", "pm_v", self.vertices.len().to_string()),
ro("Smooth Type", "pm_smooth", smooth),
ro(
"Smooth M Density",
"pm_smooth_m",
self.m_smooth_density.to_string(),
),
ro(
"Smooth N Density",
"pm_smooth_n",
self.n_smooth_density.to_string(),
),
ro(
"Elevation",
"pm_elevation",
format!("{:.4}", self.elevation),
),
ro(
"Normal",
"pm_normal",
format!(
"{:.3}, {:.3}, {:.3}",
self.normal.x, self.normal.y, self.normal.z
let yesno = |b: bool| if b { "Yes" } else { "No" };
let first = self.vertices.first();
// Grid faces: one quad per cell; closed direction adds a wrap row/column.
let m = self.m_vertex_count.max(0) as i64;
let n = self.n_vertex_count.max(0) as i64;
let cells_m = if self.is_closed_m() { m } else { (m - 1).max(0) };
let cells_n = if self.is_closed_n() { n } else { (n - 1).max(0) };
let face_count = cells_m * cells_n;
vec![
PropSection {
title: "Geometry".into(),
props: vec![
ro("Vertex", "pm_vertex", String::new()),
ro(
"Vertex X",
"pm_vx",
first.map(|v| format!("{:.4}", v.location.x)).unwrap_or_default(),
),
),
],
}]
ro(
"Vertex Y",
"pm_vy",
first.map(|v| format!("{:.4}", v.location.y)).unwrap_or_default(),
),
ro(
"Vertex Z",
"pm_vz",
first.map(|v| format!("{:.4}", v.location.z)).unwrap_or_default(),
),
ro("M vertex count", "pm_m", self.m_vertex_count.to_string()),
ro("N vertex count", "pm_n", self.n_vertex_count.to_string()),
ro("M closed", "pm_closed_m", yesno(self.is_closed_m())),
ro("N closed", "pm_closed_n", yesno(self.is_closed_n())),
ro("M density", "pm_smooth_m", self.m_smooth_density.to_string()),
ro("N density", "pm_smooth_n", self.n_smooth_density.to_string()),
ro("Vertex count", "pm_v", self.vertices.len().to_string()),
ro("Face count", "pm_faces", face_count.to_string()),
],
},
PropSection {
title: "Misc".into(),
props: vec![ro("Fit/smooth", "pm_smooth", smooth)],
},
]
}
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
@@ -431,40 +449,44 @@ impl PropertyEditable for PolyfaceMesh {
acadrust::entities::PolyfaceSmoothType::Cubic => "Cubic",
acadrust::entities::PolyfaceSmoothType::Bezier => "Bezier",
};
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Vertices", "pfm_v", self.vertices.len().to_string()),
ro("Faces", "pfm_f", self.faces.len().to_string()),
ro("Smooth Surface", "pfm_smooth", smooth),
ro(
"Seqend",
"pfm_seqend_handle",
match self.seqend_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
},
),
ro(
"Elevation",
"pfm_elevation",
format!("{:.4}", self.elevation),
),
ro(
"Normal",
"pfm_normal",
format!(
"{:.3}, {:.3}, {:.3}",
self.normal.x, self.normal.y, self.normal.z
let first = self.vertices.first();
vec![
PropSection {
title: "Geometry".into(),
props: vec![
ro("Vertex", "pfm_vertex", String::new()),
ro(
"Vertex X",
"pfm_vx",
first.map(|v| format!("{:.4}", v.location.x)).unwrap_or_default(),
),
),
ro(
"Thickness",
"pfm_thickness",
format!("{:.4}", self.thickness),
),
],
}]
ro(
"Vertex Y",
"pfm_vy",
first.map(|v| format!("{:.4}", v.location.y)).unwrap_or_default(),
),
ro(
"Vertex Z",
"pfm_vz",
first.map(|v| format!("{:.4}", v.location.z)).unwrap_or_default(),
),
// Polyface meshes store an explicit vertex/face list rather
// than an M×N grid, so the grid-only rows are not applicable.
ro("M vertex count", "pfm_m", String::new()),
ro("N vertex count", "pfm_n", String::new()),
ro("M closed", "pfm_closed_m", String::new()),
ro("N closed", "pfm_closed_n", String::new()),
ro("M density", "pfm_density_m", String::new()),
ro("N density", "pfm_density_n", String::new()),
ro("Vertex count", "pfm_v", self.vertices.len().to_string()),
ro("Face count", "pfm_f", self.faces.len().to_string()),
],
},
PropSection {
title: "Misc".into(),
props: vec![ro("Fit/smooth", "pfm_smooth", smooth)],
},
]
}
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
@@ -598,24 +620,35 @@ impl Grippable for Mesh {
impl PropertyEditable for Mesh {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
let creased_edges = self.edges.iter().filter(|e| e.has_crease()).count();
// Watertight when every face edge is shared by exactly two faces
// (closed manifold). Empty meshes are not watertight.
let mut edge_use: std::collections::HashMap<(usize, usize), u32> =
std::collections::HashMap::new();
for face in &self.faces {
let vs = &face.vertices;
for i in 0..vs.len() {
let a = vs[i];
let b = vs[(i + 1) % vs.len()];
let key = if a < b { (a, b) } else { (b, a) };
*edge_use.entry(key).or_insert(0) += 1;
}
}
let watertight =
!self.faces.is_empty() && edge_use.values().all(|&c| c == 2);
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Vertices", "msh_v", self.vertices.len().to_string()),
ro("Faces", "msh_f", self.faces.len().to_string()),
ro("Edges", "msh_e", self.edges.len().to_string()),
ro("Creased Edges", "msh_creased", creased_edges.to_string()),
ro("Version", "msh_version", self.version.to_string()),
ro(
"Subdivision Level",
"Level of Smoothness",
"msh_subdiv",
self.subdivision_level.to_string(),
),
ro("Number of Faces", "msh_f", self.faces.len().to_string()),
ro("Number of Grips", "msh_grips", self.vertices.len().to_string()),
ro(
"Blend Crease",
"msh_blend_crease",
if self.blend_crease { "Yes" } else { "No" },
"Watertight",
"msh_watertight",
if watertight { "Yes" } else { "No" },
),
],
}]
+50 -41
View File
@@ -194,46 +194,39 @@ impl PropertyEditable for MLine {
acadrust::entities::MLineJustification::Zero => "Zero",
acadrust::entities::MLineJustification::Bottom => "Bottom",
};
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Style", "ml_style", self.style_name.clone()),
ro(
"Style Handle",
"ml_style_handle",
match self.style_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
let cur = self.vertices.first();
let cur_x = cur.map(|v| v.position.x).unwrap_or(0.0);
let cur_y = cur.map(|v| v.position.y).unwrap_or(0.0);
let cur_z = cur.map(|v| v.position.z).unwrap_or(0.0);
vec![
PropSection {
title: "Geometry".into(),
props: vec![
ro("Vertex", "ml_vertex", if self.vertices.is_empty() { String::new() } else { "1".to_string() }),
edit("Vertex X", "ml_vertex_x", cur_x),
edit("Vertex Y", "ml_vertex_y", cur_y),
edit("Vertex Z", "ml_vertex_z", cur_z),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro("Style", "ml_style", self.style_name.clone()),
Property {
label: "Style justification".into(),
field: "ml_justification",
value: PropValue::Choice {
selected: just_str.to_string(),
options: ["Top", "Zero", "Bottom"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
),
ro("Vertices", "ml_verts", self.vertices.len().to_string()),
ro(
"Style Elements",
"ml_style_elem_count",
self.style_element_count.to_string(),
),
edit("Scale", "ml_scale", self.scale_factor),
Property {
label: "Justification".into(),
field: "ml_justification",
value: PropValue::Choice {
selected: just_str.to_string(),
options: ["Top", "Zero", "Bottom"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
Property {
label: "Closed".into(),
field: "ml_closed",
value: PropValue::BoolToggle {
field: "ml_closed",
value: self.flags.contains(acadrust::entities::MLineFlags::CLOSED),
},
},
],
}]
edit("Style scale", "ml_scale", self.scale_factor),
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
@@ -261,8 +254,24 @@ impl PropertyEditable for MLine {
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
if field == "ml_scale" && v != 0.0 {
self.scale_factor = v;
match field {
"ml_scale" if v != 0.0 => self.scale_factor = v,
"ml_vertex_x" => {
if let Some(vx) = self.vertices.first_mut() {
vx.position.x = v;
}
}
"ml_vertex_y" => {
if let Some(vx) = self.vertices.first_mut() {
vx.position.y = v;
}
}
"ml_vertex_z" => {
if let Some(vx) = self.vertices.first_mut() {
vx.position.z = v;
}
}
_ => {}
}
}
}
+105 -59
View File
@@ -192,69 +192,115 @@ fn grips(t: &MText) -> Vec<GripDef> {
vec![square_grip(0, p), triangle_grip(1, width_grip)]
}
fn columns_str(c: &acadrust::entities::MTextColumnData) -> &'static str {
match c.column_type {
1 => "Static",
2 => "Dynamic",
_ => "None",
}
}
fn properties(t: &MText, text_style_names: &[String]) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("Insert X", "ins_x", t.insertion_point.x),
edit("Insert Y", "ins_y", t.insertion_point.y),
edit("Insert Z", "ins_z", t.insertion_point.z),
edit("Height", "height", t.height),
edit("Width", "rect_w", t.rectangle_width),
edit("Rect Height", "rect_h", t.rectangle_height.unwrap_or(0.0)),
edit("Rotation", "rotation", t.rotation.to_degrees()),
edit("Line Spacing", "line_spacing", t.line_spacing_factor),
Property {
label: "H-Align".into(),
field: "h_align",
value: PropValue::Choice {
selected: mtext_halign_str(&t.attachment_point).to_string(),
options: ["Left", "Center", "Right"]
.into_iter()
.map(str::to_string)
.collect(),
// Absolute line-space distance for single spacing (~1.66 * height) scaled
// by the line-spacing factor.
let line_space_distance = t.height * 1.666_666_666_666_667 * t.line_spacing_factor;
let bg_mask_on = (t.background_fill_flags & 0x03) != 0;
let text_frame_on = (t.background_fill_flags & 0x10) != 0;
vec![
PropSection {
title: "Text".into(),
props: vec![
Property {
label: "Contents".into(),
field: "content",
value: PropValue::EditText(t.value.clone()),
},
},
Property {
label: "V-Align".into(),
field: "v_align",
value: PropValue::Choice {
selected: mtext_valign_str(&t.attachment_point).to_string(),
options: ["Top", "Middle", "Bottom"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
ro(
"Attachment",
"attachment",
attachment_str(&t.attachment_point).to_string(),
),
ro(
"Direction",
"direction",
drawing_dir_str(&t.drawing_direction).to_string(),
),
Property {
label: "Content".into(),
field: "content",
value: PropValue::EditText(t.value.clone()),
},
Property {
label: "Style".into(),
field: "style",
value: PropValue::Choice {
selected: if t.style.trim().is_empty() {
"Standard".into()
} else {
t.style.clone()
Property {
label: "Style".into(),
field: "style",
value: PropValue::Choice {
selected: if t.style.trim().is_empty() {
"Standard".into()
} else {
t.style.clone()
},
options: text_style_names.to_vec(),
},
options: text_style_names.to_vec(),
},
},
],
}]
ro(
"Annotative",
"annotative",
if t.is_annotative { "Yes" } else { "No" },
),
Property {
label: "Justify".into(),
field: "h_align",
value: PropValue::Choice {
selected: mtext_halign_str(&t.attachment_point).to_string(),
options: ["Left", "Center", "Right"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
Property {
label: "V-Align".into(),
field: "v_align",
value: PropValue::Choice {
selected: mtext_valign_str(&t.attachment_point).to_string(),
options: ["Top", "Middle", "Bottom"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
ro(
"Attachment",
"attachment",
attachment_str(&t.attachment_point).to_string(),
),
ro(
"Direction",
"direction",
drawing_dir_str(&t.drawing_direction).to_string(),
),
edit("Text height", "height", t.height),
edit("Rotation", "rotation", t.rotation.to_degrees()),
edit("Line space factor", "line_spacing", t.line_spacing_factor),
ro(
"Line space distance",
"line_space_distance",
format!("{line_space_distance:.4}"),
),
ro("Line space style", "line_space_style", String::new()),
ro(
"Background mask",
"background_mask",
if bg_mask_on { "On" } else { "Off" },
),
edit("Defined width", "rect_w", t.rectangle_width),
edit("Defined height", "rect_h", t.rectangle_height.unwrap_or(0.0)),
ro(
"Text frame",
"text_frame",
if text_frame_on { "On" } else { "Off" },
),
ro(
"Columns",
"columns",
columns_str(&t.column_data).to_string(),
),
],
},
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "ins_x", t.insertion_point.x),
edit("Position Y", "ins_y", t.insertion_point.y),
edit("Position Z", "ins_z", t.insertion_point.z),
],
},
]
}
fn apply_geom_prop(t: &mut MText, field: &str, value: &str) {
+189 -192
View File
@@ -520,204 +520,186 @@ fn choice(label: &str, field: &'static str, selected: &str, opts: &[&str]) -> Pr
}
}
fn hexh(h: Option<acadrust::Handle>) -> String {
match h {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
}
}
fn properties(ml: &MultiLeader) -> Vec<PropSection> {
let ctx = &ml.context;
let total_pts: usize = ctx
.leader_roots
.iter()
.flat_map(|r| r.lines.iter())
.map(|l| l.points.len())
.sum();
let first_root = ctx.leader_roots.first();
let mut props = vec![
// Content
choice(
"Content Type",
"content_type",
content_type_str(&ml.content_type),
&["None", "MText", "Block", "Tolerance"],
),
Property {
label: "Text".into(),
field: "text_string",
value: PropValue::EditText(ctx.text_string.clone()),
},
edit("Text Height", "text_height", ml.text_height),
edit("Text X", "text_x", ctx.text_location.x),
edit("Text Y", "text_y", ctx.text_location.y),
edit("Text Z", "text_z", ctx.text_location.z),
bool_toggle("Text Frame", "text_frame", ml.text_frame),
// Leader line
choice(
"Path Type",
"path_type",
path_type_str(&ml.path_type),
&["Straight", "Spline", "Invisible"],
),
bool_toggle("Landing", "enable_landing", ml.enable_landing),
bool_toggle("Dogleg", "enable_dogleg", ml.enable_dogleg),
edit("Dogleg Length", "dogleg_length", ml.dogleg_length),
edit("Arrow Size", "arrowhead_size", ml.arrowhead_size),
edit("Scale", "scale_factor", ml.scale_factor),
bool_toggle(
"Annotation Scale",
"enable_annotation_scale",
ml.enable_annotation_scale,
),
// Text attachment
choice(
"Left Attach",
"text_left_attachment",
attachment_str(&ml.text_left_attachment),
&[
"Top of Top",
"Mid of Top",
"Mid of Text",
"Mid of Bot",
"Bot of Bot",
"Bottom Line",
],
),
choice(
"Right Attach",
"text_right_attachment",
attachment_str(&ml.text_right_attachment),
&[
"Top of Top",
"Mid of Top",
"Mid of Text",
"Mid of Bot",
"Bot of Bot",
"Bottom Line",
],
),
// Top / Bottom attach (used when text_attachment_direction = Vertical)
choice(
"Top Attach",
"text_top_attachment",
attachment_str(&ml.text_top_attachment),
&[
"Top of Top",
"Mid of Top",
"Mid of Text",
"Mid of Bot",
"Bot of Bot",
"Bottom Line",
],
),
choice(
"Bottom Attach",
"text_bottom_attachment",
attachment_str(&ml.text_bottom_attachment),
&[
"Top of Top",
"Mid of Top",
"Mid of Text",
"Mid of Bot",
"Bot of Bot",
"Bottom Line",
],
),
// Stats
ro("Leader Pts", "total_pts", total_pts.to_string()),
ro("Roots", "root_count", ctx.leader_roots.len().to_string()),
// Style references / handles (read-only — the multileader's own
// copies of the style values are the authoritative render inputs).
ro(
"Style Handle",
"style_handle",
match ml.style_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
},
),
ro(
"Text Style Handle",
"text_style_handle",
match ml.text_style_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
},
),
ro(
"Arrow Handle",
"arrowhead_handle",
match ml.arrowhead_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
},
),
ro(
"Line Type Handle",
"line_type_handle",
match ml.line_type_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
},
),
ro(
"Block Content Handle",
"block_content_handle",
match ml.block_content_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
},
),
// Less common toggles surfaced read-only.
ro(
"Extend Leader",
"extend_leader_to_text",
if ml.extend_leader_to_text {
"Yes"
} else {
"No"
},
),
ro(
"Text Direction Negative",
"text_direction_negative",
if ml.text_direction_negative {
"Yes"
} else {
"No"
},
),
ro(
"Text Align In IPE",
"text_align_in_ipe",
ml.text_align_in_ipe.to_string(),
),
ro(
"Property Override Flags",
"property_override_flags",
format!("{:#018b}", ml.property_override_flags.bits()),
),
ro(
"Block Scale",
"block_scale",
format!(
"{:.3} × {:.3} × {:.3}",
ml.block_scale.x, ml.block_scale.y, ml.block_scale.z
// ── Misc ─────────────────────────────────────────────────────────────
let misc = PropSection {
title: "Misc".into(),
props: vec![
ro("Leader type", "path_type", path_type_str(&ml.path_type)),
choice(
"Content type",
"content_type",
content_type_str(&ml.content_type),
&["None", "MText", "Block", "Tolerance"],
),
),
ro(
"Block Rotation",
"block_rotation",
format!("{:.3}°", ml.block_rotation.to_degrees()),
),
];
edit("Overall scale", "scale_factor", ml.scale_factor),
ro(
"Annotative",
"enable_annotation_scale",
if ml.enable_annotation_scale { "Yes" } else { "No" },
),
// No stored annotative-scale name on the entity.
ro("Annotative scale", "annotative_scale", String::new()),
],
};
// Connection point for first root (most common case)
if let Some(root) = ctx.leader_roots.first() {
props.push(edit("Root Conn X", "conn_x", root.connection_point.x));
props.push(edit("Root Conn Y", "conn_y", root.connection_point.y));
props.push(edit("Root Conn Z", "conn_z", root.connection_point.z));
}
// ── Leaders ──────────────────────────────────────────────────────────
let leaders = PropSection {
title: "Leaders".into(),
props: vec![
ro("Leader type", "path_type", path_type_str(&ml.path_type)),
ro("Leader color", "line_color", format!("{:?}", ml.line_color)),
ro(
"Leader linetype",
"line_type_handle",
hexh(ml.line_type_handle),
),
ro(
"Leader linetype scale",
"leader_linetype_scale",
format!("{:.4}", ml.common.linetype_scale),
),
ro(
"Leader lineweight",
"line_weight",
format!("{:?}", ml.line_weight),
),
ro("Arrowhead", "arrowhead_handle", hexh(ml.arrowhead_handle)),
edit("Arrowhead size", "arrowhead_size", ml.arrowhead_size),
],
};
vec![PropSection {
title: "Geometry".into(),
props,
}]
// ── Leader Structure ─────────────────────────────────────────────────
let structure = PropSection {
title: "Leader Structure".into(),
props: vec![
// Maximum leader points / segment-angle constraints are leader-style
// settings not stored on the entity instance.
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" },
),
edit(
"Landing distance",
"landing_distance",
first_root.map(|r| r.landing_distance).unwrap_or(0.0),
),
edit("Dogleg length", "dogleg_length", ml.dogleg_length),
],
};
// ── Text ─────────────────────────────────────────────────────────────
let text = PropSection {
title: "Text".into(),
props: vec![
ro("Text style", "text_style_handle", hexh(ml.text_style_handle)),
ro(
"Text angle",
"text_angle_type",
format!("{:?}", ml.text_angle_type),
),
ro("Text color", "text_color", format!("{:?}", ml.text_color)),
edit("Text height", "text_height", ml.text_height),
ro(
"Justification",
"text_alignment",
format!("{:?}", ml.text_alignment),
),
bool_toggle("Frame text", "text_frame", ml.text_frame),
choice(
"Left attachment type",
"text_left_attachment",
attachment_str(&ml.text_left_attachment),
&[
"Top of Top",
"Mid of Top",
"Mid of Text",
"Mid of Bot",
"Bot of Bot",
"Bottom Line",
],
),
choice(
"Right attachment type",
"text_right_attachment",
attachment_str(&ml.text_right_attachment),
&[
"Top of Top",
"Mid of Top",
"Mid of Text",
"Mid of Bot",
"Bot of Bot",
"Bottom Line",
],
),
ro(
"Text align type",
"text_attachment_direction",
format!("{:?}", ml.text_attachment_direction),
),
edit("Landing gap", "landing_gap", ctx.landing_gap),
ro(
"Background mask",
"background_fill_enabled",
if ctx.background_fill_enabled {
"Yes"
} else {
"No"
},
),
],
};
// ── Block ────────────────────────────────────────────────────────────
let block = PropSection {
title: "Block".into(),
props: vec![
ro(
"Source block",
"block_content_handle",
hexh(ml.block_content_handle),
),
ro(
"Block attachment",
"block_connection_type",
format!("{:?}", ml.block_connection_type),
),
ro(
"Block color",
"block_content_color",
format!("{:?}", ml.block_content_color),
),
ro(
"Block scale",
"block_scale",
format!(
"{:.3} × {:.3} × {:.3}",
ml.block_scale.x, ml.block_scale.y, ml.block_scale.z
),
),
edit(
"Block rotation",
"block_rotation",
ml.block_rotation.to_degrees(),
),
],
};
vec![misc, leaders, structure, text, block]
}
fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) {
@@ -804,6 +786,21 @@ fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) {
ml.scale_factor = v;
}
}
"landing_distance" => {
if let (Some(v), Some(root)) = (f64(value), ml.context.leader_roots.first_mut()) {
root.landing_distance = v;
}
}
"landing_gap" => {
if let Some(v) = f64(value) {
ml.context.landing_gap = v;
}
}
"block_rotation" => {
if let Some(v) = f64(value) {
ml.block_rotation = v.to_radians();
}
}
"conn_x" => {
if let (Some(v), Some(root)) = (f64(value), ml.context.leader_roots.first_mut()) {
root.connection_point.x = v;
+25 -36
View File
@@ -79,42 +79,30 @@ fn properties(ole: &Ole2Frame) -> Vec<PropSection> {
OleObjectType::Embedded => "Embedded",
OleObjectType::Static => "Static",
};
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Type", "ole_type", type_str),
edit("Upper Left X", "ole_ulx", ole.upper_left_corner.x),
edit("Upper Left Y", "ole_uly", ole.upper_left_corner.y),
edit("Lower Right X", "ole_lrx", ole.lower_right_corner.x),
edit("Lower Right Y", "ole_lry", ole.lower_right_corner.y),
ro("Version", "ole_version", ole.version.to_string()),
ro(
"Source App",
"ole_source_app",
if ole.source_application.is_empty() {
"(unknown)".to_string()
} else {
ole.source_application.clone()
},
),
ro(
"Data Size",
"ole_data_size",
format!("{} bytes", ole.binary_data.len()),
),
ro("DWG Mode", "ole_dwg_mode", ole.dwg_mode.to_string()),
ro(
"DWG Trailing Byte",
"ole_dwg_trailing",
format!("{:#04x}", ole.dwg_trailing_byte),
),
ro(
"Paper Space",
"ole_paper_space",
if ole.is_paper_space { "Yes" } else { "No" },
),
],
}]
let width = (ole.lower_right_corner.x - ole.upper_left_corner.x).abs();
let height = (ole.upper_left_corner.y - ole.lower_right_corner.y).abs();
vec![
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "ole_ulx", ole.upper_left_corner.x),
edit("Position Y", "ole_uly", ole.upper_left_corner.y),
edit("Position Z", "ole_ulz", ole.upper_left_corner.z),
ro("Width", "ole_width", format!("{:.4}", width)),
ro("Height", "ole_height", format!("{:.4}", height)),
ro("Scale width", "ole_scale_width", String::new()),
ro("Scale height", "ole_scale_height", String::new()),
ro("Lock aspect", "ole_lock_aspect", String::new()),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro("Type", "ole_type", type_str),
ro("Plot quality", "ole_plot_quality", String::new()),
],
},
]
}
fn apply_geom_prop(ole: &mut Ole2Frame, field: &str, value: &str) {
@@ -124,6 +112,7 @@ fn apply_geom_prop(ole: &mut Ole2Frame, field: &str, value: &str) {
match field {
"ole_ulx" => ole.upper_left_corner.x = v,
"ole_uly" => ole.upper_left_corner.y = v,
"ole_ulz" => ole.upper_left_corner.z = v,
"ole_lrx" => ole.lower_right_corner.x = v,
"ole_lry" => ole.lower_right_corner.y = v,
_ => {}
+3 -3
View File
@@ -182,9 +182,9 @@ fn properties(pt: &Point) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("X", "loc_x", pt.location.x),
edit("Y", "loc_y", pt.location.y),
edit("Z", "loc_z", pt.location.z),
edit("Position X", "loc_x", pt.location.x),
edit("Position Y", "loc_y", pt.location.y),
edit("Position Z", "loc_z", pt.location.z),
],
}]
}
+83 -81
View File
@@ -2,7 +2,7 @@ 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, ro_prop as ro, square_grip};
use crate::entities::common::{edit_prop as edit, parse_f64, 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, PropValue, Property};
@@ -387,39 +387,58 @@ impl Grippable for Polyline2D {
impl PropertyEditable for Polyline2D {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
let smooth = match self.smooth_surface {
acadrust::entities::SmoothSurfaceType::None => "None",
acadrust::entities::SmoothSurfaceType::QuadraticBSpline => "Quadratic",
acadrust::entities::SmoothSurfaceType::CubicBSpline => "Cubic",
acadrust::entities::SmoothSurfaceType::Bezier => "Bezier",
};
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Vertices", "vertices", self.vertices.len().to_string()),
edit("Elevation", "pl2_elevation", self.elevation),
edit("Default Start W", "pl2_start_w", self.start_width),
edit("Default End W", "pl2_end_w", self.end_width),
edit("Thickness", "pl2_thickness", self.thickness),
ro("Smooth Surface", "pl2_smooth", smooth),
ro(
"Normal",
"pl2_normal",
format!(
"{:.3}, {:.3}, {:.3}",
self.normal.x, self.normal.y, self.normal.z
),
),
Property {
label: "Closed".into(),
field: "pl2_closed",
value: PropValue::BoolToggle {
let n = self.vertices.len();
let mut area = 0.0;
let mut length = 0.0;
let seg_count = if self.is_closed() { n } else { n.saturating_sub(1) };
for i in 0..seg_count {
let a = &self.vertices[i].location;
let b = &self.vertices[(i + 1) % n].location;
area += a.x * b.y - b.x * a.y;
length += ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt();
}
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();
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("Global width", "pl2_start_w", self.start_width),
edit("Elevation", "pl2_elevation", self.elevation),
ro("Area", "pl2_area", format!("{area:.4}")),
ro("Length", "pl2_length", format!("{length:.4}")),
],
},
PropSection {
title: "Misc".into(),
props: vec![
Property {
label: "Closed".into(),
field: "pl2_closed",
value: self.is_closed(),
value: PropValue::BoolToggle {
field: "pl2_closed",
value: self.is_closed(),
},
},
},
],
}]
ro(
"Linetype generation",
"pl2_ltype_gen",
if self.flags.bits() & 128 != 0 { "Enabled" } else { "Disabled" },
),
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
@@ -437,29 +456,18 @@ impl PropertyEditable for Polyline2D {
}
}
"pl2_elevation" => {
if let Ok(v) = value.trim().parse::<f64>() {
if let Some(v) = parse_f64(value) {
self.elevation = v;
}
}
"pl2_start_w" => {
if let Ok(v) = value.trim().parse::<f64>() {
if let Some(v) = parse_f64(value) {
if v >= 0.0 {
self.start_width = v;
}
}
}
"pl2_end_w" => {
if let Ok(v) = value.trim().parse::<f64>() {
if v >= 0.0 {
self.end_width = v;
}
}
}
"pl2_thickness" => {
if let Ok(v) = value.trim().parse::<f64>() {
self.thickness = v;
}
}
_ => {}
}
}
@@ -606,41 +614,43 @@ impl Grippable for Polyline3D {
impl PropertyEditable for Polyline3D {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
use acadrust::entities::polyline3d::SmoothSurfaceType as SST;
let smooth = match self.smooth_type {
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 fit_smooth = match self.smooth_type {
SST::None => "None",
SST::QuadraticBSpline => "Quadratic",
SST::CubicBSpline => "Cubic",
SST::Bezier => "Bezier",
};
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Vertices", "vertices", self.vertices.len().to_string()),
edit("Default Start W", "pl3_start_w", self.default_start_width),
edit("Default End W", "pl3_end_w", self.default_end_width),
ro("Smooth", "pl3_smooth", smooth),
ro("Mesh M", "pl3_mesh_m", self.mesh_m_count.to_string()),
ro("Mesh N", "pl3_mesh_n", self.mesh_n_count.to_string()),
ro(
"Smooth M Density",
"pl3_smooth_m",
self.smooth_m_density.to_string(),
),
ro(
"Smooth N Density",
"pl3_smooth_n",
self.smooth_n_density.to_string(),
),
Property {
label: "Closed".into(),
field: "pl3_closed",
value: PropValue::BoolToggle {
vec![
PropSection {
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),
],
},
PropSection {
title: "Misc".into(),
props: vec![
Property {
label: "Closed".into(),
field: "pl3_closed",
value: self.is_closed(),
value: PropValue::BoolToggle {
field: "pl3_closed",
value: self.is_closed(),
},
},
},
],
}]
ro("Fit/Smooth", "pl3_smooth", fit_smooth),
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
@@ -655,14 +665,6 @@ impl PropertyEditable for Polyline3D {
} else {
self.open();
}
return;
}
if let Ok(v) = value.trim().parse::<f64>() {
match field {
"pl3_start_w" if v >= 0.0 => self.default_start_width = v,
"pl3_end_w" if v >= 0.0 => self.default_end_width = v,
_ => {}
}
}
}
}
+160 -85
View File
@@ -163,57 +163,101 @@ impl Grippable for RasterImage {
impl PropertyEditable for RasterImage {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("File", "ri_file", self.file_path.clone()),
edit("Insert X", "ri_ox", self.insertion_point.x),
edit("Insert Y", "ri_oy", self.insertion_point.y),
edit("Insert Z", "ri_oz", self.insertion_point.z),
edit("Brightness", "ri_bright", self.brightness as f64),
edit("Contrast", "ri_contrast", self.contrast as f64),
edit("Fade", "ri_fade", self.fade as f64),
Property {
label: "Clipping".into(),
field: "ri_clip",
value: PropValue::BoolToggle {
field: "ri_clip",
value: self.clipping_enabled,
let rotation_deg = self.u_vector.y.atan2(self.u_vector.x).to_degrees();
let scale = self.u_vector.length();
let show_image = self.flags.contains(acadrust::entities::ImageDisplayFlags::SHOW_IMAGE);
let show_clipped = self
.flags
.contains(acadrust::entities::ImageDisplayFlags::USE_CLIPPING_BOUNDARY);
let clip_inverted = self.clip_boundary.clip_mode == acadrust::entities::ClipMode::Inside;
let transparency = format!("{:.0}%", self.common.transparency.as_percent() * 100.0);
vec![
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "ri_ox", self.insertion_point.x),
edit("Position Y", "ri_oy", self.insertion_point.y),
edit("Position Z", "ri_oz", self.insertion_point.z),
ro("Rotation", "ri_rotation", format!("{:.4}", rotation_deg)),
ro("Width", "ri_width", format!("{:.4}", self.width())),
ro("Height", "ri_height", format!("{:.4}", self.height())),
ro("Scale", "ri_scale", format!("{:.4}", scale)),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro("Name", "ri_name", self.file_name().to_string()),
edit("Brightness", "ri_bright", self.brightness as f64),
edit("Contrast", "ri_contrast", self.contrast as f64),
edit("Fade", "ri_fade", self.fade as f64),
ro("Transparency", "ri_transparency", transparency),
Property {
label: "Show image".into(),
field: "ri_show_image",
value: PropValue::BoolToggle {
field: "ri_show_image",
value: show_image,
},
},
},
ro(
"Class Version",
"ri_class_version",
self.class_version.to_string(),
),
ro(
"Definition",
"ri_def_handle",
match self.definition_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
Property {
label: "Show clipped".into(),
field: "ri_show_clipped",
value: PropValue::BoolToggle {
field: "ri_show_clipped",
value: show_clipped,
},
},
),
ro(
"Def Reactor",
"ri_def_reactor_handle",
match self.definition_reactor_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
Property {
label: "Clip inverted".into(),
field: "ri_clip_inverted",
value: PropValue::BoolToggle {
field: "ri_clip_inverted",
value: clip_inverted,
},
},
),
],
}]
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
match field {
"ri_clip" => {
self.clipping_enabled = if value == "toggle" {
!self.clipping_enabled
"ri_show_image" => {
let on = if value == "toggle" {
!self.flags.contains(acadrust::entities::ImageDisplayFlags::SHOW_IMAGE)
} else {
value == "true"
};
self.set_visible(on);
return;
}
"ri_show_clipped" => {
let on = if value == "toggle" {
!self
.flags
.contains(acadrust::entities::ImageDisplayFlags::USE_CLIPPING_BOUNDARY)
} else {
value == "true"
};
if on {
self.flags |= acadrust::entities::ImageDisplayFlags::USE_CLIPPING_BOUNDARY;
} else {
self.flags &= !acadrust::entities::ImageDisplayFlags::USE_CLIPPING_BOUNDARY;
}
return;
}
"ri_clip_inverted" => {
let on = if value == "toggle" {
self.clip_boundary.clip_mode != acadrust::entities::ClipMode::Inside
} else {
value == "true"
};
self.clip_boundary.clip_mode = if on {
acadrust::entities::ClipMode::Inside
} else {
acadrust::entities::ClipMode::Outside
};
return;
}
_ => {}
@@ -424,57 +468,91 @@ impl Grippable for Wipeout {
impl PropertyEditable for Wipeout {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("Insert X", "wo_ox", self.insertion_point.x),
edit("Insert Y", "wo_oy", self.insertion_point.y),
edit("Insert Z", "wo_oz", self.insertion_point.z),
edit("Brightness", "wo_bright", self.brightness as f64),
edit("Contrast", "wo_contrast", self.contrast as f64),
edit("Fade", "wo_fade", self.fade as f64),
Property {
label: "Clipping".into(),
field: "wo_clip",
value: PropValue::BoolToggle {
field: "wo_clip",
value: self.clipping_enabled,
let show_image = self.flags.contains(acadrust::entities::WipeoutDisplayFlags::SHOW_IMAGE);
let show_clipped = self
.flags
.contains(acadrust::entities::WipeoutDisplayFlags::USE_CLIPPING_BOUNDARY);
let bg_transparency = self
.flags
.contains(acadrust::entities::WipeoutDisplayFlags::TRANSPARENCY_ON);
vec![
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "wo_ox", self.insertion_point.x),
edit("Position Y", "wo_oy", self.insertion_point.y),
edit("Position Z", "wo_oz", self.insertion_point.z),
],
},
PropSection {
title: "Misc".into(),
props: vec![
Property {
label: "Show image".into(),
field: "wo_show_image",
value: PropValue::BoolToggle {
field: "wo_show_image",
value: show_image,
},
},
},
ro(
"Class Version",
"wo_class_version",
self.class_version.to_string(),
),
ro("Clip Mode", "wo_clip_mode", format!("{:?}", self.clip_mode)),
ro(
"Definition",
"wo_def_handle",
match self.definition_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
Property {
label: "Show clipped".into(),
field: "wo_show_clipped",
value: PropValue::BoolToggle {
field: "wo_show_clipped",
value: show_clipped,
},
},
),
ro(
"Def Reactor",
"wo_def_reactor_handle",
match self.definition_reactor_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
Property {
label: "Background transparency".into(),
field: "wo_bg_transparency",
value: PropValue::BoolToggle {
field: "wo_bg_transparency",
value: bg_transparency,
},
},
),
],
}]
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
match field {
"wo_clip" => {
self.clipping_enabled = if value == "toggle" {
!self.clipping_enabled
"wo_show_image" => {
let on = if value == "toggle" {
!self.flags.contains(acadrust::entities::WipeoutDisplayFlags::SHOW_IMAGE)
} else {
value == "true"
};
self.set_frame_visible(on);
return;
}
"wo_show_clipped" => {
let on = if value == "toggle" {
!self
.flags
.contains(acadrust::entities::WipeoutDisplayFlags::USE_CLIPPING_BOUNDARY)
} else {
value == "true"
};
if on {
self.flags |= acadrust::entities::WipeoutDisplayFlags::USE_CLIPPING_BOUNDARY;
} else {
self.flags -= acadrust::entities::WipeoutDisplayFlags::USE_CLIPPING_BOUNDARY;
}
return;
}
"wo_bg_transparency" => {
let on = if value == "toggle" {
!self.flags.contains(acadrust::entities::WipeoutDisplayFlags::TRANSPARENCY_ON)
} else {
value == "true"
};
if on {
self.flags |= acadrust::entities::WipeoutDisplayFlags::TRANSPARENCY_ON;
} else {
self.flags -= acadrust::entities::WipeoutDisplayFlags::TRANSPARENCY_ON;
}
return;
}
_ => {}
@@ -486,9 +564,6 @@ impl PropertyEditable for Wipeout {
"wo_ox" => self.insertion_point.x = v,
"wo_oy" => self.insertion_point.y = v,
"wo_oz" => self.insertion_point.z = v,
"wo_bright" => self.brightness = v.clamp(0.0, 100.0) as u8,
"wo_contrast" => self.contrast = v.clamp(0.0, 100.0) as u8,
"wo_fade" => self.fade = v.clamp(0.0, 100.0) as u8,
_ => {}
}
}
+9 -9
View File
@@ -92,12 +92,12 @@ impl PropertyEditable for Ray {
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("Base X", "ray_bx", self.base_point.x),
edit("Base Y", "ray_by", self.base_point.y),
edit("Base Z", "ray_bz", self.base_point.z),
edit("Dir X", "ray_dx", self.direction.x),
edit("Dir Y", "ray_dy", self.direction.y),
edit("Dir Z", "ray_dz", self.direction.z),
edit("Start X", "ray_bx", self.base_point.x),
edit("Start Y", "ray_by", self.base_point.y),
edit("Start Z", "ray_bz", self.base_point.z),
edit("Direction vector X", "ray_dx", self.direction.x),
edit("Direction vector Y", "ray_dy", self.direction.y),
edit("Direction vector Z", "ray_dz", self.direction.z),
],
}]
}
@@ -232,9 +232,9 @@ impl PropertyEditable for XLine {
edit("Base X", "xl_bx", self.base_point.x),
edit("Base Y", "xl_by", self.base_point.y),
edit("Base Z", "xl_bz", self.base_point.z),
edit("Dir X", "xl_dx", self.direction.x),
edit("Dir Y", "xl_dy", self.direction.y),
edit("Dir Z", "xl_dz", self.direction.z),
edit("Direction vector X", "xl_dx", self.direction.x),
edit("Direction vector Y", "xl_dy", self.direction.y),
edit("Direction vector Z", "xl_dz", self.direction.z),
],
}]
}
+17 -13
View File
@@ -7,7 +7,7 @@
use acadrust::entities::Solid;
use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, square_grip};
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};
@@ -98,21 +98,25 @@ impl Grippable for Solid {
impl PropertyEditable for Solid {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
// Elevation is the OCS Z shared by the planar corners (no dedicated
// field on the entity); reported from the first corner's Z.
let elevation = self.first_corner.z;
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("P1 X", "sl_p1x", self.first_corner.x),
edit("P1 Y", "sl_p1y", self.first_corner.y),
edit("P1 Z", "sl_p1z", self.first_corner.z),
edit("P2 X", "sl_p2x", self.second_corner.x),
edit("P2 Y", "sl_p2y", self.second_corner.y),
edit("P2 Z", "sl_p2z", self.second_corner.z),
edit("P3 X", "sl_p3x", self.third_corner.x),
edit("P3 Y", "sl_p3y", self.third_corner.y),
edit("P3 Z", "sl_p3z", self.third_corner.z),
edit("P4 X", "sl_p4x", self.fourth_corner.x),
edit("P4 Y", "sl_p4y", self.fourth_corner.y),
edit("P4 Z", "sl_p4z", self.fourth_corner.z),
edit("Point 1 X", "sl_p1x", self.first_corner.x),
edit("Point 1 Y", "sl_p1y", self.first_corner.y),
edit("Point 1 Z", "sl_p1z", self.first_corner.z),
edit("Point 2 X", "sl_p2x", self.second_corner.x),
edit("Point 2 Y", "sl_p2y", self.second_corner.y),
edit("Point 2 Z", "sl_p2z", self.second_corner.z),
edit("Point 3 X", "sl_p3x", self.third_corner.x),
edit("Point 3 Y", "sl_p3y", self.third_corner.y),
edit("Point 3 Z", "sl_p3z", self.third_corner.z),
edit("Point 4 X", "sl_p4x", self.fourth_corner.x),
edit("Point 4 Y", "sl_p4y", self.fourth_corner.y),
edit("Point 4 Z", "sl_p4z", self.fourth_corner.z),
ro("Elevation", "sl_elev", format!("{:.4}", elevation)),
edit("Thickness", "sl_thick", self.thickness),
],
}]
+38 -135
View File
@@ -10,7 +10,7 @@ use acadrust::entities::{Body, Region, Solid3D, Surface};
use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{center_grip, ro_prop as ro};
use crate::entities::common::{center_grip, edit_prop as edit, parse_f64, ro_prop as ro};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable};
use crate::scene::model::object::{GripApply, GripDef, PropSection};
@@ -52,17 +52,6 @@ fn translate_wires(wires: &mut Vec<acadrust::entities::Wire>, d: Vec3) {
}
}
fn acis_size_str(has_data: bool, sat_len: usize, sab_len: usize, is_binary: bool) -> String {
if !has_data {
return "none".to_string();
}
if is_binary {
format!("{} bytes (SAB)", sab_len)
} else {
format!("{} bytes (SAT)", sat_len)
}
}
// ── Solid3D ───────────────────────────────────────────────────────────────────
impl Grippable for Solid3D {
@@ -85,58 +74,40 @@ impl Grippable for Solid3D {
impl PropertyEditable for Solid3D {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
let size = acis_size_str(
self.acis_data.has_data(),
self.acis_data.sat_data.len(),
self.acis_data.sab_data.len(),
self.acis_data.is_binary,
);
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro(
"Ref Pt X",
"s3d_px",
format!("{:.4}", self.point_of_reference.x),
),
ro(
"Ref Pt Y",
"s3d_py",
format!("{:.4}", self.point_of_reference.y),
),
ro(
"Ref Pt Z",
"s3d_pz",
format!("{:.4}", self.point_of_reference.z),
),
ro("ACIS Data", "s3d_acis", size),
ro(
"UID",
"s3d_uid",
if self.uid.is_empty() {
"(none)".to_string()
} else {
self.uid.clone()
},
),
ro(
"Silhouettes",
"s3d_silhouettes",
self.silhouettes.len().to_string(),
),
ro(
"History",
"s3d_history",
match self.history_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
},
),
],
}]
let history = match self.history_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "None".to_string(),
};
vec![
PropSection {
title: "Solid History".into(),
props: vec![
ro("History", "s3d_history", history),
ro("Show History", "s3d_show_history", String::new()),
],
},
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "s3d_px", self.point_of_reference.x),
edit("Position Y", "s3d_py", self.point_of_reference.y),
edit("Position Z", "s3d_pz", self.point_of_reference.z),
],
},
]
}
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Some(v) = parse_f64(value) else {
return;
};
match field {
"s3d_px" => self.point_of_reference.x = v,
"s3d_py" => self.point_of_reference.y = v,
"s3d_pz" => self.point_of_reference.z = v,
_ => {}
}
}
}
// ── Region ────────────────────────────────────────────────────────────────────
@@ -161,45 +132,11 @@ impl Grippable for Region {
impl PropertyEditable for Region {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
let size = acis_size_str(
self.acis_data.has_data(),
self.acis_data.sat_data.len(),
self.acis_data.sab_data.len(),
self.acis_data.is_binary,
);
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro(
"Ref Pt X",
"rgn_px",
format!("{:.4}", self.point_of_reference.x),
),
ro(
"Ref Pt Y",
"rgn_py",
format!("{:.4}", self.point_of_reference.y),
),
ro(
"Ref Pt Z",
"rgn_pz",
format!("{:.4}", self.point_of_reference.z),
),
ro("ACIS Data", "rgn_acis", size),
ro(
"UID",
"rgn_uid",
if self.uid.is_empty() {
"(none)".to_string()
} else {
self.uid.clone()
},
),
ro(
"Silhouettes",
"rgn_silhouettes",
self.silhouettes.len().to_string(),
),
ro("Area", "rgn_area", String::new()),
ro("Perimeter", "rgn_perimeter", String::new()),
],
}]
}
@@ -229,45 +166,11 @@ impl Grippable for Body {
impl PropertyEditable for Body {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
let size = acis_size_str(
self.acis_data.has_data(),
self.acis_data.sat_data.len(),
self.acis_data.sab_data.len(),
self.acis_data.is_binary,
);
vec![PropSection {
title: "Geometry".into(),
title: "Solid History".into(),
props: vec![
ro(
"Ref Pt X",
"bdy_px",
format!("{:.4}", self.point_of_reference.x),
),
ro(
"Ref Pt Y",
"bdy_py",
format!("{:.4}", self.point_of_reference.y),
),
ro(
"Ref Pt Z",
"bdy_pz",
format!("{:.4}", self.point_of_reference.z),
),
ro("ACIS Data", "bdy_acis", size),
ro(
"UID",
"bdy_uid",
if self.uid.is_empty() {
"(none)".to_string()
} else {
self.uid.clone()
},
),
ro(
"Silhouettes",
"bdy_silhouettes",
self.silhouettes.len().to_string(),
),
ro("History", "bdy_history", String::new()),
ro("Show History", "bdy_show_history", String::new()),
],
}]
}
+103 -14
View File
@@ -5,7 +5,7 @@ use truck_modeling::{
};
use crate::command::EntityTransform;
use crate::entities::common::{ro_prop as ro, square_grip};
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
use crate::entities::traits::TruckConvertible;
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::model::object::{GripApply, GripDef, PropSection};
@@ -107,21 +107,110 @@ fn grips(spline: &Spline) -> Vec<GripDef> {
}
fn properties(spline: &Spline) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Degree", "degree", spline.degree.to_string()),
ro(
"Control Pts",
"ctrl_pts",
spline.control_points.len().to_string(),
),
ro("Fit Pts", "fit_pts", spline.fit_points.len().to_string()),
],
}]
let show = if spline.fit_points.is_empty() {
"Control Vertices"
} else {
"Fit Points"
};
let cp0 = spline.control_points.first();
let fp0 = spline.fit_points.first();
let w0 = spline.weights.first().copied();
// Polyline-approximation length over the effective defining points.
let pts = if spline.fit_points.is_empty() {
&spline.control_points
} else {
&spline.fit_points
};
let mut length = 0.0;
for w in pts.windows(2) {
let dx = w[1].x - w[0].x;
let dy = w[1].y - w[0].y;
let dz = w[1].z - w[0].z;
length += (dx * dx + dy * dy + dz * dz).sqrt();
}
let closed = spline.flags.closed || spline.flags.periodic;
let yes_no = |b: bool| if b { "Yes" } else { "No" };
vec![
PropSection {
title: "Data Points".into(),
props: vec![
ro("Show", "show", show),
edit("Degree", "degree", spline.degree as f64),
ro(
"Control Point Count",
"ctrl_pt_count",
spline.control_points.len().to_string(),
),
ro("Control Point", "ctrl_pt_index", "0"),
ro(
"Control Point X",
"ctrl_pt_x",
format!("{:.4}", cp0.map(|p| p.x).unwrap_or(0.0)),
),
ro(
"Control Point Y",
"ctrl_pt_y",
format!("{:.4}", cp0.map(|p| p.y).unwrap_or(0.0)),
),
ro(
"Control Point Z",
"ctrl_pt_z",
format!("{:.4}", cp0.map(|p| p.z).unwrap_or(0.0)),
),
ro("Weight", "weight", format!("{:.4}", w0.unwrap_or(1.0))),
ro("Knot Parameterization", "knot_param", String::new()),
ro(
"Fit Point Count",
"fit_pt_count",
spline.fit_points.len().to_string(),
),
ro("Fit Point", "fit_pt_index", "0"),
ro(
"Fit Point X",
"fit_pt_x",
format!("{:.4}", fp0.map(|p| p.x).unwrap_or(0.0)),
),
ro(
"Fit Point Y",
"fit_pt_y",
format!("{:.4}", fp0.map(|p| p.y).unwrap_or(0.0)),
),
ro(
"Fit Point Z",
"fit_pt_z",
format!("{:.4}", fp0.map(|p| p.z).unwrap_or(0.0)),
),
ro("Fit Tolerance", "fit_tolerance", String::new()),
ro("Start Tangent X", "start_tan_x", String::new()),
ro("Start Tangent Y", "start_tan_y", String::new()),
ro("Start Tangent Z", "start_tan_z", String::new()),
ro("End Tangent X", "end_tan_x", String::new()),
ro("End Tangent Y", "end_tan_y", String::new()),
ro("End Tangent Z", "end_tan_z", String::new()),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro("Closed", "closed", yes_no(closed)),
ro("Planar", "planar", yes_no(spline.flags.planar)),
ro("Length", "length", format!("{length:.4}")),
ro("Area", "area", String::new()),
],
},
]
}
fn apply_geom_prop(_spline: &mut Spline, _field: &str, _value: &str) {}
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;
}
}
}
fn apply_grip(spline: &mut Spline, grip_id: usize, apply: GripApply) {
if let Some(cp) = spline.control_points.get_mut(grip_id) {
+91 -97
View File
@@ -819,112 +819,106 @@ impl Grippable for Table {
impl PropertyEditable for Table {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
use crate::entities::common::edit_prop as edit;
use acadrust::entities::table::BreakOptionFlags;
let fmt_h = |oh: &Option<acadrust::types::Handle>| -> String {
match oh {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
}
};
vec![PropSection {
title: "Table".into(),
props: vec![
ro("Rows", "tbl_rows", self.rows.len().to_string()),
ro("Columns", "tbl_cols", self.columns.len().to_string()),
ro(
"Insert X",
"tbl_ix",
format!("{:.4}", self.insertion_point.x),
),
ro(
"Insert Y",
"tbl_iy",
format!("{:.4}", self.insertion_point.y),
),
ro(
"Insert Z",
"tbl_iz",
format!("{:.4}", self.insertion_point.z),
),
ro(
"Table Style",
"tbl_style_handle",
fmt_h(&self.table_style_handle),
),
ro(
"Block Record",
"tbl_block_rec_handle",
fmt_h(&self.block_record_handle),
),
ro(
"Data Version",
"tbl_data_version",
self.data_version.to_string(),
),
ro(
"Value Flags",
"tbl_value_flags",
format!("{:#010x}", self.value_flags),
),
ro(
"Override Flag",
"tbl_override_flag",
if self.override_flag { "Yes" } else { "No" },
),
ro(
"Override Border Color",
"tbl_override_border_color",
if self.override_border_color {
"Yes"
} else {
"No"
},
),
ro(
"Override Border LW",
"tbl_override_border_lw",
if self.override_border_line_weight {
"Yes"
} else {
"No"
},
),
ro(
"Override Border Vis",
"tbl_override_border_vis",
if self.override_border_visibility {
"Yes"
} else {
"No"
},
),
ro(
"Break Spacing",
"tbl_break_spacing",
format!("{:.4}", self.break_spacing),
),
ro(
"Break Flow",
"tbl_break_flow",
format!("{:?}", self.break_flow_direction),
),
ro(
"Break Options",
"tbl_break_options",
format!("{:#018b}", self.break_options.bits()),
),
ro(
"Normal",
"tbl_normal",
format!(
"{:.3}, {:.3}, {:.3}",
self.normal.x, self.normal.y, self.normal.z
let yes_no = |b: bool| -> String {
if b { "Yes".into() } else { "No".into() }
};
// Direction = angle of the horizontal direction vector in the XY plane.
let direction_deg =
(self.horizontal_direction.y.atan2(self.horizontal_direction.x)).to_degrees();
vec![
PropSection {
title: "Table".into(),
props: vec![
ro(
"Table style",
"tbl_style_handle",
fmt_h(&self.table_style_handle),
),
),
],
}]
ro("Rows", "tbl_rows", self.rows.len().to_string()),
ro("Columns", "tbl_cols", self.columns.len().to_string()),
ro("Direction", "tbl_direction", format!("{:.4}", direction_deg)),
ro(
"Table width",
"tbl_width",
format!("{:.4}", self.total_width()),
),
ro(
"Table height",
"tbl_height",
format!("{:.4}", self.total_height()),
),
],
},
PropSection {
title: "Table Breaks".into(),
props: vec![
ro(
"Enabled",
"tbl_break_enabled",
yes_no(self.break_options.contains(BreakOptionFlags::ENABLE_BREAKS)),
),
ro(
"Direction",
"tbl_break_direction",
format!("{:?}", self.break_flow_direction),
),
ro(
"Repeat top labels",
"tbl_break_repeat_top",
yes_no(
self.break_options
.contains(BreakOptionFlags::REPEAT_TOP_LABELS),
),
),
ro(
"Repeat bottom labels",
"tbl_break_repeat_bottom",
yes_no(
self.break_options
.contains(BreakOptionFlags::REPEAT_BOTTOM_LABELS),
),
),
ro(
"Manual positions",
"tbl_break_manual_positions",
yes_no(
self.break_options
.contains(BreakOptionFlags::ALLOW_MANUAL_POSITIONS),
),
),
ro(
"Manual heights",
"tbl_break_manual_heights",
yes_no(
self.break_options
.contains(BreakOptionFlags::ALLOW_MANUAL_HEIGHTS),
),
),
ro("Break height", "tbl_break_height", String::new()),
edit("Spacing", "tbl_break_spacing", self.break_spacing),
],
},
]
}
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
use crate::entities::common::parse_f64;
if field == "tbl_break_spacing" {
if let Some(v) = parse_f64(value) {
self.break_spacing = v;
}
}
}
}
impl Transformable for Table {
+87 -53
View File
@@ -1,7 +1,7 @@
use acadrust::entities::{Text, TextHorizontalAlignment as HA, TextVerticalAlignment as VA};
use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, parse_f64, square_grip};
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
use crate::entities::text_support::{
resolve_dxf_special_chars, resolve_text_style, text_local_bounds,
};
@@ -170,61 +170,95 @@ fn grips(t: &Text) -> Vec<GripDef> {
}
fn properties(t: &Text, text_style_names: &[String]) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("Insert X", "ins_x", t.insertion_point.x),
edit("Insert Y", "ins_y", t.insertion_point.y),
edit("Insert Z", "ins_z", t.insertion_point.z),
edit("Height", "height", t.height),
edit("Rotation", "rotation", t.rotation.to_degrees()),
edit("Width Factor", "width_factor", t.width_factor),
edit(
"Oblique Angle",
"oblique_angle",
t.oblique_angle.to_degrees(),
),
Property {
label: "H-Align".into(),
field: "h_align",
value: PropValue::Choice {
selected: text_halign_str(&t.horizontal_alignment).to_string(),
options: ["Left", "Center", "Right", "Aligned", "Middle", "Fit"]
.into_iter()
.map(str::to_string)
.collect(),
// Text alignment point (second alignment / justify point). Falls back to
// the insertion point for Left/Baseline text where no second point exists.
let ap = t.alignment_point.unwrap_or(t.insertion_point);
vec![
PropSection {
title: "Text".into(),
props: vec![
Property {
label: "Contents".into(),
field: "content",
value: PropValue::EditText(t.value.clone()),
},
},
Property {
label: "V-Align".into(),
field: "v_align",
value: PropValue::Choice {
selected: text_valign_str(&t.vertical_alignment).to_string(),
options: ["Baseline", "Bottom", "Middle", "Top"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
Property {
label: "Content".into(),
field: "content",
value: PropValue::EditText(t.value.clone()),
},
Property {
label: "Style".into(),
field: "style",
value: PropValue::Choice {
selected: if t.style.trim().is_empty() {
"Standard".into()
} else {
t.style.clone()
Property {
label: "Style".into(),
field: "style",
value: PropValue::Choice {
selected: if t.style.trim().is_empty() {
"Standard".into()
} else {
t.style.clone()
},
options: text_style_names.to_vec(),
},
options: text_style_names.to_vec(),
},
},
],
}]
ro("Annotative", "annotative", String::new()),
ro("Annotative scale", "annotative_scale", String::new()),
Property {
label: "Justify".into(),
field: "h_align",
value: PropValue::Choice {
selected: text_halign_str(&t.horizontal_alignment).to_string(),
options: ["Left", "Center", "Right", "Aligned", "Middle", "Fit"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
Property {
label: "V-Align".into(),
field: "v_align",
value: PropValue::Choice {
selected: text_valign_str(&t.vertical_alignment).to_string(),
options: ["Baseline", "Bottom", "Middle", "Top"]
.into_iter()
.map(str::to_string)
.collect(),
},
},
edit("Height", "height", t.height),
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)),
],
},
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "ins_x", t.insertion_point.x),
edit("Position Y", "ins_y", t.insertion_point.y),
edit("Position Z", "ins_z", t.insertion_point.z),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro(
"Upside down",
"upside_down",
if t.generation_flags & 0x4 != 0 {
"Yes"
} else {
"No"
},
),
ro(
"Backward",
"backward",
if t.generation_flags & 0x2 != 0 {
"Yes"
} else {
"No"
},
),
],
},
]
}
fn apply_geom_prop(t: &mut Text, field: &str, value: &str) {
+36 -44
View File
@@ -294,50 +294,40 @@ impl Grippable for Tolerance {
impl PropertyEditable for Tolerance {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Text", "tol_text", self.text.clone()),
edit("Insert X", "tol_ix", self.insertion_point.x),
edit("Insert Y", "tol_iy", self.insertion_point.y),
edit("Insert Z", "tol_iz", self.insertion_point.z),
ro(
"Dim Style",
"tol_dim_style",
if self.dimension_style_name.is_empty() {
"(default)".to_string()
} else {
self.dimension_style_name.clone()
},
),
ro(
"Dim Style Handle",
"tol_dim_style_handle",
match self.dimension_style_handle {
Some(h) if !h.is_null() => format!("{:X}", h.value()),
_ => "(none)".to_string(),
},
),
edit("Text Height", "tol_text_height", self.text_height),
edit("Dim Gap", "tol_dim_gap", self.dimension_gap),
ro(
"Direction",
"tol_direction",
format!(
"{:.3}, {:.3}, {:.3}",
self.direction.x, self.direction.y, self.direction.z
vec![
PropSection {
title: "Text".into(),
props: vec![
ro("Text style", "tol_text_style", String::new()),
edit("Text height", "tol_text_height", self.text_height),
],
},
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "tol_ix", self.insertion_point.x),
edit("Position Y", "tol_iy", self.insertion_point.y),
edit("Position Z", "tol_iz", self.insertion_point.z),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro(
"Dimension style",
"tol_dim_style",
if self.dimension_style_name.is_empty() {
"(default)".to_string()
} else {
self.dimension_style_name.clone()
},
),
),
ro(
"Normal",
"tol_normal",
format!(
"{:.3}, {:.3}, {:.3}",
self.normal.x, self.normal.y, self.normal.z
),
),
],
}]
edit("Direction X", "tol_dir_x", self.direction.x),
edit("Direction Y", "tol_dir_y", self.direction.y),
edit("Direction Z", "tol_dir_z", self.direction.z),
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
@@ -349,7 +339,9 @@ impl PropertyEditable for Tolerance {
"tol_iy" => self.insertion_point.y = v,
"tol_iz" => self.insertion_point.z = v,
"tol_text_height" if v > 0.0 => self.text_height = v,
"tol_dim_gap" => self.dimension_gap = v,
"tol_dir_x" => self.direction.x = v,
"tol_dir_y" => self.direction.y = v,
"tol_dir_z" => self.direction.z = v,
_ => {}
}
}
+67 -51
View File
@@ -147,58 +147,74 @@ impl Grippable for Underlay {
impl PropertyEditable for Underlay {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
let type_str = match self.underlay_type {
acadrust::entities::UnderlayType::Pdf => "PDF",
acadrust::entities::UnderlayType::Dwf => "DWF",
acadrust::entities::UnderlayType::Dgn => "DGN",
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() {
(0.0_f64, 0.0_f64)
} else {
let verts = self.world_clip_boundary();
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
for v in &verts {
min_x = min_x.min(v.x);
min_y = min_y.min(v.y);
max_x = max_x.max(v.x);
max_y = max_y.max(v.y);
}
(max_x - min_x, max_y - min_y)
};
vec![PropSection {
title: "Geometry".into(),
props: vec![
ro("Type", "ul_type", type_str),
edit("Ins X", "ul_ix", self.insertion_point.x),
edit("Ins Y", "ul_iy", self.insertion_point.y),
edit("Ins Z", "ul_iz", self.insertion_point.z),
edit("X Scale", "ul_sx", self.x_scale),
edit("Y Scale", "ul_sy", self.y_scale),
edit("Z Scale", "ul_sz", self.z_scale),
edit("Rotation", "ul_rot", self.rotation.to_degrees()),
edit("Contrast", "ul_contrast", self.contrast as f64),
edit("Fade", "ul_fade", self.fade as f64),
ro(
"On",
"ul_on",
if self.flags.contains(UnderlayDisplayFlags::ON) {
"Yes"
} else {
"No"
},
),
ro(
"Clipping",
"ul_clip",
if self.flags.contains(UnderlayDisplayFlags::CLIPPING) {
"Yes"
} else {
"No"
},
),
ro(
"Monochrome",
"ul_mono",
if self.flags.contains(UnderlayDisplayFlags::MONOCHROME) {
"Yes"
} else {
"No"
},
),
ro(
"Clip Inverted",
"ul_clip_inverted",
if self.clip_inverted { "Yes" } else { "No" },
),
],
}]
let show = self.flags.contains(UnderlayDisplayFlags::ON);
let clipping = self.flags.contains(UnderlayDisplayFlags::CLIPPING);
let monochrome = self.flags.contains(UnderlayDisplayFlags::MONOCHROME);
let adjust_bg = self.flags.contains(UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND);
vec![
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "ul_ix", self.insertion_point.x),
edit("Position Y", "ul_iy", self.insertion_point.y),
edit("Position Z", "ul_iz", self.insertion_point.z),
edit("Scale X", "ul_sx", self.x_scale),
edit("Scale Y", "ul_sy", self.y_scale),
edit("Scale Z", "ul_sz", self.z_scale),
ro("Width", "ul_width", format!("{:.4}", width)),
ro("Height", "ul_height", format!("{:.4}", height)),
edit("Rotation", "ul_rot", self.rotation.to_degrees()),
],
},
PropSection {
title: "Underlay Adjust".into(),
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),
),
],
},
PropSection {
title: "Misc".into(),
props: vec![
// Underlay name/path/layers live on the separate
// 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)),
ro("Underlay layers", "ul_layers", String::new()),
],
},
]
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
+78 -180
View File
@@ -122,196 +122,94 @@ fn properties(vp: &Viewport) -> Vec<PropSection> {
crate::scene::vp_effective_scale(vp.custom_scale, vp.view_height, vp.height);
let current_scale_label = scale_label(effective_scale);
let view_opts: Vec<String> = STD_VIEWS.iter().map(|s| s.to_string()).collect();
let current_view = viewport_view_label(vp);
let render_opts: Vec<String> = RENDER_MODES.iter().map(|(s, _)| s.to_string()).collect();
let current_render = render_mode_label(&vp.render_mode).to_string();
let shade_opts: Vec<String> = SHADE_PLOT_LABELS.iter().map(|s| s.to_string()).collect();
let current_shade = shade_plot_label(vp.shade_plot_mode).to_string();
vec![PropSection {
title: "Geometry".into(),
props: vec![
edit("Center X", "center_x", vp.center.x),
edit("Center Y", "center_y", vp.center.y),
edit("Center Z", "center_z", vp.center.z),
edit("Width", "vp_w", vp.width),
edit("Height", "vp_h", vp.height),
// Numeric scale entry (derived from view_height; writes both).
edit("Scale (num)", "vscale", effective_scale),
// Standard scale picker.
Property {
label: "Scale".into(),
field: "vscale_std",
value: PropValue::Choice {
selected: current_scale_label,
options: scale_opts,
},
},
// Standard view picker.
Property {
label: "View".into(),
field: "vp_view",
value: PropValue::Choice {
selected: current_view,
options: view_opts,
},
},
// Render mode picker.
Property {
label: "Render Mode".into(),
field: "vp_render",
value: PropValue::Choice {
selected: current_render,
options: render_opts,
},
},
// Shade plot mode picker.
Property {
label: "Shade Plot".into(),
field: "vp_shade_plot",
value: PropValue::Choice {
selected: current_shade,
options: shade_opts,
},
},
// Display state toggles.
Property {
label: "Locked".into(),
field: "vp_locked",
value: PropValue::BoolToggle {
field: "vp_locked",
value: vp.status.locked,
},
},
Property {
label: "On".into(),
field: "vp_on",
value: PropValue::BoolToggle {
let annotation_scale = if effective_scale.abs() > 1e-9 {
format!("1:{}", scale_label(effective_scale))
} else {
String::new()
};
vec![
PropSection {
title: "Geometry".into(),
props: vec![
edit("Center X", "center_x", vp.center.x),
edit("Center Y", "center_y", vp.center.y),
edit("Center Z", "center_z", vp.center.z),
edit("Height", "vp_h", vp.height),
edit("Width", "vp_w", vp.width),
],
},
PropSection {
title: "Misc".into(),
props: vec![
Property {
label: "On".into(),
field: "vp_on",
value: vp.status.is_on,
value: PropValue::BoolToggle {
field: "vp_on",
value: vp.status.is_on,
},
},
},
Property {
label: "Perspective".into(),
field: "vp_perspective",
value: PropValue::BoolToggle {
field: "vp_perspective",
value: vp.status.perspective,
ro("Clipped", "vp_clipped", String::new()),
Property {
label: "Display locked".into(),
field: "vp_locked",
value: PropValue::BoolToggle {
field: "vp_locked",
value: vp.status.locked,
},
},
},
Property {
label: "Hide Plot".into(),
field: "vp_hide_plot",
value: PropValue::BoolToggle {
field: "vp_hide_plot",
value: vp.status.hide_plot,
ro("Annotation scale", "vp_anno_scale", annotation_scale),
Property {
label: "Standard scale".into(),
field: "vscale_std",
value: PropValue::Choice {
selected: current_scale_label,
options: scale_opts,
},
},
},
Property {
label: "UCS Icon".into(),
field: "vp_ucs_icon",
value: PropValue::BoolToggle {
field: "vp_ucs_icon",
value: vp.ucs_icon_visible,
},
},
edit("Target X", "vtgt_x", vp.view_target.x),
edit("Target Z", "vtgt_z", vp.view_target.z),
// Perspective lens length.
edit("Lens Length (mm)", "vp_lens", vp.lens_length),
// Per-Viewport UCS.
Property {
label: "UCS Per Viewport".into(),
field: "vp_ucs_per_vp",
value: PropValue::BoolToggle {
edit("Custom scale", "vscale", effective_scale),
Property {
label: "UCS per viewport".into(),
field: "vp_ucs_per_vp",
value: vp.ucs_per_viewport,
value: PropValue::BoolToggle {
field: "vp_ucs_per_vp",
value: vp.ucs_per_viewport,
},
},
},
edit("UCS Origin X", "vp_ucs_ox", vp.ucs_origin.x),
edit("UCS Origin Y", "vp_ucs_oy", vp.ucs_origin.y),
edit("UCS Origin Z", "vp_ucs_oz", vp.ucs_origin.z),
edit("UCS X-Axis X", "vp_ucs_xx", vp.ucs_x_axis.x),
edit("UCS X-Axis Y", "vp_ucs_xy", vp.ucs_x_axis.y),
edit("UCS X-Axis Z", "vp_ucs_xz", vp.ucs_x_axis.z),
edit("UCS Y-Axis X", "vp_ucs_yx", vp.ucs_y_axis.x),
edit("UCS Y-Axis Y", "vp_ucs_yy", vp.ucs_y_axis.y),
edit("UCS Y-Axis Z", "vp_ucs_yz", vp.ucs_y_axis.z),
// Snap / grid metadata (drives the snap-aware UI; not rendered
// into the viewport contents directly).
edit("Snap Base X", "vp_snap_bx", vp.snap_base.x),
edit("Snap Base Y", "vp_snap_by", vp.snap_base.y),
edit("Snap Spacing X", "vp_snap_sx", vp.snap_spacing.x),
edit("Snap Spacing Y", "vp_snap_sy", vp.snap_spacing.y),
edit("Snap Angle", "vp_snap_ang", vp.snap_angle.to_degrees()),
edit("Twist Angle", "vp_twist", vp.twist_angle.to_degrees()),
edit("Front Clip Z", "vp_front_clip", vp.front_clip_z),
edit("Back Clip Z", "vp_back_clip", vp.back_clip_z),
edit("Circle Sides", "vp_circle_sides", vp.circle_sides as f64),
ro(
"Grid Flags",
"vp_grid_flags",
format!("{:#06b}", vp.grid_flags.to_bits()),
),
ro("Grid Major", "vp_grid_major", vp.grid_major.to_string()),
ro(
"Base UCS",
"vp_base_ucs_handle",
if vp.base_ucs_handle.is_null() {
"(none)".to_string()
} else {
format!("{:X}", vp.base_ucs_handle.value())
ro(
"Layer property overrides",
"vp_layer_overrides",
if vp.frozen_layers.is_empty() {
"No".to_string()
} else {
format!("Yes ({})", vp.frozen_layers.len())
},
),
ro(
"Visual style",
"vp_visual_style_handle",
if vp.visual_style_handle.is_null() {
"(none)".to_string()
} else {
format!("{:X}", vp.visual_style_handle.value())
},
),
Property {
label: "Shade plot".into(),
field: "vp_shade_plot",
value: PropValue::Choice {
selected: current_shade,
options: shade_opts,
},
},
),
ro("UCS Ortho", "vp_ucs_ortho", vp.ucs_ortho_type.to_string()),
ro(
"Background",
"vp_background_handle",
if vp.background_handle.is_null() {
"(none)".to_string()
} else {
format!("{:X}", vp.background_handle.value())
},
),
ro(
"Shade Plot",
"vp_shade_plot_handle",
if vp.shade_plot_handle.is_null() {
"(none)".to_string()
} else {
format!("{:X}", vp.shade_plot_handle.value())
},
),
ro(
"Visual Style",
"vp_visual_style_handle",
if vp.visual_style_handle.is_null() {
"(none)".to_string()
} else {
format!("{:X}", vp.visual_style_handle.value())
},
),
// 3D lighting (not yet applied to the viewport mesh path).
ro(
"Default Lighting",
"vp_default_lighting",
if vp.default_lighting { "Yes" } else { "No" },
),
ro(
"Lighting Type",
"vp_lighting_type",
vp.default_lighting_type.to_string(),
),
ro(
"Ambient Color",
"vp_ambient_color",
format!("{:#010x}", vp.ambient_color as u32),
),
],
}]
ro("Linked to Sheet View", "vp_sheet_view", String::new()),
],
},
]
}
/// Identify which standard view matches the viewport's view direction.