cargo fmt

This commit is contained in:
Hakan 2026-05-12 10:36:15 +03:00
commit ca07268c7c
204 changed files with 13988 additions and 5516 deletions

View file

@ -61,16 +61,28 @@ fn to_truck(arc: &Arc) -> TruckEntity {
for i in 0..=n {
let a = start_a + (end_a - start_a) * (i as f64 / n as f64);
let p = arc_pt(a);
pts.push([(p.x + t * nx) as f32, (p.y + t * ny) as f32, (p.z + t * nz) as f32]);
pts.push([
(p.x + t * nx) as f32,
(p.y + t * ny) as f32,
(p.z + t * nz) as f32,
]);
}
pts.push([f32::NAN; 3]);
let ps = arc_pt(sa);
pts.push([ps.x as f32, ps.y as f32, ps.z as f32]);
pts.push([(ps.x + t * nx) as f32, (ps.y + t * ny) as f32, (ps.z + t * nz) as f32]);
pts.push([
(ps.x + t * nx) as f32,
(ps.y + t * ny) as f32,
(ps.z + t * nz) as f32,
]);
pts.push([f32::NAN; 3]);
let pe = arc_pt(ea);
pts.push([pe.x as f32, pe.y as f32, pe.z as f32]);
pts.push([(pe.x + t * nx) as f32, (pe.y + t * ny) as f32, (pe.z + t * nz) as f32]);
pts.push([
(pe.x + t * nx) as f32,
(pe.y + t * ny) as f32,
(pe.z + t * nz) as f32,
]);
return TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![(cv, SnapHint::Center)],
@ -129,7 +141,11 @@ fn properties(arc: &Arc) -> 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(
"Start Angle (deg)",
"start_angle",
arc.start_angle.to_degrees(),
),
edit("End Angle (deg)", "end_angle", arc.end_angle.to_degrees()),
],
}

View file

@ -16,7 +16,11 @@ impl TruckConvertible for AttributeDefinition {
fn to_truck(&self, document: &acadrust::CadDocument) -> Option<TruckEntity> {
let normal = (self.normal.x, self.normal.y, self.normal.z);
let (wsx, wsy, wsz) = transform::ocs_point_to_wcs(
(self.insertion_point.x, self.insertion_point.y, self.insertion_point.z),
(
self.insertion_point.x,
self.insertion_point.y,
self.insertion_point.z,
),
normal,
);
let snap_pt = Vec3::new(wsx as f32, wsy as f32, wsz as f32);
@ -95,7 +99,9 @@ impl PropertyEditable for AttributeDefinition {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"att_ix" => self.insertion_point.x = v,
"att_iy" => self.insertion_point.y = v,
@ -126,7 +132,11 @@ impl TruckConvertible for AttributeEntity {
fn to_truck(&self, document: &acadrust::CadDocument) -> Option<TruckEntity> {
let normal = (self.normal.x, self.normal.y, self.normal.z);
let (wsx, wsy, wsz) = transform::ocs_point_to_wcs(
(self.insertion_point.x, self.insertion_point.y, self.insertion_point.z),
(
self.insertion_point.x,
self.insertion_point.y,
self.insertion_point.z,
),
normal,
);
let snap_pt = Vec3::new(wsx as f32, wsy as f32, wsz as f32);
@ -199,7 +209,9 @@ impl PropertyEditable for AttributeEntity {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"atte_ix" => self.insertion_point.x = v,
"atte_iy" => self.insertion_point.y = v,

View file

@ -31,21 +31,23 @@ fn to_truck(circle: &Circle) -> TruckEntity {
};
// right: centre + r*Ax, left: centre - r*Ax, top: centre + r*Ay, bot: centre - r*Ay
let p_right = pt(ax, (0.0, 0.0, 0.0), r);
let p_left = pt((ax.0 * -1.0, ax.1 * -1.0, ax.2 * -1.0), (0.0, 0.0, 0.0), r);
let p_top = pt(ay, (0.0, 0.0, 0.0), r);
let p_bot = pt((ay.0 * -1.0, ay.1 * -1.0, ay.2 * -1.0), (0.0, 0.0, 0.0), r);
let p_left = pt((ax.0 * -1.0, ax.1 * -1.0, ax.2 * -1.0), (0.0, 0.0, 0.0), r);
let p_top = pt(ay, (0.0, 0.0, 0.0), r);
let p_bot = pt((ay.0 * -1.0, ay.1 * -1.0, ay.2 * -1.0), (0.0, 0.0, 0.0), r);
let cv = Vec3::new(cwx as f32, cwy as f32, cwz as f32);
let rf = r as f32;
let q = |d: (f64, f64, f64)| Vec3::new(
(cwx + r * d.0) as f32,
(cwy + r * d.1) as f32,
(cwz + r * d.2) as f32,
);
let q = |d: (f64, f64, f64)| {
Vec3::new(
(cwx + r * d.0) as f32,
(cwy + r * d.1) as f32,
(cwz + r * d.2) as f32,
)
};
let snap_pts = vec![
(cv, SnapHint::Center),
(q(ax), SnapHint::Quadrant),
(q(ay), SnapHint::Quadrant),
(q(ax), SnapHint::Quadrant),
(q(ay), SnapHint::Quadrant),
(q((-ax.0, -ax.1, -ax.2)), SnapHint::Quadrant),
(q((-ay.0, -ay.1, -ay.2)), SnapHint::Quadrant),
];
@ -75,13 +77,21 @@ fn to_truck(circle: &Circle) -> TruckEntity {
pts.push([f32::NAN; 3]);
for i in 0..=n {
let (x, y, z) = circ_pt(i as f64 * tau / n as f64);
pts.push([(x + t * nx) as f32, (y + t * ny) as f32, (z + t * nz) as f32]);
pts.push([
(x + t * nx) as f32,
(y + t * ny) as f32,
(z + t * nz) as f32,
]);
}
pts.push([f32::NAN; 3]);
for i in 0..4usize {
let (x, y, z) = circ_pt(i as f64 * std::f64::consts::FRAC_PI_2);
pts.push([x as f32, y as f32, z as f32]);
pts.push([(x + t * nx) as f32, (y + t * ny) as f32, (z + t * nz) as f32]);
pts.push([
(x + t * nx) as f32,
(y + t * ny) as f32,
(z + t * nz) as f32,
]);
if i < 3 {
pts.push([f32::NAN; 3]);
}
@ -96,7 +106,7 @@ fn to_truck(circle: &Circle) -> TruckEntity {
}
let right = builder::vertex(p_right);
let left = builder::vertex(p_left);
let left = builder::vertex(p_left);
let upper = builder::circle_arc(&right, &left, p_top);
let lower = builder::circle_arc(&left, &right, p_bot);
let wire: Wire = [upper, lower].into_iter().collect();

View file

@ -6,7 +6,9 @@ use acadrust::Entity;
use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
use crate::entities::common::{
diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable};
use crate::scene::object::{GripApply, GripDef, PropSection};
@ -33,9 +35,21 @@ fn base_props(base: &DimensionBase) -> Vec<crate::scene::object::Property> {
edit("Text Y", "text_y", base.text_middle_point.y),
edit("Text Z", "text_z", base.text_middle_point.z),
edit("Text Rotation", "text_rotation", base.text_rotation),
edit("Horizontal Dir", "horizontal_direction", base.horizontal_direction),
edit("Line Spacing", "line_spacing_factor", base.line_spacing_factor),
ro("Measurement", "measurement", format!("{:.4}", base.actual_measurement)),
edit(
"Horizontal Dir",
"horizontal_direction",
base.horizontal_direction,
),
edit(
"Line Spacing",
"line_spacing_factor",
base.line_spacing_factor,
),
ro(
"Measurement",
"measurement",
format!("{:.4}", base.actual_measurement),
),
]
}
@ -43,13 +57,29 @@ fn properties(dim: &Dimension) -> PropSection {
let mut props = base_props(dim.base());
match dim {
Dimension::Aligned(d) => {
props.extend(linear_like_props(d.first_point, d.second_point, d.definition_point));
props.push(edit("Ext Rotation", "ext_line_rotation", d.ext_line_rotation));
props.extend(linear_like_props(
d.first_point,
d.second_point,
d.definition_point,
));
props.push(edit(
"Ext Rotation",
"ext_line_rotation",
d.ext_line_rotation,
));
}
Dimension::Linear(d) => {
props.extend(linear_like_props(d.first_point, d.second_point, d.definition_point));
props.extend(linear_like_props(
d.first_point,
d.second_point,
d.definition_point,
));
props.push(edit("Rotation", "rotation", d.rotation));
props.push(edit("Ext Rotation", "ext_line_rotation", d.ext_line_rotation));
props.push(edit(
"Ext Rotation",
"ext_line_rotation",
d.ext_line_rotation,
));
}
Dimension::Radius(d) => {
props.extend(radius_like_props(d.angle_vertex, d.definition_point));
@ -438,9 +468,7 @@ fn apply_ordinate_fields(d: &mut DimensionOrdinate, field: &str, value: &str) {
fn apply_transform(dim: &mut Dimension, t: &EntityTransform) {
match t {
EntityTransform::Translate(d) => dim.translate(acadrust::types::Vector3::new(
d.x as f64,
d.y as f64,
d.z as f64,
d.x as f64, d.y as f64, d.z as f64,
)),
EntityTransform::Rotate { center, angle_rad } => {
transform_dimension_points(dim, |pt| rotate_point(pt, *center, *angle_rad))

View file

@ -92,9 +92,9 @@ fn to_truck(ell: &Ellipse) -> TruckEntity {
};
let snap_pts = vec![
(center_v3, SnapHint::Center),
(q(r_major, 0.0), SnapHint::Quadrant),
(q(r_major, 0.0), SnapHint::Quadrant),
(q(-r_major, 0.0), SnapHint::Quadrant),
(q(0.0, r_minor), SnapHint::Quadrant),
(q(0.0, r_minor), SnapHint::Quadrant),
(q(0.0, -r_minor), SnapHint::Quadrant),
];
TruckEntity {

View file

@ -193,17 +193,29 @@ impl Grippable for Hatch {
}
}
BoundaryEdge::Line(l) => {
out.push(diamond_grip(id, Vec3::new(l.start.x as f32, l.start.y as f32, elev)));
out.push(diamond_grip(
id,
Vec3::new(l.start.x as f32, l.start.y as f32, elev),
));
id += 1;
out.push(diamond_grip(id, Vec3::new(l.end.x as f32, l.end.y as f32, elev)));
out.push(diamond_grip(
id,
Vec3::new(l.end.x as f32, l.end.y as f32, elev),
));
id += 1;
}
BoundaryEdge::CircularArc(a) => {
out.push(diamond_grip(id, Vec3::new(a.center.x as f32, a.center.y as f32, elev)));
out.push(diamond_grip(
id,
Vec3::new(a.center.x as f32, a.center.y as f32, elev),
));
id += 1;
}
BoundaryEdge::EllipticArc(e) => {
out.push(diamond_grip(id, Vec3::new(e.center.x as f32, e.center.y as f32, elev)));
out.push(diamond_grip(
id,
Vec3::new(e.center.x as f32, e.center.y as f32, elev),
));
id += 1;
}
BoundaryEdge::Spline(s) => {
@ -241,7 +253,8 @@ impl Grippable for Hatch {
BoundaryEdge::Polyline(p) => {
for v in &mut p.vertices {
if id == grip_id {
let (nx, ny) = resolve(&apply, Vec3::new(v.x as f32, v.y as f32, elev));
let (nx, ny) =
resolve(&apply, Vec3::new(v.x as f32, v.y as f32, elev));
v.x = nx;
v.y = ny;
break 'outer;
@ -251,14 +264,18 @@ impl Grippable for Hatch {
}
BoundaryEdge::Line(l) => {
if id == grip_id {
let (nx, ny) = resolve(&apply, Vec3::new(l.start.x as f32, l.start.y as f32, elev));
let (nx, ny) = resolve(
&apply,
Vec3::new(l.start.x as f32, l.start.y as f32, elev),
);
l.start.x = nx;
l.start.y = ny;
break 'outer;
}
id += 1;
if id == grip_id {
let (nx, ny) = resolve(&apply, Vec3::new(l.end.x as f32, l.end.y as f32, elev));
let (nx, ny) =
resolve(&apply, Vec3::new(l.end.x as f32, l.end.y as f32, elev));
l.end.x = nx;
l.end.y = ny;
break 'outer;
@ -267,7 +284,10 @@ impl Grippable for Hatch {
}
BoundaryEdge::CircularArc(a) => {
if id == grip_id {
let (nx, ny) = resolve(&apply, Vec3::new(a.center.x as f32, a.center.y as f32, elev));
let (nx, ny) = resolve(
&apply,
Vec3::new(a.center.x as f32, a.center.y as f32, elev),
);
a.center.x = nx;
a.center.y = ny;
break 'outer;
@ -276,7 +296,10 @@ impl Grippable for Hatch {
}
BoundaryEdge::EllipticArc(e) => {
if id == grip_id {
let (nx, ny) = resolve(&apply, Vec3::new(e.center.x as f32, e.center.y as f32, elev));
let (nx, ny) = resolve(
&apply,
Vec3::new(e.center.x as f32, e.center.y as f32, elev),
);
e.center.x = nx;
e.center.y = ny;
break 'outer;
@ -287,7 +310,8 @@ impl Grippable for Hatch {
if !s.fit_points.is_empty() {
for fp in &mut s.fit_points {
if id == grip_id {
let (nx, ny) = resolve(&apply, Vec3::new(fp.x as f32, fp.y as f32, elev));
let (nx, ny) =
resolve(&apply, Vec3::new(fp.x as f32, fp.y as f32, elev));
fp.x = nx;
fp.y = ny;
break 'outer;
@ -297,7 +321,8 @@ impl Grippable for Hatch {
} else {
for cp in &mut s.control_points {
if id == grip_id {
let (nx, ny) = resolve(&apply, Vec3::new(cp.x as f32, cp.y as f32, elev));
let (nx, ny) =
resolve(&apply, Vec3::new(cp.x as f32, cp.y as f32, elev));
cp.x = nx;
cp.y = ny;
break 'outer;

View file

@ -84,7 +84,11 @@ fn apply_transform(ins: &mut Insert, t: &EntityTransform) {
};
let t = Transform::from_translation(Vector3::new(-(p1.x as f64), -(p1.y as f64), 0.0))
.then(&Transform::from_matrix(mirror))
.then(&Transform::from_translation(Vector3::new(p1.x as f64, p1.y as f64, 0.0)));
.then(&Transform::from_translation(Vector3::new(
p1.x as f64,
p1.y as f64,
0.0,
)));
acadrust::Entity::apply_transform(entity, &t);
});
}

View file

@ -26,7 +26,10 @@ fn to_truck(leader: &Leader) -> TruckEntity {
key_verts.push(p3(v));
}
for i in 0..verts.len().saturating_sub(1) {
tangents.push(TangentGeom::Line { p1: p3(&verts[i]), p2: p3(&verts[i + 1]) });
tangents.push(TangentGeom::Line {
p1: p3(&verts[i]),
p2: p3(&verts[i + 1]),
});
}
// Arrowhead at vertex[0]
@ -42,16 +45,28 @@ fn to_truck(leader: &Leader) -> TruckEntity {
let (s, c) = a.sin_cos();
let tip_f = p3(tip);
points.push(nan);
points.push([tip_f[0] + (dx*c - dy*s)*sz, tip_f[1] + (dx*s + dy*c)*sz, tip_f[2]]);
points.push([
tip_f[0] + (dx * c - dy * s) * sz,
tip_f[1] + (dx * s + dy * c) * sz,
tip_f[2],
]);
points.push(tip_f);
points.push([tip_f[0] + (dx*c + dy*s)*sz, tip_f[1] + (-dx*s + dy*c)*sz, tip_f[2]]);
points.push([
tip_f[0] + (dx * c + dy * s) * sz,
tip_f[1] + (-dx * s + dy * c) * sz,
tip_f[2],
]);
}
// Landing line at last vertex
if leader.hookline_enabled && verts.len() >= 2 {
let last = verts.last().unwrap();
let prev = &verts[verts.len() - 2];
let sign = if (last.x - prev.x) >= 0.0 { 1.0_f32 } else { -1.0_f32 };
let sign = if (last.x - prev.x) >= 0.0 {
1.0_f32
} else {
-1.0_f32
};
let len = leader.text_height as f32 * 1.5;
let last_f = p3(last);
points.push(nan);
@ -108,9 +123,7 @@ fn apply_grip(leader: &mut Leader, grip_id: usize, apply: GripApply) {
}
} else if let GripApply::Translate(d) = apply {
leader.translate(acadrust::types::Vector3::new(
d.x as f64,
d.y as f64,
d.z as f64,
d.x as f64, d.y as f64, d.z as f64,
));
}
}
@ -257,8 +270,20 @@ fn apply_geom_prop(leader: &mut Leader, field: &str, value: &str) {
_ => LeaderCreationType::WithText,
};
}
"arrow_enabled" => leader.arrow_enabled = if value == "toggle" { !leader.arrow_enabled } else { value == "true" },
"hookline_enabled" => leader.hookline_enabled = if value == "toggle" { !leader.hookline_enabled } else { value == "true" },
"arrow_enabled" => {
leader.arrow_enabled = if value == "toggle" {
!leader.arrow_enabled
} else {
value == "true"
}
}
"hookline_enabled" => {
leader.hookline_enabled = if value == "toggle" {
!leader.hookline_enabled
} else {
value == "true"
}
}
"hookline_direction" => {
leader.hookline_direction = match value {
"Same" => HooklineDirection::Same,
@ -404,7 +429,9 @@ fn apply_transform(leader: &mut Leader, t: &EntityTransform) {
impl TruckConvertible for Leader {
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
if self.vertices.is_empty() { return None; }
if self.vertices.is_empty() {
return None;
}
Some(to_truck(self))
}
}

View file

@ -14,36 +14,47 @@ use crate::scene::wire_model::TangentGeom;
fn to_truck(line: &Line) -> TruckEntity {
let normal = (line.normal.x, line.normal.y, line.normal.z);
let (sx, sy, sz) = crate::scene::transform::ocs_point_to_wcs(
(line.start.x, line.start.y, line.start.z), normal,
);
let (ex, ey, ez) = crate::scene::transform::ocs_point_to_wcs(
(line.end.x, line.end.y, line.end.z), normal,
(line.start.x, line.start.y, line.start.z),
normal,
);
let (ex, ey, ez) =
crate::scene::transform::ocs_point_to_wcs((line.end.x, line.end.y, line.end.z), normal);
let p0 = Point3::new(sx, sy, sz);
let p1 = Point3::new(ex, ey, ez);
let kv = vec![
[p0.x as f32, p0.y as f32, p0.z as f32],
[p1.x as f32, p1.y as f32, p1.z as f32],
];
let tangent = TangentGeom::Line { p1: kv[0], p2: kv[1] };
let tangent = TangentGeom::Line {
p1: kv[0],
p2: kv[1],
};
if line.thickness.abs() > 1e-10 {
let t = line.thickness;
let (nx, ny, nz) = normal;
let p0t = [
(sx + t * nx) as f32, (sy + t * ny) as f32, (sz + t * nz) as f32,
(sx + t * nx) as f32,
(sy + t * ny) as f32,
(sz + t * nz) as f32,
];
let p1t = [
(ex + t * nx) as f32, (ey + t * ny) as f32, (ez + t * nz) as f32,
(ex + t * nx) as f32,
(ey + t * ny) as f32,
(ez + t * nz) as f32,
];
let pts = vec![
kv[0], kv[1],
kv[0],
kv[1],
[f32::NAN; 3],
p0t, p1t,
p0t,
p1t,
[f32::NAN; 3],
kv[0], p0t,
kv[0],
p0t,
[f32::NAN; 3],
kv[1], p1t,
kv[1],
p1t,
];
return TruckEntity {
object: TruckObject::Lines(pts),

View file

@ -3,7 +3,9 @@ use glam::Vec3;
use truck_modeling::{builder, Edge, Point3, Wire};
use crate::command::EntityTransform;
use crate::entities::common::{diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
use crate::entities::common::{
diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
@ -32,7 +34,9 @@ fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] {
let a0 = (p0[1] - cy).atan2(p0[0] - cx);
let a1 = (p1[1] - cy).atan2(p1[0] - cx);
let (sa, mut ea) = if bulge > 0.0 { (a0, a1) } else { (a1, a0) };
if ea < sa { ea += TAU; }
if ea < sa {
ea += TAU;
}
let mid_a = sa + (ea - sa) * 0.5;
[cx + r * mid_a.cos(), cy + r * mid_a.sin()]
}
@ -58,7 +62,9 @@ fn bulge_from_midpoint(p0: [f64; 2], p1: [f64; 2], mid: [f64; 2]) -> Option<f64>
// Determine arc direction: cross product (p1-p0) × (mid-p0)
let cross = (p1[0] - p0[0]) * (mid[1] - p0[1]) - (p1[1] - p0[1]) * (mid[0] - p0[0]);
let (sa, mut ea) = if cross > 0.0 { (a0, a1) } else { (a1, a0) };
if ea < sa { ea += TAU; }
if ea < sa {
ea += TAU;
}
let span = ea - sa; // central angle in (0, TAU]
let bulge = (span / 4.0).tan();
Some(if cross >= 0.0 { bulge } else { -bulge })
@ -77,7 +83,11 @@ fn thick_segments(
let (nx, ny, nz) = normal;
let t = thickness;
let off = |p: [f32; 3]| -> [f32; 3] {
[(p[0] as f64 + t * nx) as f32, (p[1] as f64 + t * ny) as f32, (p[2] as f64 + t * nz) as f32]
[
(p[0] as f64 + t * nx) as f32,
(p[1] as f64 + t * ny) as f32,
(p[2] as f64 + t * nz) as f32,
]
};
let mut pts: Vec<[f32; 3]> = Vec::with_capacity(path_pts.len() * 2 + seg_data.len() * 3 + 4);
// Bottom path
@ -106,7 +116,13 @@ fn thick_segments(
pts.push(off(last));
}
}
TruckEntity { object: TruckObject::Lines(pts), snap_pts: vec![], tangent_geoms: tangents, key_vertices: key_verts, fill_tris: vec![] }
TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![],
tangent_geoms: tangents,
key_vertices: key_verts,
fill_tris: vec![],
}
}
fn to_truck(pline: &LwPolyline) -> TruckEntity {
@ -156,22 +172,40 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
if bulge.abs() < 1e-9 {
let (wx, wy, wz) = to_wcs(ox1, oy1);
path.push([wx as f32, wy as f32, wz as f32]);
tgs.push(TangentGeom::Line { p1: path[path.len()-2], p2: *path.last().unwrap() });
tgs.push(TangentGeom::Line {
p1: path[path.len() - 2],
p2: *path.last().unwrap(),
});
} else {
let angle = 4.0 * bulge.atan();
let dx = ox1 - ox0; let dy = oy1 - oy0;
let dx = ox1 - ox0;
let dy = oy1 - oy0;
let d = (dx * dx + dy * dy).sqrt().max(1e-12);
let r = (d / 2.0) / (angle / 2.0).sin().abs();
let mx = (ox0 + ox1) * 0.5; let my = (oy0 + oy1) * 0.5;
let px = -dy / d; let py = dx / d;
let mx = (ox0 + ox1) * 0.5;
let my = (oy0 + oy1) * 0.5;
let px = -dy / d;
let py = dx / d;
let ss = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
let ocx = mx + ss * px * (r - h); let ocy = my + ss * py * (r - h);
let ocx = mx + ss * px * (r - h);
let ocy = my + ss * py * (r - h);
let a0 = (oy0 - ocy).atan2(ox0 - ocx);
let mut a1 = (oy1 - ocy).atan2(ox1 - ocx);
if bulge > 0.0 { if a1 < a0 { a1 += TAU; } } else { if a1 > a0 { a1 -= TAU; } }
if bulge > 0.0 {
if a1 < a0 {
a1 += TAU;
}
} else {
if a1 > a0 {
a1 -= TAU;
}
}
let (wcx, wcy, wcz) = to_wcs(ocx, ocy);
tgs.push(TangentGeom::Circle { center: [wcx as f32, wcy as f32, wcz as f32], radius: r as f32 });
tgs.push(TangentGeom::Circle {
center: [wcx as f32, wcy as f32, wcz as f32],
radius: r as f32,
});
for j in 1..=16usize {
let a = a0 + (a1 - a0) * (j as f64 / 16.0);
let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin());
@ -199,26 +233,43 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
let (wx0, wy0, wz0) = to_wcs(ox0, oy0);
let p_start = [wx0 as f32, wy0 as f32, wz0 as f32];
pts.push(p_start);
if i == 0 { kv.push(p_start); }
if i == 0 {
kv.push(p_start);
}
if bulge.abs() < 1e-9 {
let (wx1, wy1, wz1) = to_wcs(ox1, oy1);
let p_end = [wx1 as f32, wy1 as f32, wz1 as f32];
pts.push(p_end);
kv.push(p_end);
tgs.push(TangentGeom::Line { p1: p_start, p2: p_end });
tgs.push(TangentGeom::Line {
p1: p_start,
p2: p_end,
});
} else {
let angle = 4.0 * bulge.atan();
let dx = ox1 - ox0; let dy = oy1 - oy0;
let dx = ox1 - ox0;
let dy = oy1 - oy0;
let d = (dx * dx + dy * dy).sqrt().max(1e-12);
let r = (d / 2.0) / (angle / 2.0).sin().abs();
let mx = (ox0 + ox1) * 0.5; let my = (oy0 + oy1) * 0.5;
let px = -dy / d; let py = dx / d;
let mx = (ox0 + ox1) * 0.5;
let my = (oy0 + oy1) * 0.5;
let px = -dy / d;
let py = dx / d;
let ss = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
let ocx = mx + ss * px * (r - h); let ocy = my + ss * py * (r - h);
let ocx = mx + ss * px * (r - h);
let ocy = my + ss * py * (r - h);
let a0 = (oy0 - ocy).atan2(ox0 - ocx);
let mut a1 = (oy1 - ocy).atan2(ox1 - ocx);
if bulge > 0.0 { if a1 < a0 { a1 += TAU; } } else { if a1 > a0 { a1 -= TAU; } }
if bulge > 0.0 {
if a1 < a0 {
a1 += TAU;
}
} else {
if a1 > a0 {
a1 -= TAU;
}
}
for j in 1..=16usize {
let a = a0 + (a1 - a0) * (j as f64 / 16.0);
let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin());
@ -227,7 +278,10 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
let (wx1, wy1, wz1) = to_wcs(ox1, oy1);
kv.push([wx1 as f32, wy1 as f32, wz1 as f32]);
let (wcx, wcy, wcz) = to_wcs(ocx, ocy);
tgs.push(TangentGeom::Circle { center: [wcx as f32, wcy as f32, wcz as f32], radius: r as f32 });
tgs.push(TangentGeom::Circle {
center: [wcx as f32, wcy as f32, wcz as f32],
radius: r as f32,
});
}
if i + 1 < seg_count {
pts.push([f32::NAN; 3]);
@ -314,7 +368,11 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
fn grips(pline: &LwPolyline) -> Vec<GripDef> {
let elev = pline.elevation as f32;
let n = pline.vertices.len();
let seg_count = if pline.is_closed { n } else { n.saturating_sub(1) };
let seg_count = if pline.is_closed {
n
} else {
n.saturating_sub(1)
};
let mut out: Vec<GripDef> = pline
.vertices
@ -335,7 +393,10 @@ fn grips(pline: &LwPolyline) -> Vec<GripDef> {
[v1.location.x, v1.location.y],
v0.bulge,
);
out.push(diamond_grip(n + i, Vec3::new(mid[0] as f32, mid[1] as f32, elev)));
out.push(diamond_grip(
n + i,
Vec3::new(mid[0] as f32, mid[1] as f32, elev),
));
}
out
}
@ -382,7 +443,11 @@ fn apply_grip(pline: &mut LwPolyline, grip_id: usize, apply: GripApply) {
} else {
// Arc midpoint grip for segment (grip_id - n)
let seg = grip_id - n;
let count = if pline.is_closed { n } else { n.saturating_sub(1) };
let count = if pline.is_closed {
n
} else {
n.saturating_sub(1)
};
if seg >= count {
return;
}
@ -399,7 +464,10 @@ fn apply_grip(pline: &mut LwPolyline, grip_id: usize, apply: GripApply) {
[old[0] + d.x as f64, old[1] + d.y as f64]
}
};
let p0 = [pline.vertices[seg].location.x, pline.vertices[seg].location.y];
let p0 = [
pline.vertices[seg].location.x,
pline.vertices[seg].location.y,
];
let p1 = [
pline.vertices[(seg + 1) % n].location.x,
pline.vertices[(seg + 1) % n].location.y,

View file

@ -4,9 +4,9 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{ro_prop as ro, square_grip};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::wire_model::SnapHint;
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::wire_model::SnapHint;
// ── Face3D ────────────────────────────────────────────────────────────────────
@ -125,7 +125,9 @@ impl PropertyEditable for Face3D {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"f3_p1x" => self.first_corner.x = v,
"f3_p1y" => self.first_corner.y = v,
@ -153,12 +155,7 @@ impl Transformable for Face3D {
&mut entity.third_corner,
&mut entity.fourth_corner,
] {
crate::scene::transform::reflect_xy_point(
&mut corner.x,
&mut corner.y,
p1,
p2,
);
crate::scene::transform::reflect_xy_point(&mut corner.x, &mut corner.y, p1, p2);
}
});
}
@ -174,12 +171,20 @@ impl TruckConvertible for PolygonMesh {
return None;
}
let closed_m = self.flags.contains(acadrust::entities::PolygonMeshFlags::CLOSED_M);
let closed_n = self.flags.contains(acadrust::entities::PolygonMeshFlags::CLOSED_N);
let closed_m = self
.flags
.contains(acadrust::entities::PolygonMeshFlags::CLOSED_M);
let closed_n = self
.flags
.contains(acadrust::entities::PolygonMeshFlags::CLOSED_N);
let pt = |i: usize, j: usize| -> [f32; 3] {
let v = &self.vertices[i * n + j];
[v.location.x as f32, v.location.y as f32, v.location.z as f32]
[
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
]
};
let mut pts: Vec<[f32; 3]> = Vec::new();
@ -238,7 +243,11 @@ impl Grippable for PolygonMesh {
.map(|(i, v)| {
square_grip(
i,
Vec3::new(v.location.x as f32, v.location.y as f32, v.location.z as f32),
Vec3::new(
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
),
)
})
.collect()
@ -303,7 +312,11 @@ impl TruckConvertible for PolyfaceMesh {
let get_v = |idx: i16| -> Option<[f32; 3]> {
let i = (idx.abs() as usize).checked_sub(1)?;
let v = self.vertices.get(i)?;
Some([v.location.x as f32, v.location.y as f32, v.location.z as f32])
Some([
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
])
};
let mut pts: Vec<[f32; 3]> = Vec::new();
@ -356,7 +369,11 @@ impl Grippable for PolyfaceMesh {
.map(|(i, v)| {
square_grip(
i,
Vec3::new(v.location.x as f32, v.location.y as f32, v.location.z as f32),
Vec3::new(
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
),
)
})
.collect()

View file

@ -26,7 +26,11 @@ impl TruckConvertible for MLine {
// Center spine.
for v in &self.vertices {
pts.push([v.position.x as f32, v.position.y as f32, v.position.z as f32]);
pts.push([
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
]);
}
if closed && n >= 2 {
pts.push([
@ -79,7 +83,7 @@ impl TruckConvertible for MLine {
[
[f32::NAN; 3],
[px + mx * (-half), py + my * (-half), pz + mz * (-half)],
[px + mx * half, py + my * half, pz + mz * half ],
[px + mx * half, py + my * half, pz + mz * half],
]
};
pts.extend_from_slice(&cap_v(&self.vertices[0]));
@ -90,7 +94,13 @@ impl TruckConvertible for MLine {
let key_verts: Vec<[f32; 3]> = self
.vertices
.iter()
.map(|v| [v.position.x as f32, v.position.y as f32, v.position.z as f32])
.map(|v| {
[
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
]
})
.collect();
let snap_pts = self
@ -98,7 +108,11 @@ impl TruckConvertible for MLine {
.iter()
.map(|v| {
(
Vec3::new(v.position.x as f32, v.position.y as f32, v.position.z as f32),
Vec3::new(
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
),
SnapHint::Node,
)
})
@ -122,7 +136,11 @@ impl Grippable for MLine {
.map(|(i, v)| {
square_grip(
i,
Vec3::new(v.position.x as f32, v.position.y as f32, v.position.z as f32),
Vec3::new(
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
),
)
})
.collect()
@ -174,12 +192,15 @@ impl PropertyEditable for MLine {
} else {
value == "true"
};
self.flags.set(acadrust::entities::MLineFlags::CLOSED, closed);
self.flags
.set(acadrust::entities::MLineFlags::CLOSED, closed);
return;
}
_ => {}
}
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
if field == "ml_scale" && v != 0.0 {
self.scale_factor = v;
}

View file

@ -8,24 +8,24 @@ pub mod hatch;
pub mod insert;
pub mod leader;
pub mod line;
pub mod multileader;
pub mod lwpolyline;
pub mod mesh;
pub mod mline;
pub mod mtext;
pub mod multileader;
pub mod ole2frame;
pub mod point;
pub mod polyline;
pub mod raster_image;
pub mod underlay;
pub mod ray;
pub mod shape;
pub mod solid;
pub mod solid3d;
pub mod spline;
pub mod table;
pub mod text;
pub(crate) mod text_support;
pub mod table;
pub mod tolerance;
pub mod traits;
pub mod underlay;
pub mod viewport;

View file

@ -25,12 +25,11 @@ pub(crate) fn catmull_rom_pts(ctrl: &[[f32; 3]], segs_per_span: u32) -> Vec<[f32
let t3 = t2 * t;
let mut pt = [0.0_f32; 3];
for k in 0..3 {
pt[k] = 0.5 * (
(2.0 * p1[k])
+ (-p0[k] + p2[k]) * t
+ (2.0 * p0[k] - 5.0 * p1[k] + 4.0 * p2[k] - p3[k]) * t2
+ (-p0[k] + 3.0 * p1[k] - 3.0 * p2[k] + p3[k]) * t3
);
pt[k] = 0.5
* ((2.0 * p1[k])
+ (-p0[k] + p2[k]) * t
+ (2.0 * p0[k] - 5.0 * p1[k] + 4.0 * p2[k] - p3[k]) * t2
+ (-p0[k] + 3.0 * p1[k] - 3.0 * p2[k] + p3[k]) * t3);
}
out.push(pt);
}
@ -60,16 +59,20 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
snap_pts.push(node(cp_f));
for line in &root.lines {
if line.points.is_empty() { continue; }
if line.points.is_empty() {
continue;
}
if !invisible {
if !first { points.push(nan); }
if !first {
points.push(nan);
}
first = false;
// Build the full control-point list: line.points + connection_point
let mut ctrl: Vec<[f32; 3]> = line.points.iter().map(|p| p3(p)).collect();
let last_f = *ctrl.last().unwrap_or(&cp_f);
let dist = ((last_f[0]-cp_f[0]).powi(2) + (last_f[1]-cp_f[1]).powi(2)).sqrt();
let dist = ((last_f[0] - cp_f[0]).powi(2) + (last_f[1] - cp_f[1]).powi(2)).sqrt();
if dist > 1e-9 {
ctrl.push(cp_f);
}
@ -91,7 +94,10 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
}
for i in 0..ctrl.len().saturating_sub(1) {
tangents.push(TangentGeom::Line { p1: ctrl[i], p2: ctrl[i + 1] });
tangents.push(TangentGeom::Line {
p1: ctrl[i],
p2: ctrl[i + 1],
});
}
}
@ -99,34 +105,54 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
if draw_arrow {
let tip = &line.points[0];
let tip_f = p3(tip);
let next = if line.points.len() >= 2 { line.points[1] } else { *cp };
let next = if line.points.len() >= 2 {
line.points[1]
} else {
*cp
};
let dx = (next.x - tip.x) as f32;
let dy = (next.y - tip.y) as f32;
let dl = (dx*dx + dy*dy).sqrt().max(1e-9);
let dl = (dx * dx + dy * dy).sqrt().max(1e-9);
let (dx, dy) = (dx / dl, dy / dl);
let a = std::f32::consts::PI / 6.0;
let (s, c) = a.sin_cos();
points.push(nan);
points.push([tip_f[0]+(dx*c-dy*s)*arrow_size, tip_f[1]+(dx*s+dy*c)*arrow_size, tip_f[2]]);
points.push([
tip_f[0] + (dx * c - dy * s) * arrow_size,
tip_f[1] + (dx * s + dy * c) * arrow_size,
tip_f[2],
]);
points.push(tip_f);
points.push([tip_f[0]+(dx*c+dy*s)*arrow_size, tip_f[1]+(-dx*s+dy*c)*arrow_size, tip_f[2]]);
points.push([
tip_f[0] + (dx * c + dy * s) * arrow_size,
tip_f[1] + (-dx * s + dy * c) * arrow_size,
tip_f[2],
]);
}
}
// Landing shelf at connection_point
if ml.enable_landing && ml.enable_dogleg && ml.dogleg_length > 0.0 {
let dir = &root.direction;
let dl = (dir.x*dir.x + dir.y*dir.y).sqrt().max(1e-9);
let dl = (dir.x * dir.x + dir.y * dir.y).sqrt().max(1e-9);
let d = ml.dogleg_length;
points.push(nan);
points.push(cp_f);
points.push([(cp.x + dir.x/dl*d) as f32, (cp.y + dir.y/dl*d) as f32, cp.z as f32]);
points.push([
(cp.x + dir.x / dl * d) as f32,
(cp.y + dir.y / dl * d) as f32,
cp.z as f32,
]);
}
}
// Text strokes (MText content rendered inline)
if ml.content_type == LeaderContentType::MText && !ml.context.text_string.is_empty() {
let height = if ml.context.text_height > 0.0 { ml.context.text_height } else { ml.text_height };
let height = if ml.context.text_height > 0.0 {
ml.context.text_height
} else {
ml.text_height
};
let ins = &ml.context.text_location;
let z = ins.z as f32;
snap_pts.push(node([ins.x as f32, ins.y as f32, z]));
@ -141,7 +167,9 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
&ml.context.text_string,
);
for stroke in &strokes {
if stroke.len() < 2 { continue; }
if stroke.len() < 2 {
continue;
}
points.push(nan);
for &[x, y] in stroke {
points.push([x, y, z]);
@ -149,7 +177,9 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
}
}
if points.is_empty() { return None; }
if points.is_empty() {
return None;
}
Some(TruckEntity {
object: TruckObject::Lines(points),
@ -173,7 +203,10 @@ fn grips(ml: &MultiLeader) -> Vec<GripDef> {
for root in &ml.context.leader_roots {
for line in &root.lines {
for p in &line.points {
result.push(square_grip(id, Vec3::new(p.x as f32, p.y as f32, p.z as f32)));
result.push(square_grip(
id,
Vec3::new(p.x as f32, p.y as f32, p.z as f32),
));
id += 1;
}
}
@ -181,7 +214,10 @@ fn grips(ml: &MultiLeader) -> Vec<GripDef> {
if ml.content_type == LeaderContentType::MText {
let tl = &ml.context.text_location;
result.push(diamond_grip(id, Vec3::new(tl.x as f32, tl.y as f32, tl.z as f32)));
result.push(diamond_grip(
id,
Vec3::new(tl.x as f32, tl.y as f32, tl.z as f32),
));
}
result
@ -292,8 +328,12 @@ fn properties(ml: &MultiLeader) -> PropSection {
let mut props = vec![
// Content
choice("Content Type", "content_type", content_type_str(&ml.content_type),
&["None", "MText", "Block", "Tolerance"]),
choice(
"Content Type",
"content_type",
content_type_str(&ml.content_type),
&["None", "MText", "Block", "Tolerance"],
),
Property {
label: "Text".into(),
field: "text_string",
@ -305,21 +345,49 @@ fn properties(ml: &MultiLeader) -> PropSection {
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"]),
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),
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"]),
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",
],
),
// Stats
ro("Leader Pts", "total_pts", total_pts.to_string()),
ro("Roots", "root_count", ctx.leader_roots.len().to_string()),
@ -332,7 +400,10 @@ fn properties(ml: &MultiLeader) -> PropSection {
props.push(edit("Root Conn Z", "conn_z", root.connection_point.z));
}
PropSection { title: "Geometry".into(), props }
PropSection {
title: "Geometry".into(),
props,
}
}
fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) {
@ -349,12 +420,33 @@ fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) {
}
"text_string" => ml.context.text_string = value.to_string(),
"text_height" => {
if let Some(v) = f64(value) { ml.text_height = v; ml.context.text_height = v; }
if let Some(v) = f64(value) {
ml.text_height = v;
ml.context.text_height = v;
}
}
"text_x" => {
if let Some(v) = f64(value) {
ml.context.text_location.x = v;
}
}
"text_y" => {
if let Some(v) = f64(value) {
ml.context.text_location.y = v;
}
}
"text_z" => {
if let Some(v) = f64(value) {
ml.context.text_location.z = v;
}
}
"text_frame" => {
ml.text_frame = if value == "toggle" {
!ml.text_frame
} else {
value == "true"
}
}
"text_x" => { if let Some(v) = f64(value) { ml.context.text_location.x = v; } }
"text_y" => { if let Some(v) = f64(value) { ml.context.text_location.y = v; } }
"text_z" => { if let Some(v) = f64(value) { ml.context.text_location.z = v; } }
"text_frame" => ml.text_frame = if value == "toggle" { !ml.text_frame } else { value == "true" },
"path_type" => {
ml.path_type = match value {
"Spline" => MultiLeaderPathType::Spline,
@ -362,12 +454,42 @@ fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) {
_ => MultiLeaderPathType::StraightLineSegments,
};
}
"enable_landing" => ml.enable_landing = if value == "toggle" { !ml.enable_landing } else { value == "true" },
"enable_dogleg" => ml.enable_dogleg = if value == "toggle" { !ml.enable_dogleg } else { value == "true" },
"enable_annotation_scale" => ml.enable_annotation_scale = if value == "toggle" { !ml.enable_annotation_scale } else { value == "true" },
"dogleg_length" => { if let Some(v) = f64(value) { ml.dogleg_length = v; } }
"arrowhead_size" => { if let Some(v) = f64(value) { ml.arrowhead_size = v; } }
"scale_factor" => { if let Some(v) = f64(value) { ml.scale_factor = v; } }
"enable_landing" => {
ml.enable_landing = if value == "toggle" {
!ml.enable_landing
} else {
value == "true"
}
}
"enable_dogleg" => {
ml.enable_dogleg = if value == "toggle" {
!ml.enable_dogleg
} else {
value == "true"
}
}
"enable_annotation_scale" => {
ml.enable_annotation_scale = if value == "toggle" {
!ml.enable_annotation_scale
} else {
value == "true"
}
}
"dogleg_length" => {
if let Some(v) = f64(value) {
ml.dogleg_length = v;
}
}
"arrowhead_size" => {
if let Some(v) = f64(value) {
ml.arrowhead_size = v;
}
}
"scale_factor" => {
if let Some(v) = f64(value) {
ml.scale_factor = v;
}
}
"conn_x" => {
if let (Some(v), Some(root)) = (f64(value), ml.context.leader_roots.first_mut()) {
root.connection_point.x = v;
@ -417,10 +539,18 @@ fn apply_transform(ml: &mut MultiLeader, t: &EntityTransform) {
}
}
crate::scene::transform::reflect_xy_point(
&mut root.connection_point.x, &mut root.connection_point.y, p1, p2);
&mut root.connection_point.x,
&mut root.connection_point.y,
p1,
p2,
);
}
crate::scene::transform::reflect_xy_point(
&mut entity.context.text_location.x, &mut entity.context.text_location.y, p1, p2);
&mut entity.context.text_location.x,
&mut entity.context.text_location.y,
p1,
p2,
);
});
}

View file

@ -2,7 +2,7 @@ use acadrust::entities::{Ole2Frame, OleObjectType};
use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{diamond_grip, ro_prop as ro, edit_prop as edit, square_grip};
use crate::entities::common::{diamond_grip, edit_prop as edit, ro_prop as ro, square_grip};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
@ -13,7 +13,7 @@ fn to_truck(ole: &Ole2Frame) -> TruckEntity {
let y0 = ole.lower_right_corner.y as f32;
let x1 = ole.lower_right_corner.x as f32;
let y1 = ole.upper_left_corner.y as f32;
let z = ole.upper_left_corner.z as f32;
let z = ole.upper_left_corner.z as f32;
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
let s = 0.5_f32;
@ -30,12 +30,20 @@ fn to_truck(ole: &Ole2Frame) -> TruckEntity {
let cy = (y0 + y1) * 0.5;
let pts = vec![
// Rectangle
[x0, y0, z], [x1, y0, z], [x1, y0, z], [x1, y1, z],
[x1, y1, z], [x0, y1, z], [x0, y1, z], [x0, y0, z],
[x0, y0, z],
[x1, y0, z],
[x1, y0, z],
[x1, y1, z],
[x1, y1, z],
[x0, y1, z],
[x0, y1, z],
[x0, y0, z],
// Diagonal X
[x0, y0, z], [x1, y1, z],
[x0, y0, z],
[x1, y1, z],
[f32::NAN; 3],
[x1, y0, z], [x0, y1, z],
[x1, y0, z],
[x0, y1, z],
];
let center = Vec3::new(cx, cy, z);
TruckEntity {
@ -59,7 +67,11 @@ fn grips(ole: &Ole2Frame) -> Vec<GripDef> {
ole.lower_right_corner.z as f32,
);
let center = (ul + lr) * 0.5;
vec![square_grip(0, ul), square_grip(1, lr), diamond_grip(2, center)]
vec![
square_grip(0, ul),
square_grip(1, lr),
diamond_grip(2, center),
]
}
fn properties(ole: &Ole2Frame) -> PropSection {
@ -81,7 +93,9 @@ fn properties(ole: &Ole2Frame) -> PropSection {
}
fn apply_geom_prop(ole: &mut Ole2Frame, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"ole_ulx" => ole.upper_left_corner.x = v,
"ole_uly" => ole.upper_left_corner.y = v,
@ -139,15 +153,25 @@ impl TruckConvertible for Ole2Frame {
}
impl Grippable for Ole2Frame {
fn grips(&self) -> Vec<GripDef> { grips(self) }
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { apply_grip(self, grip_id, apply); }
fn grips(&self) -> Vec<GripDef> {
grips(self)
}
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply);
}
}
impl PropertyEditable for Ole2Frame {
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection { properties(self) }
fn apply_geom_prop(&mut self, field: &str, value: &str) { apply_geom_prop(self, field, value); }
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
properties(self)
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
apply_geom_prop(self, field, value);
}
}
impl Transformable for Ole2Frame {
fn apply_transform(&mut self, t: &EntityTransform) { apply_transform(self, t); }
fn apply_transform(&mut self, t: &EntityTransform) {
apply_transform(self, t);
}
}

View file

@ -17,7 +17,13 @@ fn tessellate_polyline(pl: &Polyline) -> TruckEntity {
let pts: Vec<[f32; 3]> = pl
.vertices
.iter()
.map(|v| [v.location.x as f32, v.location.y as f32, v.location.z as f32])
.map(|v| {
[
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
]
})
.collect();
let mut points = pts.clone();
@ -49,7 +55,11 @@ impl Grippable for Polyline {
.map(|(i, v)| {
square_grip(
i,
Vec3::new(v.location.x as f32, v.location.y as f32, v.location.z as f32),
Vec3::new(
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
),
)
})
.collect()
@ -152,7 +162,11 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
let (nx, ny, nz) = normal;
let t = pl.thickness;
let off = |p: [f32; 3]| -> [f32; 3] {
[(p[0] as f64 + t * nx) as f32, (p[1] as f64 + t * ny) as f32, (p[2] as f64 + t * nz) as f32]
[
(p[0] as f64 + t * nx) as f32,
(p[1] as f64 + t * ny) as f32,
(p[2] as f64 + t * nz) as f32,
]
};
let mut path: Vec<[f32; 3]> = Vec::new();
let mut kv: Vec<[f32; 3]> = Vec::new();
@ -169,13 +183,18 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
if bulge.abs() < 1e-9 {
let (wx, wy, wz) = to_wcs(ox1, oy1);
path.push([wx as f32, wy as f32, wz as f32]);
tgs.push(TangentGeom::Line { p1: path[path.len()-2], p2: *path.last().unwrap() });
tgs.push(TangentGeom::Line {
p1: path[path.len() - 2],
p2: *path.last().unwrap(),
});
} else {
let angle = 4.0 * bulge.atan();
let dx = ox1 - ox0; let dy = oy1 - oy0;
let dx = ox1 - ox0;
let dy = oy1 - oy0;
let d = (dx * dx + dy * dy).sqrt().max(1e-12);
let r = (d / 2.0) / (angle / 2.0).sin().abs();
let mx = (ox0 + ox1) * 0.5; let my = (oy0 + oy1) * 0.5;
let mx = (ox0 + ox1) * 0.5;
let my = (oy0 + oy1) * 0.5;
let px = -dy / d;
let ss = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
@ -183,9 +202,20 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
let ocy = my + ss * (dx / d) * (r - h);
let a0 = (oy0 - ocy).atan2(ox0 - ocx);
let mut a1 = (oy1 - ocy).atan2(ox1 - ocx);
if bulge > 0.0 { if a1 < a0 { a1 += TAU; } } else { if a1 > a0 { a1 -= TAU; } }
if bulge > 0.0 {
if a1 < a0 {
a1 += TAU;
}
} else {
if a1 > a0 {
a1 -= TAU;
}
}
let (wcx, wcy, wcz) = to_wcs(ocx, ocy);
tgs.push(TangentGeom::Circle { center: [wcx as f32, wcy as f32, wcz as f32], radius: r as f32 });
tgs.push(TangentGeom::Circle {
center: [wcx as f32, wcy as f32, wcz as f32],
radius: r as f32,
});
for j in 1..=16usize {
let a = a0 + (a1 - a0) * (j as f64 / 16.0);
let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin());
@ -198,15 +228,26 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
let mut pts: Vec<[f32; 3]> = Vec::with_capacity(path.len() * 2 + kv.len() * 3 + 4);
pts.extend_from_slice(&path);
pts.push([f32::NAN; 3]);
for &p in &path { pts.push(off(p)); }
for &p in &path {
pts.push(off(p));
}
if !kv.is_empty() {
pts.push([f32::NAN; 3]);
for (i, &pb) in kv.iter().enumerate() {
pts.push(pb); pts.push(off(pb));
if i + 1 < kv.len() { pts.push([f32::NAN; 3]); }
pts.push(pb);
pts.push(off(pb));
if i + 1 < kv.len() {
pts.push([f32::NAN; 3]);
}
}
}
return TruckEntity { object: TruckObject::Lines(pts), snap_pts: vec![], tangent_geoms: tgs, key_vertices: kv, fill_tris: vec![] };
return TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![],
tangent_geoms: tgs,
key_vertices: kv,
fill_tris: vec![],
};
}
for i in 0..seg_count {
@ -289,12 +330,7 @@ impl Grippable for Polyline2D {
self.vertices
.iter()
.enumerate()
.map(|(i, v)| {
square_grip(
i,
Vec3::new(v.location.x as f32, v.location.y as f32, elev),
)
})
.map(|(i, v)| square_grip(i, Vec3::new(v.location.x as f32, v.location.y as f32, elev)))
.collect()
}
@ -341,7 +377,11 @@ impl PropertyEditable for Polyline2D {
} else {
value == "true"
};
if closed { self.close(); } else { self.flags.set_closed(false); }
if closed {
self.close();
} else {
self.flags.set_closed(false);
}
}
"pl2_elevation" => {
if let Ok(v) = value.trim().parse::<f64>() {
@ -372,14 +412,18 @@ impl Transformable for Polyline2D {
fn tessellate_polyline3d(pl: &Polyline3D) -> TruckEntity {
let to_pt = |v: &acadrust::entities::Vertex3DPolyline| -> [f32; 3] {
[v.position.x as f32, v.position.y as f32, v.position.z as f32]
[
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
]
};
// DXF vertex flags: 8 = spline-fit curve point, 16 = spline frame control point.
// When spline-fit vertices are present use them for the wire and control points for snap;
// otherwise treat all vertices uniformly.
let spline_curve: Vec<_> = pl.vertices.iter().filter(|v| v.flags & 8 != 0).collect();
let ctrl_pts: Vec<_> = pl.vertices.iter().filter(|v| v.flags & 16 != 0).collect();
let ctrl_pts: Vec<_> = pl.vertices.iter().filter(|v| v.flags & 16 != 0).collect();
let (wire_pts, key_verts) = if !spline_curve.is_empty() {
let wire: Vec<[f32; 3]> = spline_curve.iter().map(|v| to_pt(v)).collect();
@ -418,7 +462,11 @@ impl Grippable for Polyline3D {
.map(|(i, v)| {
square_grip(
i,
Vec3::new(v.position.x as f32, v.position.y as f32, v.position.z as f32),
Vec3::new(
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
),
)
})
.collect()
@ -462,8 +510,16 @@ impl PropertyEditable for Polyline3D {
fn apply_geom_prop(&mut self, field: &str, value: &str) {
if field == "pl3_closed" {
let closed = if value == "toggle" { !self.is_closed() } else { value == "true" };
if closed { self.close(); } else { self.open(); }
let closed = if value == "toggle" {
!self.is_closed()
} else {
value == "true"
};
if closed {
self.close();
} else {
self.open();
}
}
}
}

View file

@ -57,13 +57,7 @@ fn image_wire(corners: [[f32; 3]; 4], with_x: bool) -> Vec<[f32; 3]> {
pts
}
fn reflect_vec3(
vx: &mut f64,
vy: &mut f64,
ax: f64,
ay: f64,
len2: f64,
) {
fn reflect_vec3(vx: &mut f64, vy: &mut f64, ax: f64, ay: f64, len2: f64) {
let dot = *vx * ax + *vy * ay;
*vx = 2.0 * dot * ax / len2 - *vx;
*vy = 2.0 * dot * ay / len2 - *vy;
@ -97,11 +91,8 @@ impl TruckConvertible for RasterImage {
let cb = &self.clip_boundary;
match cb.clip_type {
acadrust::entities::ClipType::Polygonal if cb.vertices.len() >= 3 => {
let mut poly: Vec<[f32; 3]> = cb
.vertices
.iter()
.map(|v| px_to_world(v.x, v.y))
.collect();
let mut poly: Vec<[f32; 3]> =
cb.vertices.iter().map(|v| px_to_world(v.x, v.y)).collect();
if let Some(&first) = poly.first() {
poly.push(first);
}
@ -198,13 +189,18 @@ impl PropertyEditable for RasterImage {
fn apply_geom_prop(&mut self, field: &str, value: &str) {
match field {
"ri_clip" => {
self.clipping_enabled =
if value == "toggle" { !self.clipping_enabled } else { value == "true" };
self.clipping_enabled = if value == "toggle" {
!self.clipping_enabled
} else {
value == "true"
};
return;
}
_ => {}
}
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"ri_ox" => self.insertion_point.x = v,
"ri_oy" => self.insertion_point.y = v,
@ -255,8 +251,7 @@ impl TruckConvertible for Wipeout {
&& matches!(
self.clip_type,
acadrust::entities::WipeoutClipType::Polygonal
)
{
) {
// Convert pixel-space boundary vertices to world space:
// world = insertion_point + u_vector * v.x * size.x + v_vector * v.y * size.y
let ox = self.insertion_point.x as f32;
@ -358,13 +353,18 @@ impl Grippable for Wipeout {
let ox = self.insertion_point.x;
let oy = self.insertion_point.y;
let oz = self.insertion_point.z;
let cur_wx = ox + self.u_vector.x * v.x * self.size.x + self.v_vector.x * v.y * self.size.y;
let cur_wy = oy + self.u_vector.y * v.x * self.size.x + self.v_vector.y * v.y * self.size.y;
let cur_wz = oz + self.u_vector.z * v.x * self.size.x + self.v_vector.z * v.y * self.size.y;
let cur_wx =
ox + self.u_vector.x * v.x * self.size.x + self.v_vector.x * v.y * self.size.y;
let cur_wy =
oy + self.u_vector.y * v.x * self.size.x + self.v_vector.y * v.y * self.size.y;
let cur_wz =
oz + self.u_vector.z * v.x * self.size.x + self.v_vector.z * v.y * self.size.y;
let new_w = match apply {
GripApply::Translate(d) => {
[cur_wx + d.x as f64, cur_wy + d.y as f64, cur_wz + d.z as f64]
}
GripApply::Translate(d) => [
cur_wx + d.x as f64,
cur_wy + d.y as f64,
cur_wz + d.z as f64,
],
GripApply::Absolute(p) => [p.x as f64, p.y as f64, p.z as f64],
};
// Back-project: solve for pixel coords using u_vector and v_vector.
@ -424,13 +424,18 @@ impl PropertyEditable for Wipeout {
fn apply_geom_prop(&mut self, field: &str, value: &str) {
match field {
"wo_clip" => {
self.clipping_enabled =
if value == "toggle" { !self.clipping_enabled } else { value == "true" };
self.clipping_enabled = if value == "toggle" {
!self.clipping_enabled
} else {
value == "true"
};
return;
}
_ => {}
}
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"wo_ox" => self.insertion_point.x = v,
"wo_oy" => self.insertion_point.y = v,

View file

@ -104,14 +104,22 @@ impl PropertyEditable for Ray {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"ray_bx" => self.base_point.x = v,
"ray_by" => self.base_point.y = v,
"ray_bz" => self.base_point.z = v,
"ray_dx" => { self.direction.x = v; }
"ray_dy" => { self.direction.y = v; }
"ray_dz" => { self.direction.z = v; }
"ray_dx" => {
self.direction.x = v;
}
"ray_dy" => {
self.direction.y = v;
}
"ray_dz" => {
self.direction.z = v;
}
_ => {}
}
}
@ -233,14 +241,22 @@ impl PropertyEditable for XLine {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"xl_bx" => self.base_point.x = v,
"xl_by" => self.base_point.y = v,
"xl_bz" => self.base_point.z = v,
"xl_dx" => { self.direction.x = v; }
"xl_dy" => { self.direction.y = v; }
"xl_dz" => { self.direction.z = v; }
"xl_dx" => {
self.direction.x = v;
}
"xl_dy" => {
self.direction.y = v;
}
"xl_dz" => {
self.direction.z = v;
}
_ => {}
}
}

View file

@ -8,12 +8,12 @@ use acadrust::entities::Shape;
use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{ro_prop as ro, 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::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::wire_model::SnapHint;
use crate::scene::transform;
use crate::scene::wire_model::SnapHint;
// ── Marker geometry ───────────────────────────────────────────────────────────
@ -21,11 +21,11 @@ use crate::scene::transform;
fn shape_marker(ox: f32, oy: f32, oz: f32, size: f32) -> Vec<[f32; 3]> {
let s = size * 0.5;
vec![
[ox, oy + s, oz], // top
[ox + s, oy, oz], // right
[ox, oy - s, oz], // bottom
[ox - s, oy, oz], // left
[ox, oy + s, oz], // close
[ox, oy + s, oz], // top
[ox + s, oy, oz], // right
[ox, oy - s, oz], // bottom
[ox - s, oy, oz], // left
[ox, oy + s, oz], // close
[f32::NAN; 3],
]
}
@ -91,24 +91,26 @@ impl PropertyEditable for Shape {
PropSection {
title: "Geometry".into(),
props: vec![
ro("Name", "shp_name", self.shape_name.clone()),
ro("Style", "shp_style", self.style_name.clone()),
edit("Insert X", "shp_ix", self.insertion_point.x),
edit("Insert Y", "shp_iy", self.insertion_point.y),
edit("Insert Z", "shp_iz", self.insertion_point.z),
edit("Size", "shp_sz", self.size),
edit("Rotation","shp_rot", self.rotation.to_degrees()),
ro("Name", "shp_name", self.shape_name.clone()),
ro("Style", "shp_style", self.style_name.clone()),
edit("Insert X", "shp_ix", self.insertion_point.x),
edit("Insert Y", "shp_iy", self.insertion_point.y),
edit("Insert Z", "shp_iz", self.insertion_point.z),
edit("Size", "shp_sz", self.size),
edit("Rotation", "shp_rot", self.rotation.to_degrees()),
],
}
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"shp_ix" => self.insertion_point.x = v,
"shp_iy" => self.insertion_point.y = v,
"shp_iz" => self.insertion_point.z = v,
"shp_sz" => self.size = v.max(0.001),
"shp_ix" => self.insertion_point.x = v,
"shp_iy" => self.insertion_point.y = v,
"shp_iz" => self.insertion_point.z = v,
"shp_sz" => self.size = v.max(0.001),
"shp_rot" => self.rotation = v.to_radians(),
_ => {}
}

View file

@ -29,10 +29,17 @@ impl TruckConvertible for Solid {
// AutoCAD stores corners in "Z" order: p0-p1 are top edge, p2-p3 are bottom edge,
// so the visual quad is p0→p1→p3→p2→p0.
let pts = vec![
p0, p1, [f32::NAN; 3],
p1, p3, [f32::NAN; 3],
p3, p2, [f32::NAN; 3],
p2, p0,
p0,
p1,
[f32::NAN; 3],
p1,
p3,
[f32::NAN; 3],
p3,
p2,
[f32::NAN; 3],
p2,
p0,
];
let snap = vec![
@ -108,7 +115,9 @@ impl PropertyEditable for Solid {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"sl_p1x" => self.first_corner.x = v,
"sl_p1y" => self.first_corner.y = v,
@ -137,12 +146,7 @@ impl Transformable for Solid {
&mut entity.third_corner,
&mut entity.fourth_corner,
] {
crate::scene::transform::reflect_xy_point(
&mut corner.x,
&mut corner.y,
p1,
p2,
);
crate::scene::transform::reflect_xy_point(&mut corner.x, &mut corner.y, p1, p2);
}
});
}

View file

@ -71,9 +71,21 @@ impl PropertyEditable for Solid3D {
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(
"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),
],
}
@ -113,9 +125,21 @@ impl PropertyEditable for Region {
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(
"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),
],
}
@ -155,9 +179,21 @@ impl PropertyEditable for Body {
PropSection {
title: "Geometry".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(
"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),
],
}

View file

@ -52,7 +52,11 @@ fn to_truck(spl: &Spline) -> TruckEntity {
.collect();
let bspline = BSplineCurve::new(knot_vec, ctrl_pts);
let (t0, t1) = bspline.range_tuple();
(bspline.subs(t0), bspline.subs(t1), Curve::BSplineCurve(bspline))
(
bspline.subs(t0),
bspline.subs(t1),
Curve::BSplineCurve(bspline),
)
};
let snap_source = if !spl.fit_points.is_empty() {

View file

@ -21,7 +21,11 @@ impl TruckConvertible for Table {
let origin = v3(&self.insertion_point);
let h_raw = v3(&self.horizontal_direction);
let h = if h_raw.length_squared() > 1e-10 { h_raw.normalize() } else { Vec3::X };
let h = if h_raw.length_squared() > 1e-10 {
h_raw.normalize()
} else {
Vec3::X
};
// Perpendicular "down" direction in the drawing plane (tables grow downward)
let v_down = Vec3::new(h.y, -h.x, 0.0);
@ -58,13 +62,13 @@ impl TruckConvertible for Table {
};
for &ry in &row_offsets {
let left = origin + h * 0.0 + v_down * ry;
let left = origin + h * 0.0 + v_down * ry;
let right = origin + h * total_w + v_down * ry;
add_seg(left, right);
}
for &cx in &col_offsets {
let top = origin + h * cx + v_down * 0.0;
let top = origin + h * cx + v_down * 0.0;
let bottom = origin + h * cx + v_down * total_h;
add_seg(top, bottom);
}
@ -75,7 +79,10 @@ impl TruckConvertible for Table {
for (ri, row) in self.rows.iter().enumerate() {
let row_top = row_offsets[ri];
let row_bot = row_offsets.get(ri + 1).copied().unwrap_or(row_top + row.height as f32);
let row_bot = row_offsets
.get(ri + 1)
.copied()
.unwrap_or(row_top + row.height as f32);
let row_mid = (row_top + row_bot) * 0.5;
for (ci, cell) in row.cells.iter().enumerate() {
@ -84,7 +91,7 @@ impl TruckConvertible for Table {
continue;
}
let col_left = col_offsets[ci];
let col_left = col_offsets[ci];
let col_width = self.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0);
let col_right = col_left + col_width;
@ -95,19 +102,19 @@ impl TruckConvertible for Table {
// 0/default = MiddleCenter (5)
let align = cell.style.as_ref().map_or(5, |s| s.alignment);
let horiz = ((align - 1).rem_euclid(3)) + 1; // 1=left, 2=center, 3=right
let vert = ((align - 1) / 3) + 1; // 1=top, 2=middle, 3=bottom
let vert = ((align - 1) / 3) + 1; // 1=top, 2=middle, 3=bottom
let text_w = cxf::measure_text(text, text_height, 1.0, "txt");
let x_offset = match horiz {
1 => col_left + margin, // left
3 => col_right - margin - text_w, // right
_ => col_left + (col_width - text_w) * 0.5, // center (default)
1 => col_left + margin, // left
3 => col_right - margin - text_w, // right
_ => col_left + (col_width - text_w) * 0.5, // center (default)
};
let y_offset = match vert {
1 => row_top + margin, // top
3 => row_bot - margin - text_height, // bottom
_ => row_mid - text_height * 0.5, // middle (default)
1 => row_top + margin, // top
3 => row_bot - margin - text_height, // bottom
_ => row_mid - text_height * 0.5, // middle (default)
};
let text_origin = origin + h * x_offset + v_down * y_offset;
@ -170,11 +177,23 @@ impl PropertyEditable for Table {
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("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),
),
],
}
}

View file

@ -3,7 +3,9 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, parse_f64, square_grip};
use crate::entities::text_support::{resolve_dxf_special_chars, resolve_text_style, text_local_bounds};
use crate::entities::text_support::{
resolve_dxf_special_chars, resolve_text_style, text_local_bounds,
};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
use crate::scene::cxf;
@ -49,17 +51,28 @@ fn sync_text_alignment_point(t: &mut Text) {
fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
let normal = (t.normal.x, t.normal.y, t.normal.z);
let (wsx, wsy, wsz) = crate::scene::transform::ocs_point_to_wcs(
(t.insertion_point.x, t.insertion_point.y, t.insertion_point.z),
(
t.insertion_point.x,
t.insertion_point.y,
t.insertion_point.z,
),
normal,
);
let snap_pt = Vec3::new(wsx as f32, wsy as f32, wsz as f32);
let resolved_style = resolve_text_style(&t.style, document);
let font_name = resolved_style.font_name;
let base_wf = (if t.width_factor > 0.0 { t.width_factor as f32 } else { 1.0 }
* resolved_style.width_factor.max(0.01))
let base_wf = (if t.width_factor > 0.0 {
t.width_factor as f32
} else {
1.0
} * resolved_style.width_factor.max(0.01))
.clamp(0.01, 100.0);
// is_backward mirrors text left-right via negative width factor.
let width_factor = if resolved_style.is_backward { -base_wf } else { base_wf };
let width_factor = if resolved_style.is_backward {
-base_wf
} else {
base_wf
};
// is_upside_down rotates 180° around the insertion point.
let rotation = if resolved_style.is_upside_down {
t.rotation as f32 + std::f32::consts::PI

View file

@ -125,16 +125,32 @@ pub fn resolve_dxf_special_chars(s: &str) -> String {
}
chars.next(); // consume second '%'
match chars.peek().map(|c| c.to_ascii_lowercase()) {
Some('d') => { chars.next(); out.push('°'); }
Some('p') => { chars.next(); out.push('±'); }
Some('c') => { chars.next(); out.push('⌀'); }
Some('u') | Some('o') => { chars.next(); } // toggle codes — strip silently
Some('%') => { chars.next(); out.push('%'); }
Some('d') => {
chars.next();
out.push('°');
}
Some('p') => {
chars.next();
out.push('±');
}
Some('c') => {
chars.next();
out.push('⌀');
}
Some('u') | Some('o') => {
chars.next();
} // toggle codes — strip silently
Some('%') => {
chars.next();
out.push('%');
}
Some(d) if d.is_ascii_digit() => {
let mut digits = String::with_capacity(3);
for _ in 0..3 {
match chars.peek() {
Some(&ch) if ch.is_ascii_digit() => { digits.push(chars.next().unwrap()); }
Some(&ch) if ch.is_ascii_digit() => {
digits.push(chars.next().unwrap());
}
_ => break,
}
}
@ -150,7 +166,10 @@ pub fn resolve_dxf_special_chars(s: &str) -> String {
out.push('%');
out.push_str(&digits);
}
_ => { out.push('%'); out.push('%'); }
_ => {
out.push('%');
out.push('%');
}
}
}
@ -208,7 +227,9 @@ pub fn strip_mtext_codes(s: &str) -> String {
} else {
// \U or \u without '+' — strip until semicolon
for c in chars.by_ref() {
if c == ';' { break; }
if c == ';' {
break;
}
}
}
}
@ -222,7 +243,9 @@ pub fn strip_mtext_codes(s: &str) -> String {
let mut sep = '/';
let mut in_lower = false;
for c in chars.by_ref() {
if c == ';' { break; }
if c == ';' {
break;
}
if !in_lower && (c == '/' || c == '^' || c == '#') {
sep = c;
in_lower = true;
@ -261,12 +284,16 @@ pub fn strip_mtext_codes(s: &str) -> String {
Some(c) if "pHWQTACcfFMX".contains(c) => {
chars.next();
for c in chars.by_ref() {
if c == ';' { break; }
if c == ';' {
break;
}
}
}
// Unknown escape — consume and silently discard the code character so
// it does not appear as a literal in the output.
Some(_) => { chars.next(); }
Some(_) => {
chars.next();
}
None => {}
},
// Strip brace grouping markers (scope delimiters for in-line formatting)
@ -280,13 +307,15 @@ pub fn strip_mtext_codes(s: &str) -> String {
}
pub fn split_mtext_lines(s: &str) -> Vec<String> {
let lines: Vec<String> = s.split('\n')
.map(|l| l.trim().to_string())
.collect();
let lines: Vec<String> = s.split('\n').map(|l| l.trim().to_string()).collect();
// Drop leading and trailing blank lines, but preserve blank lines in the
// middle — they are intentional paragraph separators (\\P\\P in MTEXT).
let start = lines.iter().position(|l| !l.is_empty()).unwrap_or(0);
let end = lines.iter().rposition(|l| !l.is_empty()).map(|i| i + 1).unwrap_or(0);
let end = lines
.iter()
.rposition(|l| !l.is_empty())
.map(|i| i + 1)
.unwrap_or(0);
lines[start..end].to_vec()
}

View file

@ -40,13 +40,20 @@ fn substitute_gdt_codes(s: &str) -> String {
let mut depth = 1usize;
for c in chars.by_ref() {
match c {
'{' => { depth += 1; inner.push(c); }
'}' => {
depth -= 1;
if depth == 0 { break; }
'{' => {
depth += 1;
inner.push(c);
}
'}' => {
depth -= 1;
if depth == 0 {
break;
}
inner.push(c);
}
_ => {
inner.push(c);
}
_ => { inner.push(c); }
}
}
// Is it a GDT font switch?
@ -67,27 +74,27 @@ fn substitute_gdt_codes(s: &str) -> String {
/// Map a GDT font character to a short ASCII approximation.
fn gdt_char_to_ascii(c: char) -> &'static str {
match c {
'a' => "SRT", // Straightness
'b' => "FLT", // Flatness
'c' => "FLT", // Flatness
'd' => "PSF", // Profile of Surface
'e' => "CYL", // Cylindricity
'f' => "PRL", // Profile of Line
'g' => "CIR", // Circularity
'h' => "PAR", // Parallelism
'i' => "SYM", // Symmetry
'j' => "PRP", // Perpendicularity
'k' => "PLN", // Profile of Line
'l' => "(L)", // LMC
'm' => "(M)", // MMC / Diameter
'n' => "ANG", // Angularity
'o' => "(o)", // at maximum material boundary
'p' => "POS", // Position
'a' => "SRT", // Straightness
'b' => "FLT", // Flatness
'c' => "FLT", // Flatness
'd' => "PSF", // Profile of Surface
'e' => "CYL", // Cylindricity
'f' => "PRL", // Profile of Line
'g' => "CIR", // Circularity
'h' => "PAR", // Parallelism
'i' => "SYM", // Symmetry
'j' => "PRP", // Perpendicularity
'k' => "PLN", // Profile of Line
'l' => "(L)", // LMC
'm' => "(M)", // MMC / Diameter
'n' => "ANG", // Angularity
'o' => "(o)", // at maximum material boundary
'p' => "POS", // Position
'q' => "(q)",
'r' => "RUN", // Circular Runout
's' => "(S)", // RFS / Regardless of Feature Size
't' => "TRN", // Total Runout
'u' => "CON", // Concentricity
'r' => "RUN", // Circular Runout
's' => "(S)", // RFS / Regardless of Feature Size
't' => "TRN", // Total Runout
'u' => "CON", // Concentricity
'v' => "(v)",
'w' => "(w)",
_ => "?",
@ -118,7 +125,11 @@ fn tessellate_tolerance(tol: &Tolerance) -> Vec<Vec<[f32; 2]>> {
}
// ── Metrics ──────────────────────────────────────────────────────────
let h = if tol.text_height > 1e-6 { tol.text_height as f32 } else { 2.5_f32 };
let h = if tol.text_height > 1e-6 {
tol.text_height as f32
} else {
2.5_f32
};
let gap = (h * 0.35).max(0.1);
let cell_h = h + 2.0 * gap;
let char_w = h * 0.65;
@ -143,9 +154,7 @@ fn tessellate_tolerance(tol: &Tolerance) -> Vec<Vec<[f32; 2]>> {
let (sa, ca) = angle.sin_cos();
// Rotate only; origin is kept as f64 and applied later with full precision.
let rot = |x: f32, y: f32| -> [f32; 2] {
[x * ca - y * sa, x * sa + y * ca]
};
let rot = |x: f32, y: f32| -> [f32; 2] { [x * ca - y * sa, x * sa + y * ca] };
let mut out: Vec<Vec<[f32; 2]>> = Vec::new();
@ -182,20 +191,17 @@ fn tessellate_tolerance(tol: &Tolerance) -> Vec<Vec<[f32; 2]>> {
let row_y = cell_h * ri as f32 + gap;
let mut cell_x = 0.0_f32;
for (ci, cell) in row.iter().enumerate() {
let cw = if ci < col_widths.len() { col_widths[ci] } else { 0.0 };
let cw = if ci < col_widths.len() {
col_widths[ci]
} else {
0.0
};
if !cell.is_empty() {
let text_w = cell.len() as f32 * char_w;
let tx = cell_x + (cw - text_w) * 0.5;
// Tessellate text in local frame then transform
let local_strokes = cxf::tessellate_text_ex(
[0.0, 0.0],
h,
0.0,
1.0,
0.0,
"txt",
cell,
);
let local_strokes =
cxf::tessellate_text_ex([0.0, 0.0], h, 0.0, 1.0, 0.0, "txt", cell);
for polyline in local_strokes {
let transformed: Vec<[f32; 2]> = polyline
.into_iter()
@ -289,7 +295,9 @@ impl PropertyEditable for Tolerance {
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
let Ok(v) = value.trim().parse::<f64>() else { return };
let Ok(v) = value.trim().parse::<f64>() else {
return;
};
match field {
"tol_ix" => self.insertion_point.x = v,
"tol_iy" => self.insertion_point.y = v,

View file

@ -132,82 +132,62 @@ impl EntityTypeOps for EntityType {
pline,
text_style_names,
)),
EntityType::Polyline(pl) => Some(PropertyEditable::geometry_properties(
pl,
text_style_names,
)),
EntityType::Polyline2D(pl) => Some(PropertyEditable::geometry_properties(
pl,
text_style_names,
)),
EntityType::Polyline3D(pl) => Some(PropertyEditable::geometry_properties(
pl,
text_style_names,
)),
EntityType::Ray(ray) => Some(PropertyEditable::geometry_properties(
ray,
text_style_names,
)),
EntityType::XLine(xl) => Some(PropertyEditable::geometry_properties(
xl,
text_style_names,
)),
EntityType::RasterImage(img) => Some(PropertyEditable::geometry_properties(
img,
text_style_names,
)),
EntityType::Wipeout(wo) => Some(PropertyEditable::geometry_properties(
wo,
text_style_names,
)),
EntityType::AttributeDefinition(a) => Some(PropertyEditable::geometry_properties(
a,
text_style_names,
)),
EntityType::AttributeEntity(a) => Some(PropertyEditable::geometry_properties(
a,
text_style_names,
)),
EntityType::MLine(ml) => Some(PropertyEditable::geometry_properties(
ml,
text_style_names,
)),
EntityType::Tolerance(tol) => Some(PropertyEditable::geometry_properties(
tol,
text_style_names,
)),
EntityType::Polyline(pl) => {
Some(PropertyEditable::geometry_properties(pl, text_style_names))
}
EntityType::Polyline2D(pl) => {
Some(PropertyEditable::geometry_properties(pl, text_style_names))
}
EntityType::Polyline3D(pl) => {
Some(PropertyEditable::geometry_properties(pl, text_style_names))
}
EntityType::Ray(ray) => {
Some(PropertyEditable::geometry_properties(ray, text_style_names))
}
EntityType::XLine(xl) => {
Some(PropertyEditable::geometry_properties(xl, text_style_names))
}
EntityType::RasterImage(img) => {
Some(PropertyEditable::geometry_properties(img, text_style_names))
}
EntityType::Wipeout(wo) => {
Some(PropertyEditable::geometry_properties(wo, text_style_names))
}
EntityType::AttributeDefinition(a) => {
Some(PropertyEditable::geometry_properties(a, text_style_names))
}
EntityType::AttributeEntity(a) => {
Some(PropertyEditable::geometry_properties(a, text_style_names))
}
EntityType::MLine(ml) => {
Some(PropertyEditable::geometry_properties(ml, text_style_names))
}
EntityType::Tolerance(tol) => {
Some(PropertyEditable::geometry_properties(tol, text_style_names))
}
EntityType::Solid(solid) => Some(PropertyEditable::geometry_properties(
solid,
text_style_names,
)),
EntityType::Solid3D(s) => Some(PropertyEditable::geometry_properties(
s,
text_style_names,
)),
EntityType::Region(r) => Some(PropertyEditable::geometry_properties(
r,
text_style_names,
)),
EntityType::Body(b) => Some(PropertyEditable::geometry_properties(
b,
text_style_names,
)),
EntityType::Face3D(f) => Some(PropertyEditable::geometry_properties(
f,
text_style_names,
)),
EntityType::PolygonMesh(pm) => Some(PropertyEditable::geometry_properties(
pm,
text_style_names,
)),
EntityType::PolyfaceMesh(pfm) => Some(PropertyEditable::geometry_properties(
pfm,
text_style_names,
)),
EntityType::Table(tbl) => Some(PropertyEditable::geometry_properties(
tbl,
text_style_names,
)),
EntityType::Solid3D(s) => {
Some(PropertyEditable::geometry_properties(s, text_style_names))
}
EntityType::Region(r) => {
Some(PropertyEditable::geometry_properties(r, text_style_names))
}
EntityType::Body(b) => Some(PropertyEditable::geometry_properties(b, text_style_names)),
EntityType::Face3D(f) => {
Some(PropertyEditable::geometry_properties(f, text_style_names))
}
EntityType::PolygonMesh(pm) => {
Some(PropertyEditable::geometry_properties(pm, text_style_names))
}
EntityType::PolyfaceMesh(pfm) => {
Some(PropertyEditable::geometry_properties(pfm, text_style_names))
}
EntityType::Table(tbl) => {
Some(PropertyEditable::geometry_properties(tbl, text_style_names))
}
EntityType::Hatch(hatch) => Some(PropertyEditable::geometry_properties(
hatch,
text_style_names,
@ -233,30 +213,25 @@ impl EntityTypeOps for EntityType {
EntityType::Insert(ins) => {
Some(PropertyEditable::geometry_properties(ins, text_style_names))
}
EntityType::Dimension(dim) => Some(PropertyEditable::geometry_properties(
dim,
text_style_names,
)),
EntityType::Dimension(dim) => {
Some(PropertyEditable::geometry_properties(dim, text_style_names))
}
EntityType::Leader(leader) => Some(PropertyEditable::geometry_properties(
leader,
text_style_names,
)),
EntityType::MultiLeader(ml) => Some(PropertyEditable::geometry_properties(
ml,
text_style_names,
)),
EntityType::Underlay(ul) => Some(PropertyEditable::geometry_properties(
ul,
text_style_names,
)),
EntityType::Shape(shp) => Some(PropertyEditable::geometry_properties(
shp,
text_style_names,
)),
EntityType::Ole2Frame(ole) => Some(PropertyEditable::geometry_properties(
ole,
text_style_names,
)),
EntityType::MultiLeader(ml) => {
Some(PropertyEditable::geometry_properties(ml, text_style_names))
}
EntityType::Underlay(ul) => {
Some(PropertyEditable::geometry_properties(ul, text_style_names))
}
EntityType::Shape(shp) => {
Some(PropertyEditable::geometry_properties(shp, text_style_names))
}
EntityType::Ole2Frame(ole) => {
Some(PropertyEditable::geometry_properties(ole, text_style_names))
}
_ => None,
}
}
@ -277,7 +252,9 @@ impl EntityTypeOps for EntityType {
EntityType::XLine(xl) => PropertyEditable::apply_geom_prop(xl, field, value),
EntityType::RasterImage(img) => PropertyEditable::apply_geom_prop(img, field, value),
EntityType::Wipeout(wo) => PropertyEditable::apply_geom_prop(wo, field, value),
EntityType::AttributeDefinition(a) => PropertyEditable::apply_geom_prop(a, field, value),
EntityType::AttributeDefinition(a) => {
PropertyEditable::apply_geom_prop(a, field, value)
}
EntityType::AttributeEntity(a) => PropertyEditable::apply_geom_prop(a, field, value),
EntityType::MLine(ml) => PropertyEditable::apply_geom_prop(ml, field, value),
EntityType::Tolerance(tol) => PropertyEditable::apply_geom_prop(tol, field, value),

View file

@ -12,15 +12,15 @@ const STANDARD_SCALES: &[(&str, f64)] = &[
("1:500", 0.002),
("1:200", 0.005),
("1:100", 0.01),
("1:50", 0.02),
("1:20", 0.05),
("1:10", 0.1),
("1:5", 0.2),
("1:2", 0.5),
("1:1", 1.0),
("2:1", 2.0),
("5:1", 5.0),
("10:1", 10.0),
("1:50", 0.02),
("1:20", 0.05),
("1:10", 0.1),
("1:5", 0.2),
("1:2", 0.5),
("1:1", 1.0),
("2:1", 2.0),
("5:1", 5.0),
("10:1", 10.0),
];
fn scale_label(scale: f64) -> String {
@ -29,24 +29,35 @@ fn scale_label(scale: f64) -> String {
return label.to_string();
}
}
format!("{:.6}", scale).trim_end_matches('0').trim_end_matches('.').to_string()
format!("{:.6}", scale)
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
}
// ── Render mode options ───────────────────────────────────────────────────
const RENDER_MODES: &[(&str, ViewportRenderMode)] = &[
("2D Wireframe", ViewportRenderMode::Wireframe2D),
("3D Wireframe", ViewportRenderMode::Wireframe3D),
("Hidden Line", ViewportRenderMode::HiddenLine),
("Flat Shaded", ViewportRenderMode::FlatShaded),
("Gouraud Shaded", ViewportRenderMode::GouraudShaded),
("Flat Shaded + Edges", ViewportRenderMode::FlatShadedWithEdges),
("Gouraud Shaded + Edges", ViewportRenderMode::GouraudShadedWithEdges),
("2D Wireframe", ViewportRenderMode::Wireframe2D),
("3D Wireframe", ViewportRenderMode::Wireframe3D),
("Hidden Line", ViewportRenderMode::HiddenLine),
("Flat Shaded", ViewportRenderMode::FlatShaded),
("Gouraud Shaded", ViewportRenderMode::GouraudShaded),
(
"Flat Shaded + Edges",
ViewportRenderMode::FlatShadedWithEdges,
),
(
"Gouraud Shaded + Edges",
ViewportRenderMode::GouraudShadedWithEdges,
),
];
fn render_mode_label(mode: &ViewportRenderMode) -> &'static str {
for (label, m) in RENDER_MODES {
if m == mode { return label; }
if m == mode {
return label;
}
}
"2D Wireframe"
}
@ -56,14 +67,25 @@ fn render_mode_label(mode: &ViewportRenderMode) -> &'static str {
const SHADE_PLOT_LABELS: &[&str] = &["As Displayed", "Wireframe", "Hidden", "Rendered"];
fn shade_plot_label(mode: i16) -> &'static str {
SHADE_PLOT_LABELS.get(mode as usize).copied().unwrap_or("As Displayed")
SHADE_PLOT_LABELS
.get(mode as usize)
.copied()
.unwrap_or("As Displayed")
}
// ── Standard view options ─────────────────────────────────────────────────
const STD_VIEWS: &[&str] = &[
"Top", "Bottom", "Front", "Back", "Left", "Right",
"SW Isometric", "SE Isometric", "NE Isometric", "NW Isometric",
"Top",
"Bottom",
"Front",
"Back",
"Left",
"Right",
"SW Isometric",
"SE Isometric",
"NE Isometric",
"NW Isometric",
];
fn grips(vp: &Viewport) -> Vec<GripDef> {
@ -144,27 +166,42 @@ fn properties(vp: &Viewport) -> PropSection {
Property {
label: "Locked".into(),
field: "vp_locked",
value: PropValue::BoolToggle { field: "vp_locked", value: vp.status.locked },
value: PropValue::BoolToggle {
field: "vp_locked",
value: vp.status.locked,
},
},
Property {
label: "On".into(),
field: "vp_on",
value: PropValue::BoolToggle { 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 },
value: PropValue::BoolToggle {
field: "vp_perspective",
value: vp.status.perspective,
},
},
Property {
label: "Hide Plot".into(),
field: "vp_hide_plot",
value: PropValue::BoolToggle { field: "vp_hide_plot", value: vp.status.hide_plot },
value: PropValue::BoolToggle {
field: "vp_hide_plot",
value: vp.status.hide_plot,
},
},
Property {
label: "UCS Icon".into(),
field: "vp_ucs_icon",
value: PropValue::BoolToggle { field: "vp_ucs_icon", value: vp.ucs_icon_visible },
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),
@ -174,7 +211,10 @@ fn properties(vp: &Viewport) -> PropSection {
Property {
label: "UCS Per Viewport".into(),
field: "vp_ucs_per_vp",
value: PropValue::BoolToggle { 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),
@ -197,16 +237,36 @@ fn viewport_view_label(vp: &Viewport) -> String {
let dz = d.z;
// Use a simple threshold comparison to classify the view direction.
if dx.abs() < 0.1 && dy.abs() < 0.1 && dz > 0.5 { return "Top".into(); }
if dx.abs() < 0.1 && dy.abs() < 0.1 && dz < -0.5 { return "Bottom".into(); }
if dx.abs() < 0.1 && dy < -0.5 && dz.abs() < 0.1 { return "Front".into(); }
if dx.abs() < 0.1 && dy > 0.5 && dz.abs() < 0.1 { return "Back".into(); }
if dx < -0.5 && dy.abs() < 0.1 && dz.abs() < 0.1 { return "Left".into(); }
if dx > 0.5 && dy.abs() < 0.1 && dz.abs() < 0.1 { return "Right".into(); }
if dx < -0.4 && dy < -0.4 && dz > 0.4 { return "SW Isometric".into(); }
if dx > 0.4 && dy < -0.4 && dz > 0.4 { return "SE Isometric".into(); }
if dx > 0.4 && dy > 0.4 && dz > 0.4 { return "NE Isometric".into(); }
if dx < -0.4 && dy > 0.4 && dz > 0.4 { return "NW Isometric".into(); }
if dx.abs() < 0.1 && dy.abs() < 0.1 && dz > 0.5 {
return "Top".into();
}
if dx.abs() < 0.1 && dy.abs() < 0.1 && dz < -0.5 {
return "Bottom".into();
}
if dx.abs() < 0.1 && dy < -0.5 && dz.abs() < 0.1 {
return "Front".into();
}
if dx.abs() < 0.1 && dy > 0.5 && dz.abs() < 0.1 {
return "Back".into();
}
if dx < -0.5 && dy.abs() < 0.1 && dz.abs() < 0.1 {
return "Left".into();
}
if dx > 0.5 && dy.abs() < 0.1 && dz.abs() < 0.1 {
return "Right".into();
}
if dx < -0.4 && dy < -0.4 && dz > 0.4 {
return "SW Isometric".into();
}
if dx > 0.4 && dy < -0.4 && dz > 0.4 {
return "SE Isometric".into();
}
if dx > 0.4 && dy > 0.4 && dz > 0.4 {
return "NE Isometric".into();
}
if dx < -0.4 && dy > 0.4 && dz > 0.4 {
return "NW Isometric".into();
}
"Custom".into()
}
@ -216,27 +276,51 @@ fn apply_geom_prop(vp: &mut Viewport, field: &str, value: &str) {
// Boolean / toggle fields handled first (value = "toggle" or "true"/"false").
match field {
"vp_locked" => {
vp.status.locked = if value == "toggle" { !vp.status.locked } else { value == "true" };
vp.status.locked = if value == "toggle" {
!vp.status.locked
} else {
value == "true"
};
return;
}
"vp_on" => {
vp.status.is_on = if value == "toggle" { !vp.status.is_on } else { value == "true" };
vp.status.is_on = if value == "toggle" {
!vp.status.is_on
} else {
value == "true"
};
return;
}
"vp_perspective" => {
vp.status.perspective = if value == "toggle" { !vp.status.perspective } else { value == "true" };
vp.status.perspective = if value == "toggle" {
!vp.status.perspective
} else {
value == "true"
};
return;
}
"vp_hide_plot" => {
vp.status.hide_plot = if value == "toggle" { !vp.status.hide_plot } else { value == "true" };
vp.status.hide_plot = if value == "toggle" {
!vp.status.hide_plot
} else {
value == "true"
};
return;
}
"vp_ucs_icon" => {
vp.ucs_icon_visible = if value == "toggle" { !vp.ucs_icon_visible } else { value == "true" };
vp.ucs_icon_visible = if value == "toggle" {
!vp.ucs_icon_visible
} else {
value == "true"
};
return;
}
"vp_ucs_per_vp" => {
vp.ucs_per_viewport = if value == "toggle" { !vp.ucs_per_viewport } else { value == "true" };
vp.ucs_per_viewport = if value == "toggle" {
!vp.ucs_per_viewport
} else {
value == "true"
};
return;
}
_ => {}
@ -272,16 +356,16 @@ fn apply_geom_prop(vp: &mut Viewport, field: &str, value: &str) {
// Standard view direction picker.
if field == "vp_view" {
let dir: Option<(f64, f64, f64)> = match value {
"Top" => Some(( 0.0, 0.0, 1.0)),
"Bottom" => Some(( 0.0, 0.0, -1.0)),
"Front" => Some(( 0.0, -1.0, 0.0)),
"Back" => Some(( 0.0, 1.0, 0.0)),
"Left" => Some((-1.0, 0.0, 0.0)),
"Right" => Some(( 1.0, 0.0, 0.0)),
"SW Isometric" => Some((-1.0, -1.0, 1.0)),
"SE Isometric" => Some(( 1.0, -1.0, 1.0)),
"NE Isometric" => Some(( 1.0, 1.0, 1.0)),
"NW Isometric" => Some((-1.0, 1.0, 1.0)),
"Top" => Some((0.0, 0.0, 1.0)),
"Bottom" => Some((0.0, 0.0, -1.0)),
"Front" => Some((0.0, -1.0, 0.0)),
"Back" => Some((0.0, 1.0, 0.0)),
"Left" => Some((-1.0, 0.0, 0.0)),
"Right" => Some((1.0, 0.0, 0.0)),
"SW Isometric" => Some((-1.0, -1.0, 1.0)),
"SE Isometric" => Some((1.0, -1.0, 1.0)),
"NE Isometric" => Some((1.0, 1.0, 1.0)),
"NW Isometric" => Some((-1.0, 1.0, 1.0)),
_ => None,
};
if let Some((dx, dy, dz)) = dir {