cargo fmt
This commit is contained in:
parent
468cbde5e1
commit
ca07268c7c
204 changed files with 13988 additions and 5516 deletions
|
|
@ -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()),
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue