fix(grips): compute grip positions in f64 so they sit on the wire

Grip markers drew visibly offset from the entity on large-coordinate
(UTM-scale) drawings: every Grippable impl cast the f64 entity coordinates
to f32 BEFORE the world-offset subtraction, and at 1e7 magnitude an f32
holds ~0.9 drawing units of rounding error. The wires subtract the offset
in f64 first (precision-preserving), so the grips landed up to ~1 unit
away from the geometry they belong to.

GripDef.world is now DVec3: producers fill it straight from the f64
entity data, refresh_selected_grips subtracts the world offset in f64,
and only the small offset-local result is cast to f32 for screen-space
projection (grips_to_screen / find_hit_grip) and drag state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-11 00:04:37 +03:00
1 parent ed29933d87
commit 25715d1b03
33 files changed
+898 -817

No files matched your search

+6 -3
View File
@@ -437,9 +437,12 @@ impl OpenCADStudio {
let grips = dispatch::grips(entity)
.into_iter()
.map(|mut g| {
g.world.x -= wo[0] as f32;
g.world.y -= wo[1] as f32;
g.world.z -= wo[2] as f32;
// Subtract in f64: at UTM magnitudes an f32 cast before
// the offset costs ~1 unit and draws the grip off the
// wire.
g.world.x -= wo[0];
g.world.y -= wo[1];
g.world.z -= wo[2];
g
})
.collect();
+16 -9
View File
@@ -304,10 +304,13 @@ impl OpenCADStudio {
// header so the ✓ marks the right one (text/dim/mline come from
// the document header directly). DXF provides these via
// $CTABLESTYLE / $CMLEADERSTYLE; DWG leaves them at "Standard".
self.ribbon.active_table_style =
self.tabs[i].scene.document.header.current_table_style_name.clone();
self.tabs[i].active_mleader_style = self
.tabs[i]
self.ribbon.active_table_style = self.tabs[i]
.scene
.document
.header
.current_table_style_name
.clone();
self.tabs[i].active_mleader_style = self.tabs[i]
.scene
.document
.header
@@ -3838,8 +3841,8 @@ impl OpenCADStudio {
handle: popup.handle,
grip_id,
is_translate,
origin_world: g.world,
last_world: g.world,
origin_world: g.world.as_vec3(),
last_world: g.world.as_vec3(),
});
}
return Task::none();
@@ -3900,8 +3903,8 @@ impl OpenCADStudio {
handle: popup.handle,
grip_id: new_gid,
is_translate: false,
origin_world: g.world,
last_world: g.world,
origin_world: g.world.as_vec3(),
last_world: g.world.as_vec3(),
});
self.grip_add_provisional = Some((popup.handle, new_gid));
}
@@ -6746,7 +6749,11 @@ impl OpenCADStudio {
if exists {
// Staged: header field is the round-trip source of truth
// ($CMLEADERSTYLE); the ribbon/tab mirror it.
self.tabs[i].scene.document.header.current_mleader_style_name = name.clone();
self.tabs[i]
.scene
.document
.header
.current_mleader_style_name = name.clone();
self.tabs[i].active_mleader_style = name.clone();
self.ribbon.active_mleader_style = name.clone();
self.command_line
+34 -29
View File
@@ -108,20 +108,17 @@ fn angle_span(start: f32, end: f32) -> f32 {
}
fn grips(arc: &Arc) -> Vec<GripDef> {
let ctr = Vec3::new(
arc.center.x as f32,
arc.center.y as f32,
arc.center.z as f32,
);
let r = arc.radius as f32;
let ctr = glam::DVec3::new(arc.center.x, arc.center.y, arc.center.z);
let r = arc.radius;
let sa = arc.start_angle as f32;
let ea = arc.end_angle as f32;
let ma = sa + angle_span(sa, ea) * 0.5;
let ma = (sa + angle_span(sa, ea) * 0.5) as f64;
let (sa, ea) = (arc.start_angle, arc.end_angle);
vec![
center_grip(0, ctr),
square_grip(1, ctr + Vec3::new(r * sa.cos(), r * sa.sin(), 0.0)),
square_grip(2, ctr + Vec3::new(r * ea.cos(), r * ea.sin(), 0.0)),
center_grip(3, ctr + Vec3::new(r * ma.cos(), r * ma.sin(), 0.0)),
square_grip(1, ctr + glam::DVec3::new(r * sa.cos(), r * sa.sin(), 0.0)),
square_grip(2, ctr + glam::DVec3::new(r * ea.cos(), r * ea.sin(), 0.0)),
center_grip(3, ctr + glam::DVec3::new(r * ma.cos(), r * ma.sin(), 0.0)),
]
}
@@ -230,29 +227,40 @@ impl crate::entities::traits::Grippable for Arc {
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
match grip_id {
0 => vec![GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch }],
0 => vec![GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
}],
3 => vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Radius", action: GripMenuAction::Radius },
GripMenuItem { label: "Arc Length", action: GripMenuAction::ArcLength },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Radius",
action: GripMenuAction::Radius,
},
GripMenuItem {
label: "Arc Length",
action: GripMenuAction::ArcLength,
},
],
_ => vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Lengthen", action: GripMenuAction::Lengthen },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Lengthen",
action: GripMenuAction::Lengthen,
},
],
}
}
fn apply_grip_menu(
&mut self,
_grip_id: usize,
_action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, _grip_id: usize, _action: crate::scene::object::GripMenuAction) {
// Radius / Arc Length / Lengthen all need a follow-up prompt;
// the actual edit happens in `apply_grip_menu_value`.
}
@@ -306,10 +314,7 @@ impl crate::entities::traits::Grippable for Arc {
}
impl crate::entities::traits::PropertyEditable for Arc {
fn geometry_properties(
&self,
_text_style_names: &[String],
) -> PropSection {
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
properties(self)
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
+28 -41
View File
@@ -8,8 +8,8 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
use crate::entities::text_support::{
layout_mtext, resolve_dxf_special_chars, resolve_text_style, text_local_bounds, MTextRenderOpts,
MTextVAnchor, ResolvedTextStyle,
layout_mtext, resolve_dxf_special_chars, resolve_text_style, text_local_bounds,
MTextRenderOpts, MTextVAnchor, ResolvedTextStyle,
};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
@@ -162,7 +162,10 @@ fn build_attr_truck(input: AttrTextInputs<'_>, document: &acadrust::CadDocument)
// stacked fractions, …) reaches the stroke output. SingleLine attribs
// keep the Text-style anchor math below — they don't accept MText codes
// in the DXF spec.
if matches!(input.mtext_flag, MTextFlag::MultiLine | MTextFlag::ConstantMultiLine) {
if matches!(
input.mtext_flag,
MTextFlag::MultiLine | MTextFlag::ConstantMultiLine
) {
let display_value = if input.value.is_empty() {
String::new()
} else {
@@ -282,7 +285,11 @@ fn build_attr_truck(input: AttrTextInputs<'_>, document: &acadrust::CadDocument)
&resolved.font_name,
line,
);
strokes_all.push(TextStroke { strokes, origin, color: None });
strokes_all.push(TextStroke {
strokes,
origin,
color: None,
});
}
let _ = input.line_count; // round-trip only — recomputed above
@@ -338,10 +345,10 @@ impl Grippable for AttributeDefinition {
}
vec![square_grip(
0,
Vec3::new(
self.insertion_point.x as f32,
self.insertion_point.y as f32,
self.insertion_point.z as f32,
glam::DVec3::new(
self.insertion_point.x,
self.insertion_point.y,
self.insertion_point.z,
),
)]
}
@@ -448,21 +455,9 @@ impl PropertyEditable for AttributeDefinition {
.collect(),
},
},
ro(
"Multiline",
"att_is_multiline",
bool_yn(self.is_multiline),
),
ro(
"Line Count",
"att_line_count",
self.line_count.to_string(),
),
ro(
"Lock Position",
"att_lock_pos",
bool_yn(self.lock_position),
),
ro("Multiline", "att_is_multiline", bool_yn(self.is_multiline)),
ro("Line Count", "att_line_count", self.line_count.to_string()),
ro("Lock Position", "att_lock_pos", bool_yn(self.lock_position)),
ro("Invisible", "att_invisible", bool_yn(self.flags.invisible)),
ro("Constant", "att_constant", bool_yn(self.flags.constant)),
ro("Verify", "att_verify", bool_yn(self.flags.verify)),
@@ -591,10 +586,10 @@ impl Grippable for AttributeEntity {
}
vec![square_grip(
0,
Vec3::new(
self.insertion_point.x as f32,
self.insertion_point.y as f32,
self.insertion_point.z as f32,
glam::DVec3::new(
self.insertion_point.x,
self.insertion_point.y,
self.insertion_point.z,
),
)]
}
@@ -691,17 +686,13 @@ impl PropertyEditable for AttributeEntity {
"atte_field_len",
self.field_length.to_string(),
),
ro("MText Mode", "atte_mtext_flag", mtext_flag_str(self.mtext_flag)),
ro(
"Multiline",
"atte_is_multiline",
bool_yn(self.is_multiline),
),
ro(
"Line Count",
"atte_line_count",
self.line_count.to_string(),
"MText Mode",
"atte_mtext_flag",
mtext_flag_str(self.mtext_flag),
),
ro("Multiline", "atte_is_multiline", bool_yn(self.is_multiline)),
ro("Line Count", "atte_line_count", self.line_count.to_string()),
ro(
"Lock Position",
"atte_lock_pos",
@@ -716,11 +707,7 @@ impl PropertyEditable for AttributeEntity {
format!("{:X}", self.attdef_handle.value())
},
),
ro(
"Invisible",
"atte_invisible",
bool_yn(self.flags.invisible),
),
ro("Invisible", "atte_invisible", bool_yn(self.flags.invisible)),
ro("Constant", "atte_constant", bool_yn(self.flags.constant)),
ro("Verify", "atte_verify", bool_yn(self.flags.verify)),
ro("Preset", "atte_preset", bool_yn(self.flags.preset)),
+6 -10
View File
@@ -112,18 +112,14 @@ fn to_truck(circle: &Circle) -> TruckEntity {
}
fn grips(circle: &Circle) -> Vec<GripDef> {
let ctr = Vec3::new(
circle.center.x as f32,
circle.center.y as f32,
circle.center.z as f32,
);
let r = circle.radius as f32;
let ctr = glam::DVec3::new(circle.center.x, circle.center.y, circle.center.z);
let r = circle.radius;
vec![
center_grip(0, ctr),
square_grip(1, ctr + Vec3::new(r, 0.0, 0.0)),
square_grip(2, ctr + Vec3::new(0.0, r, 0.0)),
square_grip(3, ctr - Vec3::new(r, 0.0, 0.0)),
square_grip(4, ctr - Vec3::new(0.0, r, 0.0)),
square_grip(1, ctr + glam::DVec3::new(r, 0.0, 0.0)),
square_grip(2, ctr + glam::DVec3::new(0.0, r, 0.0)),
square_grip(3, ctr - glam::DVec3::new(r, 0.0, 0.0)),
square_grip(4, ctr - glam::DVec3::new(0.0, r, 0.0)),
]
}
+4 -6
View File
@@ -1,7 +1,5 @@
use std::cell::Cell;
use glam::Vec3;
use crate::scene::object::{GripDef, GripShape, PropValue, Property};
/// Linear / angular unit format pulled from the document header so the
@@ -121,7 +119,7 @@ pub fn format_angle(value_rad: f64) -> String {
}
}
pub fn square_grip(id: usize, world: Vec3) -> GripDef {
pub fn square_grip(id: usize, world: glam::DVec3) -> GripDef {
GripDef {
id,
world,
@@ -133,7 +131,7 @@ pub fn square_grip(id: usize, world: Vec3) -> GripDef {
/// Centre / translate grip — same square marker as a vertex grip but
/// flagged as a "whole-object move" handle for the grip-edit code.
pub fn center_grip(id: usize, world: Vec3) -> GripDef {
pub fn center_grip(id: usize, world: glam::DVec3) -> GripDef {
GripDef {
id,
world,
@@ -146,7 +144,7 @@ pub fn center_grip(id: usize, world: Vec3) -> GripDef {
/// Mid-segment stretch grip oriented along `dir` (the segment's in-plane
/// world-XY direction). Drawn as a small rectangle elongated along the
/// segment so the affordance reads as "stretch perpendicular".
pub fn rectangle_grip(id: usize, world: Vec3, dir: [f32; 2]) -> GripDef {
pub fn rectangle_grip(id: usize, world: glam::DVec3, dir: [f32; 2]) -> GripDef {
GripDef {
id,
world,
@@ -157,7 +155,7 @@ pub fn rectangle_grip(id: usize, world: Vec3, dir: [f32; 2]) -> GripDef {
}
#[allow(dead_code)]
pub fn triangle_grip(id: usize, world: Vec3) -> GripDef {
pub fn triangle_grip(id: usize, world: glam::DVec3) -> GripDef {
GripDef {
id,
world,
+96 -68
View File
@@ -565,8 +565,10 @@ impl Transformable for Dimension {
// ── Grippable ─────────────────────────────────────────────────────────────────
fn v3(v: &acadrust::types::Vector3) -> Vec3 {
Vec3::new(v.x as f32, v.y as f32, v.z as f32)
/// f64 variant for grip positions — grips must not round UTM-scale
/// coordinates through f32 before the world-offset subtraction.
fn dv3(v: &acadrust::types::Vector3) -> glam::DVec3 {
glam::DVec3::new(v.x, v.y, v.z)
}
fn set_v3(target: &mut acadrust::types::Vector3, p: Vec3) {
@@ -590,48 +592,48 @@ fn apply_to_v3(target: &mut acadrust::types::Vector3, apply: &GripApply) {
impl Grippable for Dimension {
fn grips(&self) -> Vec<GripDef> {
let text = v3(&self.base().text_middle_point);
let text = dv3(&self.base().text_middle_point);
match self {
Dimension::Linear(d) => vec![
square_grip(0, v3(&d.first_point)),
center_grip(1, v3(&d.second_point)),
center_grip(2, v3(&d.definition_point)),
square_grip(0, dv3(&d.first_point)),
center_grip(1, dv3(&d.second_point)),
center_grip(2, dv3(&d.definition_point)),
center_grip(3, text),
],
Dimension::Aligned(d) => vec![
square_grip(0, v3(&d.first_point)),
center_grip(1, v3(&d.second_point)),
center_grip(2, v3(&d.definition_point)),
square_grip(0, dv3(&d.first_point)),
center_grip(1, dv3(&d.second_point)),
center_grip(2, dv3(&d.definition_point)),
center_grip(3, text),
],
Dimension::Radius(d) => vec![
square_grip(0, v3(&d.angle_vertex)),
center_grip(1, v3(&d.definition_point)),
square_grip(0, dv3(&d.angle_vertex)),
center_grip(1, dv3(&d.definition_point)),
center_grip(2, text),
],
Dimension::Diameter(d) => vec![
square_grip(0, v3(&d.angle_vertex)),
center_grip(1, v3(&d.definition_point)),
square_grip(0, dv3(&d.angle_vertex)),
center_grip(1, dv3(&d.definition_point)),
center_grip(2, text),
],
Dimension::Angular2Ln(d) => vec![
square_grip(0, v3(&d.angle_vertex)),
center_grip(1, v3(&d.first_point)),
center_grip(2, v3(&d.second_point)),
center_grip(3, v3(&d.definition_point)),
square_grip(0, dv3(&d.angle_vertex)),
center_grip(1, dv3(&d.first_point)),
center_grip(2, dv3(&d.second_point)),
center_grip(3, dv3(&d.definition_point)),
center_grip(4, text),
],
Dimension::Angular3Pt(d) => vec![
square_grip(0, v3(&d.angle_vertex)),
center_grip(1, v3(&d.first_point)),
center_grip(2, v3(&d.second_point)),
center_grip(3, v3(&d.definition_point)),
square_grip(0, dv3(&d.angle_vertex)),
center_grip(1, dv3(&d.first_point)),
center_grip(2, dv3(&d.second_point)),
center_grip(3, dv3(&d.definition_point)),
center_grip(4, text),
],
Dimension::Ordinate(d) => vec![
square_grip(0, v3(&d.definition_point)),
center_grip(1, v3(&d.feature_location)),
center_grip(2, v3(&d.leader_endpoint)),
square_grip(0, dv3(&d.definition_point)),
center_grip(1, dv3(&d.feature_location)),
center_grip(2, dv3(&d.leader_endpoint)),
center_grip(3, text),
],
}
@@ -697,10 +699,7 @@ impl Grippable for Dimension {
self.base_mut().actual_measurement = self.measurement();
}
fn grip_menu(
&self,
grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
let (dim_line_grip, text_grip) = match self {
Dimension::Linear(_) | Dimension::Aligned(_) => (2, 3),
@@ -710,30 +709,59 @@ impl Grippable for Dimension {
};
if grip_id == text_grip {
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Move with Dim Line", action: GripMenuAction::MoveWithDimLine },
GripMenuItem { label: "Move with Leader", action: GripMenuAction::MoveWithLeader },
GripMenuItem { label: "Move Independent", action: GripMenuAction::MoveIndependent },
GripMenuItem { label: "Reset Text", action: GripMenuAction::ResetText },
GripMenuItem { label: "Rotate Text", action: GripMenuAction::RotateText },
GripMenuItem { label: "Above Dim Line", action: GripMenuAction::AboveDimLine },
GripMenuItem { label: "Center", action: GripMenuAction::Center },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Move with Dim Line",
action: GripMenuAction::MoveWithDimLine,
},
GripMenuItem {
label: "Move with Leader",
action: GripMenuAction::MoveWithLeader,
},
GripMenuItem {
label: "Move Independent",
action: GripMenuAction::MoveIndependent,
},
GripMenuItem {
label: "Reset Text",
action: GripMenuAction::ResetText,
},
GripMenuItem {
label: "Rotate Text",
action: GripMenuAction::RotateText,
},
GripMenuItem {
label: "Above Dim Line",
action: GripMenuAction::AboveDimLine,
},
GripMenuItem {
label: "Center",
action: GripMenuAction::Center,
},
]
} else if grip_id == dim_line_grip {
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Reverse Arrows", action: GripMenuAction::ReverseArrows },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Reverse Arrows",
action: GripMenuAction::ReverseArrows,
},
]
} else {
vec![GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch }]
vec![GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
}]
}
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let (_dim_line_grip, text_grip) = match self {
Dimension::Linear(_) | Dimension::Aligned(_) => (2, 3),
@@ -1056,14 +1084,8 @@ fn tessellate_dimension_inner(
// DIMLWD (dim line + arrows) and DIMLWE (extension lines). Negative
// codes fall through to the entity's own resolved weight.
let lw_dim = resolve_dim_lineweight_px(
style.map(|s| s.dimlwd).unwrap_or(-2),
line_weight_px,
);
let lw_ext = resolve_dim_lineweight_px(
style.map(|s| s.dimlwe).unwrap_or(-2),
line_weight_px,
);
let lw_dim = resolve_dim_lineweight_px(style.map(|s| s.dimlwd).unwrap_or(-2), line_weight_px);
let lw_ext = resolve_dim_lineweight_px(style.map(|s| s.dimlwe).unwrap_or(-2), line_weight_px);
// DIMLTEX (dim line) / DIMLTEX1 (ext1) / DIMLTEX2 (ext2) — linetype
// handles → pattern. Looked up in document.line_types by handle.
@@ -1311,10 +1333,7 @@ fn split_ext_lines(points: &[[f32; 3]]) -> (Vec<[f32; 3]>, Vec<[f32; 3]>) {
/// text_middle_point). Returns None when the dim has no saved text position
/// or has no well-defined dim-line midpoint (radius/diameter handled by
/// their own leg).
fn dimtmove_leader_endpoints(
dim: &Dimension,
world_offset: [f64; 3],
) -> Option<(Vec3, Vec3)> {
fn dimtmove_leader_endpoints(dim: &Dimension, world_offset: [f64; 3]) -> Option<(Vec3, Vec3)> {
let base = dim.base();
let txt = base.text_middle_point;
if txt.x * txt.x + txt.y * txt.y + txt.z * txt.z <= 1e-16 {
@@ -1366,8 +1385,7 @@ fn text_fill_rect(
let dimgap = style.map(|s| s.dimgap).unwrap_or(0.0).max(0.0);
// ~0.6 × text_height per character; matches average glyph aspect for
// the bundled stick fonts. Inflate by 1 DIMGAP on each side.
let approx_w =
value.chars().count() as f64 * text_height * 0.6 + dimgap * 2.0;
let approx_w = value.chars().count() as f64 * text_height * 0.6 + dimgap * 2.0;
let approx_h = text_height + dimgap * 2.0;
let rot = if dim.base().text_rotation.abs() > 1e-9 {
dim.base().text_rotation
@@ -1471,7 +1489,12 @@ fn dimension_geometry(
text
};
add_segment(&mut g.dim_lines, point, leader);
append_arrow(&mut g, point, normalized_or(center - point, Vec3::X), arrow1);
append_arrow(
&mut g,
point,
normalized_or(center - point, Vec3::X),
arrow1,
);
let radius = (point - center).length();
append_center_mark(&mut g, center, params.dimcen, radius);
}
@@ -2040,7 +2063,13 @@ fn build_tolerance_suffix(
return Some(format!("±{}{}", fmt(s.dimtp), unit));
}
if s.dimtp.abs() > 1e-12 || s.dimtm.abs() > 1e-12 {
return Some(format!("+{}{} / -{}{}", fmt(s.dimtp), unit, fmt(s.dimtm), unit));
return Some(format!(
"+{}{} / -{}{}",
fmt(s.dimtp),
unit,
fmt(s.dimtm),
unit
));
}
}
None
@@ -2084,7 +2113,11 @@ fn alternate_units_text(measurement: f64, style: Option<&DimStyle>) -> Option<St
let raw = format!("{:.*}", alttdec, x * s.dimaltf);
swap_decimal_sep(&apply_linear_zero_suppression(&raw, alttzin), s.dimdsep)
};
format!("{}/{}", fmt(measurement + s.dimtp), fmt(measurement - s.dimtm))
format!(
"{}/{}",
fmt(measurement + s.dimtp),
fmt(measurement - s.dimtm)
)
} else {
sep_swapped
};
@@ -2149,7 +2182,7 @@ fn dimension_tolerance_entity(
let dy_local = match s.dimtolj {
0 => -primary_height * 0.5 + tol_height * 0.5, // bottom-aligned with primary baseline
2 => primary_height * 0.5 - tol_height * 0.5, // top-aligned with primary top
_ => 0.0, // centred (default for ±)
_ => 0.0, // centred (default for ±)
};
let rot = primary_rotation;
let (sr, cr) = rot.sin_cos();
@@ -2422,11 +2455,7 @@ fn vec3_local(v: Vector3, off: [f64; 3]) -> Vec3 {
)
}
fn dimension_text_pos_f64(
dim: &Dimension,
style: Option<&DimStyle>,
text_height: f64,
) -> Vector3 {
fn dimension_text_pos_f64(dim: &Dimension, style: Option<&DimStyle>, text_height: f64) -> Vector3 {
let base = dim.base();
let p = base.text_middle_point;
if p.x * p.x + p.y * p.y + p.z * p.z > 1e-16 {
@@ -2545,4 +2574,3 @@ fn dimension_text_pos_f64(
fn perp_sign_default() -> f64 {
1.0
}
+6 -14
View File
@@ -140,15 +140,11 @@ fn to_truck(ell: &Ellipse) -> TruckEntity {
}
fn grips(ell: &Ellipse) -> Vec<GripDef> {
let ctr = Vec3::new(
ell.center.x as f32,
ell.center.y as f32,
ell.center.z as f32,
);
let maj = Vec3::new(
(ell.center.x + ell.major_axis.x) as f32,
(ell.center.y + ell.major_axis.y) as f32,
(ell.center.z + ell.major_axis.z) as f32,
let ctr = glam::DVec3::new(ell.center.x, ell.center.y, ell.center.z);
let maj = glam::DVec3::new(
ell.center.x + ell.major_axis.x,
ell.center.y + ell.major_axis.y,
ell.center.z + ell.major_axis.z,
);
let major_xy =
((ell.major_axis.x * ell.major_axis.x + ell.major_axis.y * ell.major_axis.y) as f64).sqrt();
@@ -158,11 +154,7 @@ fn grips(ell: &Ellipse) -> Vec<GripDef> {
} else {
(0.0, ell.major_axis_length() * ell.minor_axis_ratio)
};
let min = Vec3::new(
(ell.center.x + px) as f32,
(ell.center.y + py) as f32,
ell.center.z as f32,
);
let min = glam::DVec3::new(ell.center.x + px, ell.center.y + py, ell.center.z);
vec![
center_grip(0, ctr),
square_grip(1, maj),
+33 -34
View File
@@ -3,7 +3,7 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{center_grip, edit_prop as edit, parse_f64, ro_prop as ro};
use crate::entities::traits::{Grippable, FallbackTess, PropertyEditable, Transformable};
use crate::entities::traits::{FallbackTess, Grippable, PropertyEditable, Transformable};
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::tess_util::{arc_segments, arc_signed_span, wire_chord_tol, FallbackGeometry};
use crate::scene::wire_model::SnapHint;
@@ -66,11 +66,7 @@ fn properties(h: &Hatch) -> PropSection {
),
edit("Elevation", "elevation", h.elevation),
ro("Seed Points", "seed_count", h.seed_points.len().to_string()),
ro(
"Pixel Size",
"pixel_size",
format!("{:.6}", h.pixel_size),
),
ro("Pixel Size", "pixel_size", format!("{:.6}", h.pixel_size)),
ro(
"Normal",
"normal",
@@ -174,7 +170,7 @@ impl Transformable for Hatch {
/// Spline → fit points if present, else control points (x, y)
impl Grippable for Hatch {
fn grips(&self) -> Vec<GripDef> {
let elev = self.elevation as f32;
let elev = self.elevation;
let mut out = Vec::new();
let mut id = 0usize;
for path in &self.paths {
@@ -182,33 +178,30 @@ impl Grippable for Hatch {
match edge {
BoundaryEdge::Polyline(p) => {
for v in &p.vertices {
out.push(center_grip(id, Vec3::new(v.x as f32, v.y as f32, elev)));
out.push(center_grip(id, glam::DVec3::new(v.x, v.y, elev)));
id += 1;
}
}
BoundaryEdge::Line(l) => {
out.push(center_grip(
id,
Vec3::new(l.start.x as f32, l.start.y as f32, elev),
glam::DVec3::new(l.start.x, l.start.y, elev),
));
id += 1;
out.push(center_grip(
id,
Vec3::new(l.end.x as f32, l.end.y as f32, elev),
));
out.push(center_grip(id, glam::DVec3::new(l.end.x, l.end.y, elev)));
id += 1;
}
BoundaryEdge::CircularArc(a) => {
out.push(center_grip(
id,
Vec3::new(a.center.x as f32, a.center.y as f32, elev),
glam::DVec3::new(a.center.x, a.center.y, elev),
));
id += 1;
}
BoundaryEdge::EllipticArc(e) => {
out.push(center_grip(
id,
Vec3::new(e.center.x as f32, e.center.y as f32, elev),
glam::DVec3::new(e.center.x, e.center.y, elev),
));
id += 1;
}
@@ -219,7 +212,7 @@ impl Grippable for Hatch {
s.control_points.iter().map(|p| [p.x, p.y]).collect()
};
for [x, y] in pts {
out.push(center_grip(id, Vec3::new(x as f32, y as f32, elev)));
out.push(center_grip(id, glam::DVec3::new(x, y, elev)));
id += 1;
}
}
@@ -330,24 +323,29 @@ impl Grippable for Hatch {
}
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Origin Point", action: GripMenuAction::OriginPoint },
GripMenuItem { label: "Hatch Angle", action: GripMenuAction::HatchAngle },
GripMenuItem { label: "Hatch Scale", action: GripMenuAction::HatchScale },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Origin Point",
action: GripMenuAction::OriginPoint,
},
GripMenuItem {
label: "Hatch Angle",
action: GripMenuAction::HatchAngle,
},
GripMenuItem {
label: "Hatch Scale",
action: GripMenuAction::HatchScale,
},
]
}
fn apply_grip_menu(
&mut self,
_grip_id: usize,
_action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, _grip_id: usize, _action: crate::scene::object::GripMenuAction) {
// Origin / Angle / Scale need a follow-up value — handled by
// `apply_grip_menu_value`.
}
@@ -446,7 +444,11 @@ impl FallbackTess for Hatch {
key_verts.push(p);
continue;
};
let segs = arc_segments(arc.radius, arc.sweep.abs(), wire_chord_tol(arc.radius));
let segs = arc_segments(
arc.radius,
arc.sweep.abs(),
wire_chord_tol(arc.radius),
);
for j in 0..segs {
let s = arc.sample(j as f64 / segs as f64);
let p = to_wcs(s[0], s[1]);
@@ -509,10 +511,7 @@ impl FallbackTess for Hatch {
+ ell.major_axis_endpoint.y * ell.major_axis_endpoint.y)
.sqrt();
let r_min = r_maj * ell.minor_axis_ratio;
let rot = ell
.major_axis_endpoint
.y
.atan2(ell.major_axis_endpoint.x);
let rot = ell.major_axis_endpoint.y.atan2(ell.major_axis_endpoint.x);
let (sa, span) =
arc_signed_span(ell.start_angle, ell.end_angle, ell.counter_clockwise);
let segs = arc_segments(r_maj, span.abs(), wire_chord_tol(r_maj));
+14 -17
View File
@@ -7,15 +7,11 @@ use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::{block_cache, render, tessellate};
use crate::scene::wire_model::WireModel;
use crate::scene::{block_cache, render, tessellate};
fn grips(ins: &Insert) -> Vec<GripDef> {
let p = Vec3::new(
ins.insert_point.x as f32,
ins.insert_point.y as f32,
ins.insert_point.z as f32,
);
let p = glam::DVec3::new(ins.insert_point.x, ins.insert_point.y, ins.insert_point.z);
vec![square_grip(0, p)]
}
@@ -99,7 +95,10 @@ fn apply_transform(ins: &mut Insert, t: &EntityTransform) {
crate::impl_entity_basics!(Insert);
impl crate::entities::traits::FallbackTess for Insert {
fn fallback_geometry(&self, world_offset: [f64; 3]) -> crate::scene::tess_util::FallbackGeometry {
fn fallback_geometry(
&self,
world_offset: [f64; 3],
) -> crate::scene::tess_util::FallbackGeometry {
let [ox, oy, oz] = world_offset;
let ip = Vec3::new(
(self.insert_point.x - ox) as f32,
@@ -154,15 +153,14 @@ pub(crate) fn append_insert_attribute_wires(
continue;
}
let attr_entity = EntityType::AttributeEntity(attr.clone());
let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) =
render::render_style_for_block_sub(
document,
&attr_entity,
ins_color,
ins_pat_len,
ins_pat,
ins_lw_px,
);
let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) = render::render_style_for_block_sub(
document,
&attr_entity,
ins_color,
ins_pat_len,
ins_pat,
ins_lw_px,
);
let sub_color = render::adapt_to_bg(sub_color, bg_color);
let sub_color = if is_xref && !sel {
block_cache::fade_toward_bg(sub_color, bg_color)
@@ -192,4 +190,3 @@ pub(crate) fn append_insert_attribute_wires(
wires.extend(attr_wires);
}
}
+34 -27
View File
@@ -4,7 +4,7 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
use crate::entities::traits::{TruckConvertible};
use crate::entities::traits::TruckConvertible;
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::wire_model::TangentGeom;
@@ -15,9 +15,7 @@ fn to_truck(leader: &Leader) -> TruckEntity {
let verts = &leader.vertices;
let nan = [f64::NAN; 3];
let p3 = |v: &acadrust::types::Vector3| -> [f64; 3] { [v.x, v.y, v.z] };
let p3f = |v: &acadrust::types::Vector3| -> [f32; 3] {
[v.x as f32, v.y as f32, v.z as f32]
};
let p3f = |v: &acadrust::types::Vector3| -> [f32; 3] { [v.x as f32, v.y as f32, v.z as f32] };
let mut points: Vec<[f64; 3]> = Vec::new();
let mut tangents: Vec<TangentGeom> = Vec::new();
@@ -67,7 +65,11 @@ fn to_truck(leader: &Leader) -> TruckEntity {
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_f64 } else { -1.0_f64 };
let sign = if (last.x - prev.x) >= 0.0 {
1.0_f64
} else {
-1.0_f64
};
let len = leader.text_height * 1.5;
let last_f = p3(last);
points.push(nan);
@@ -92,14 +94,14 @@ fn grips(leader: &Leader) -> Vec<GripDef> {
.vertices
.iter()
.enumerate()
.map(|(i, v)| square_grip(i, Vec3::new(v.x as f32, v.y as f32, v.z as f32)))
.map(|(i, v)| square_grip(i, glam::DVec3::new(v.x, v.y, v.z)))
.collect();
if n >= 2 {
let sum = leader.vertices.iter().fold(Vec3::ZERO, |acc, v| {
acc + Vec3::new(v.x as f32, v.y as f32, v.z as f32)
let sum = leader.vertices.iter().fold(glam::DVec3::ZERO, |acc, v| {
acc + glam::DVec3::new(v.x, v.y, v.z)
});
grips.push(center_grip(n, sum / n as f32));
grips.push(center_grip(n, sum / n as f64));
}
grips
@@ -463,31 +465,39 @@ impl crate::entities::traits::Grippable for Leader {
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
let n = self.vertices.len();
if grip_id == 0 {
// Arrow head — stretch only.
vec![GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch }]
vec![GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
}]
} else if grip_id < n {
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem { label: "Remove Vertex", action: GripMenuAction::RemoveVertex },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
GripMenuItem {
label: "Remove Vertex",
action: GripMenuAction::RemoveVertex,
},
]
} else {
// Centroid grip — move whole leader.
vec![GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch }]
vec![GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
}]
}
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let n = self.vertices.len();
match action {
@@ -514,10 +524,7 @@ impl crate::entities::traits::Grippable for Leader {
}
impl crate::entities::traits::PropertyEditable for Leader {
fn geometry_properties(
&self,
_text_style_names: &[String],
) -> PropSection {
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
properties(self)
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
+17 -23
View File
@@ -1,5 +1,4 @@
use acadrust::{entities::Line, Entity};
use glam::Vec3;
use truck_modeling::{builder, Point3};
use crate::command::EntityTransform;
@@ -67,12 +66,8 @@ fn to_truck(line: &Line) -> TruckEntity {
}
fn grips(line: &Line) -> Vec<GripDef> {
let s = Vec3::new(
line.start.x as f32,
line.start.y as f32,
line.start.z as f32,
);
let e = Vec3::new(line.end.x as f32, line.end.y as f32, line.end.z as f32);
let s = glam::DVec3::new(line.start.x, line.start.y, line.start.z);
let e = glam::DVec3::new(line.end.x, line.end.y, line.end.z);
let m = (s + e) * 0.5;
vec![square_grip(0, s), square_grip(1, e), center_grip(2, m)]
}
@@ -163,25 +158,27 @@ impl crate::entities::traits::Grippable for Line {
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
if grip_id == 2 {
vec![GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch }]
vec![GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
}]
} else {
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Lengthen", action: GripMenuAction::Lengthen },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Lengthen",
action: GripMenuAction::Lengthen,
},
]
}
}
fn apply_grip_menu(
&mut self,
_grip_id: usize,
_action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, _grip_id: usize, _action: crate::scene::object::GripMenuAction) {
// Lengthen needs a follow-up distance — handled by
// `apply_grip_menu_value`.
}
@@ -235,10 +232,7 @@ impl crate::entities::traits::Grippable for Line {
}
impl crate::entities::traits::PropertyEditable for Line {
fn geometry_properties(
&self,
_text_style_names: &[String],
) -> PropSection {
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
properties(self)
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
+41 -38
View File
@@ -1,12 +1,11 @@
use acadrust::entities::{LwPolyline, LwVertex};
use glam::Vec3;
use truck_modeling::{builder, Edge, Point3, Wire};
use crate::command::EntityTransform;
use crate::entities::common::{
edit_prop as edit, parse_f64, rectangle_grip, ro_prop as ro, square_grip,
};
use crate::entities::traits::{TruckConvertible};
use crate::entities::traits::TruckConvertible;
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::wire_model::TangentGeom;
@@ -62,9 +61,7 @@ fn thick_segments(
) -> TruckEntity {
let (nx, ny, nz) = normal;
let t = thickness;
let off = |p: [f64; 3]| -> [f64; 3] {
[p[0] + t * nx, p[1] + t * ny, p[2] + t * nz]
};
let off = |p: [f64; 3]| -> [f64; 3] { [p[0] + t * nx, p[1] + t * ny, p[2] + t * nz] };
let mut pts: Vec<[f64; 3]> = Vec::with_capacity(path_pts.len() * 2 + seg_data.len() * 3 + 4);
// Bottom path
pts.extend_from_slice(path_pts);
@@ -280,7 +277,7 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
}
fn grips(pline: &LwPolyline) -> Vec<GripDef> {
let elev = pline.elevation as f32;
let elev = pline.elevation;
let n = pline.vertices.len();
let seg_count = if pline.is_closed {
n
@@ -292,7 +289,7 @@ fn grips(pline: &LwPolyline) -> Vec<GripDef> {
.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, glam::DVec3::new(v.location.x, v.location.y, elev)))
.collect();
// One mid-segment stretch grip per segment (straight or arc). The
@@ -318,7 +315,7 @@ fn grips(pline: &LwPolyline) -> Vec<GripDef> {
let dy = (v1.location.y - v0.location.y) as f32;
out.push(rectangle_grip(
n + i,
Vec3::new(mx as f32, my as f32, elev),
glam::DVec3::new(mx, my, elev),
[dx, dy],
));
}
@@ -412,14 +409,8 @@ 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[i0].location.x,
pline.vertices[i0].location.y,
];
let p1 = [
pline.vertices[i1].location.x,
pline.vertices[i1].location.y,
];
let p0 = [pline.vertices[i0].location.x, pline.vertices[i0].location.y];
let p1 = [pline.vertices[i1].location.x, pline.vertices[i1].location.y];
if let Some(new_bulge) = bulge_from_midpoint(p0, p1, new_mid) {
pline.vertices[i0].bulge = new_bulge.clamp(-1e6, 1e6);
}
@@ -444,25 +435,27 @@ impl crate::entities::traits::Grippable for LwPolyline {
fn grips(&self) -> Vec<crate::scene::object::GripDef> {
grips(self)
}
fn apply_grip(
&mut self,
grip_id: usize,
apply: crate::scene::object::GripApply,
) {
fn apply_grip(&mut self, grip_id: usize, apply: crate::scene::object::GripApply) {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
let n = self.vertices.len();
if grip_id < n {
// Vertex grip.
return vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem { label: "Remove Vertex", action: GripMenuAction::RemoveVertex },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
GripMenuItem {
label: "Remove Vertex",
action: GripMenuAction::RemoveVertex,
},
];
}
// Segment midpoint grip.
@@ -472,21 +465,29 @@ impl crate::entities::traits::Grippable for LwPolyline {
.get(seg)
.map_or(false, |v| v.bulge.abs() >= 1e-9);
let convert = if is_arc {
GripMenuItem { label: "Convert to Line", action: GripMenuAction::ConvertToLine }
GripMenuItem {
label: "Convert to Line",
action: GripMenuAction::ConvertToLine,
}
} else {
GripMenuItem { label: "Convert to Arc", action: GripMenuAction::ConvertToArc }
GripMenuItem {
label: "Convert to Arc",
action: GripMenuAction::ConvertToArc,
}
};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
convert,
]
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let n = self.vertices.len();
match action {
@@ -583,7 +584,9 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> Vec<Vec<[f32; 2
}
let p0 = [v0.location.x as f32, v0.location.y as f32];
let p1 = [v1.location.x as f32, v1.location.y as f32];
if let Some(poly) = crate::entities::common::polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32) {
if let Some(poly) =
crate::entities::common::polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32)
{
out.push(poly);
}
}
+33 -24
View File
@@ -14,6 +14,10 @@ fn v3(v: &acadrust::types::Vector3) -> [f64; 3] {
[v.x, v.y, v.z]
}
fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 {
glam::DVec3::new(v.x, v.y, v.z)
}
fn v3f32(v: &acadrust::types::Vector3) -> [f32; 3] {
[v.x as f32, v.y as f32, v.z as f32]
}
@@ -80,10 +84,10 @@ impl TruckConvertible for Face3D {
impl Grippable for Face3D {
fn grips(&self) -> Vec<GripDef> {
vec![
square_grip(0, Vec3::from(v3f32(&self.first_corner))),
square_grip(1, Vec3::from(v3f32(&self.second_corner))),
square_grip(2, Vec3::from(v3f32(&self.third_corner))),
square_grip(3, Vec3::from(v3f32(&self.fourth_corner))),
square_grip(0, dvec3(&self.first_corner)),
square_grip(1, dvec3(&self.second_corner)),
square_grip(2, dvec3(&self.third_corner)),
square_grip(3, dvec3(&self.fourth_corner)),
]
}
@@ -247,11 +251,7 @@ 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,
),
glam::DVec3::new(v.location.x, v.location.y, v.location.z),
)
})
.collect()
@@ -300,7 +300,11 @@ impl PropertyEditable for PolygonMesh {
"pm_smooth_n",
self.n_smooth_density.to_string(),
),
ro("Elevation", "pm_elevation", format!("{:.4}", self.elevation)),
ro(
"Elevation",
"pm_elevation",
format!("{:.4}", self.elevation),
),
ro(
"Normal",
"pm_normal",
@@ -395,11 +399,7 @@ 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,
),
glam::DVec3::new(v.location.x, v.location.y, v.location.z),
)
})
.collect()
@@ -445,7 +445,11 @@ impl PropertyEditable for PolyfaceMesh {
_ => "(none)".to_string(),
},
),
ro("Elevation", "pfm_elevation", format!("{:.4}", self.elevation)),
ro(
"Elevation",
"pfm_elevation",
format!("{:.4}", self.elevation),
),
ro(
"Normal",
"pfm_normal",
@@ -454,7 +458,11 @@ impl PropertyEditable for PolyfaceMesh {
self.normal.x, self.normal.y, self.normal.z
),
),
ro("Thickness", "pfm_thickness", format!("{:.4}", self.thickness)),
ro(
"Thickness",
"pfm_thickness",
format!("{:.4}", self.thickness),
),
],
}
}
@@ -490,9 +498,7 @@ impl TruckConvertible for Mesh {
if self.vertices.is_empty() {
return None;
}
let get = |i: usize| -> Option<[f64; 3]> {
self.vertices.get(i).map(|v| [v.x, v.y, v.z])
};
let get = |i: usize| -> Option<[f64; 3]> { self.vertices.get(i).map(|v| [v.x, v.y, v.z]) };
let mut pts: Vec<[f64; 3]> = Vec::new();
if !self.edges.is_empty() {
@@ -549,7 +555,12 @@ impl TruckConvertible for Mesh {
let snap_pts: Vec<(Vec3, SnapHint)> = self
.vertices
.iter()
.map(|v| (Vec3::new(v.x as f32, v.y as f32, v.z as f32), SnapHint::Node))
.map(|v| {
(
Vec3::new(v.x as f32, v.y as f32, v.z as f32),
SnapHint::Node,
)
})
.collect();
let key_vertices: Vec<[f64; 3]> = self.vertices.iter().map(|v| [v.x, v.y, v.z]).collect();
@@ -568,9 +579,7 @@ impl Grippable for Mesh {
self.vertices
.iter()
.enumerate()
.map(|(i, v)| {
square_grip(i, Vec3::new(v.x as f32, v.y as f32, v.z as f32))
})
.map(|(i, v)| square_grip(i, glam::DVec3::new(v.x, v.y, v.z)))
.collect()
}
+15 -17
View File
@@ -126,11 +126,7 @@ 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,
),
glam::DVec3::new(v.position.x, v.position.y, v.position.z),
)
})
.collect()
@@ -153,23 +149,25 @@ impl Grippable for MLine {
}
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem { label: "Remove Vertex", action: GripMenuAction::RemoveVertex },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
GripMenuItem {
label: "Remove Vertex",
action: GripMenuAction::RemoveVertex,
},
]
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let n = self.vertices.len();
match action {
+34 -21
View File
@@ -106,7 +106,11 @@ pub fn glyph_boxes(t: &MText, document: &acadrust::CadDocument) -> Vec<GlyphBox>
};
let layout = layout_mtext(&MTextRenderOpts {
value: &t.value,
insertion: [t.insertion_point.x, t.insertion_point.y, t.insertion_point.z],
insertion: [
t.insertion_point.x,
t.insertion_point.y,
t.insertion_point.z,
],
height: t.height as f32,
rect_w: t.rectangle_width as f32,
rotation,
@@ -149,7 +153,11 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
};
let layout = layout_mtext(&MTextRenderOpts {
value: &t.value,
insertion: [t.insertion_point.x, t.insertion_point.y, t.insertion_point.z],
insertion: [
t.insertion_point.x,
t.insertion_point.y,
t.insertion_point.z,
],
height: t.height as f32,
rect_w: t.rectangle_width as f32,
rotation,
@@ -175,13 +183,13 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
}
fn grips(t: &MText) -> Vec<GripDef> {
let p = Vec3::new(
t.insertion_point.x as f32,
t.insertion_point.y as f32,
t.insertion_point.z as f32,
let p = glam::DVec3::new(
t.insertion_point.x,
t.insertion_point.y,
t.insertion_point.z,
);
let dir = Vec3::new((t.rotation as f32).cos(), (t.rotation as f32).sin(), 0.0);
let width_grip = p + dir * t.rectangle_width.max(0.0) as f32;
let dir = glam::DVec3::new(t.rotation.cos(), t.rotation.sin(), 0.0);
let width_grip = p + dir * t.rectangle_width.max(0.0);
vec![square_grip(0, p), triangle_grip(1, width_grip)]
}
@@ -348,29 +356,34 @@ impl Grippable for MText {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
if grip_id == 0 {
// Insertion point
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Move with Text", action: GripMenuAction::MoveWithText },
GripMenuItem { label: "Rotate", action: GripMenuAction::RotateText },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Move with Text",
action: GripMenuAction::MoveWithText,
},
GripMenuItem {
label: "Rotate",
action: GripMenuAction::RotateText,
},
]
} else {
// Width grip
vec![GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch }]
vec![GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
}]
}
}
fn apply_grip_menu(
&mut self,
_grip_id: usize,
_action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, _grip_id: usize, _action: crate::scene::object::GripMenuAction) {
// Rotate needs a follow-up angle handled by
// `apply_grip_menu_value`; Move-with-Text is the default drag.
}
+108 -106
View File
@@ -1,6 +1,4 @@
use acadrust::entities::{
LeaderContentType, MultiLeader, MultiLeaderPathType, TextAttachmentType,
};
use acadrust::entities::{LeaderContentType, MultiLeader, MultiLeaderPathType, TextAttachmentType};
use crate::entities::text_support::{
layout_mtext, resolve_text_style, MTextRenderOpts, MTextVAnchor, ResolvedTextStyle,
@@ -32,7 +30,7 @@ use crate::command::EntityTransform;
use crate::entities::common::{
center_grip, edit_prop as edit, ro_prop as ro, square_grip, triangle_grip,
};
use crate::entities::traits::{TruckConvertible};
use crate::entities::traits::TruckConvertible;
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::wire_model::{SnapHint, TangentGeom};
@@ -104,58 +102,61 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
.copied()
.or_else(|| ml.context.leader_roots.first().map(|r| r.connection_point))
.unwrap_or(text_loc);
let text_sign: f64 = if text_loc.x >= leader_ref.x { 1.0 } else { -1.0 };
let text_sign: f64 = if text_loc.x >= leader_ref.x {
1.0
} else {
-1.0
};
// Lay the text out once, up front: its width fixes where the landing meets
// the text. The same layout is reused below to emit the glyph strokes.
let text_layout = if ml.content_type == LeaderContentType::MText
&& !ml.context.text_string.is_empty()
{
let ctx = &ml.context;
let height = if ctx.text_height > 0.0 {
ctx.text_height as f32
let text_layout =
if ml.content_type == LeaderContentType::MText && !ml.context.text_string.is_empty() {
let ctx = &ml.context;
let height = if ctx.text_height > 0.0 {
ctx.text_height as f32
} else {
ml.text_height as f32 * ml.scale_factor as f32
};
let td = ctx.text_direction;
let mut rot = if td.x.abs() > 1e-9 || td.y.abs() > 1e-9 {
(td.y as f32).atan2(td.x as f32)
} else {
ctx.text_rotation as f32
};
let style_name = ctx
.text_style_handle
.as_ref()
.and_then(|h| {
document
.text_styles
.iter()
.find(|s| s.handle == *h)
.map(|s| s.name.clone())
})
.unwrap_or_else(|| "STANDARD".to_string());
let resolved = resolve_text_style(&style_name, document);
if resolved.is_upside_down {
rot += std::f32::consts::PI;
}
Some(layout_mtext(&MTextRenderOpts {
value: &ctx.text_string,
insertion: [text_loc.x, text_loc.y, text_loc.z],
height,
rect_w: ctx.text_width as f32,
rotation: rot,
style: &resolved,
// Side-anchored on the leader-facing edge so the text reads
// outward; flips live with the leader/text side.
attach_h_anchor: if text_sign >= 0.0 { 0.0 } else { 1.0 },
v_anchor: mleader_v_anchor(ctx.text_left_attachment),
line_spacing_factor: ctx.line_spacing_factor as f32,
vertical_text: false,
want_glyph_boxes: false,
}))
} else {
ml.text_height as f32 * ml.scale_factor as f32
None
};
let td = ctx.text_direction;
let mut rot = if td.x.abs() > 1e-9 || td.y.abs() > 1e-9 {
(td.y as f32).atan2(td.x as f32)
} else {
ctx.text_rotation as f32
};
let style_name = ctx
.text_style_handle
.as_ref()
.and_then(|h| {
document
.text_styles
.iter()
.find(|s| s.handle == *h)
.map(|s| s.name.clone())
})
.unwrap_or_else(|| "STANDARD".to_string());
let resolved = resolve_text_style(&style_name, document);
if resolved.is_upside_down {
rot += std::f32::consts::PI;
}
Some(layout_mtext(&MTextRenderOpts {
value: &ctx.text_string,
insertion: [text_loc.x, text_loc.y, text_loc.z],
height,
rect_w: ctx.text_width as f32,
rotation: rot,
style: &resolved,
// Side-anchored on the leader-facing edge so the text reads
// outward; flips live with the leader/text side.
attach_h_anchor: if text_sign >= 0.0 { 0.0 } else { 1.0 },
v_anchor: mleader_v_anchor(ctx.text_left_attachment),
line_spacing_factor: ctx.line_spacing_factor as f32,
vertical_text: false,
want_glyph_boxes: false,
}))
} else {
None
};
let dogleg = if ml.enable_landing && ml.enable_dogleg {
ml.dogleg_length.max(0.0)
} else {
@@ -184,8 +185,9 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
// Build the full control-point list: line.points + landing point
let mut ctrl: Vec<[f64; 3]> = line.points.iter().map(|p| p3(p)).collect();
let last_f = *ctrl.last().unwrap_or(&landing_pt);
let dist =
((last_f[0] - landing_pt[0]).powi(2) + (last_f[1] - landing_pt[1]).powi(2)).sqrt();
let dist = ((last_f[0] - landing_pt[0]).powi(2)
+ (last_f[1] - landing_pt[1]).powi(2))
.sqrt();
if dist > 1e-9 {
ctrl.push(landing_pt);
}
@@ -348,10 +350,7 @@ 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, glam::DVec3::new(p.x, p.y, p.z)));
id += 1;
}
}
@@ -360,16 +359,10 @@ fn grips(ml: &MultiLeader) -> Vec<GripDef> {
if ml.content_type == LeaderContentType::MText {
let tl = &ml.context.text_location;
// Text-location grip, then the wrap-width grip at the box's far edge.
result.push(center_grip(
id,
Vec3::new(tl.x as f32, tl.y as f32, tl.z as f32),
));
result.push(center_grip(id, glam::DVec3::new(tl.x, tl.y, tl.z)));
id += 1;
let (_, far) = text_box_geom(ml);
result.push(triangle_grip(
id,
Vec3::new(far[0] as f32, far[1] as f32, far[2] as f32),
));
result.push(triangle_grip(id, glam::DVec3::new(far[0], far[1], far[2])));
}
result
@@ -661,12 +654,20 @@ fn properties(ml: &MultiLeader) -> PropSection {
ro(
"Extend Leader",
"extend_leader_to_text",
if ml.extend_leader_to_text { "Yes" } else { "No" },
if ml.extend_leader_to_text {
"Yes"
} else {
"No"
},
),
ro(
"Text Direction Negative",
"text_direction_negative",
if ml.text_direction_negative { "Yes" } else { "No" },
if ml.text_direction_negative {
"Yes"
} else {
"No"
},
),
ro(
"Text Align In IPE",
@@ -935,10 +936,7 @@ impl crate::entities::traits::Grippable for MultiLeader {
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
let n_vertices: usize = self
.context
@@ -951,7 +949,10 @@ impl crate::entities::traits::Grippable for MultiLeader {
if grip_id == n_vertices {
// Text-location grip.
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Move with Leader",
action: GripMenuAction::MoveWithLeader,
@@ -968,17 +969,22 @@ impl crate::entities::traits::Grippable for MultiLeader {
} else {
// Leader-line vertex.
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Leader", action: GripMenuAction::AddLeader },
GripMenuItem { label: "Remove Leader", action: GripMenuAction::RemoveLeader },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Leader",
action: GripMenuAction::AddLeader,
},
GripMenuItem {
label: "Remove Leader",
action: GripMenuAction::RemoveLeader,
},
]
}
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
// Locate the (root, line) and vertex position owning this grip id.
let mut idx = 0usize;
@@ -996,8 +1002,12 @@ impl crate::entities::traits::Grippable for MultiLeader {
let Some((ri, li, vpos)) = loc else { return };
match action {
A::RemoveLeader => {
let total: usize =
self.context.leader_roots.iter().map(|r| r.lines.len()).sum();
let total: usize = self
.context
.leader_roots
.iter()
.map(|r| r.lines.len())
.sum();
if total > 1 {
self.context.leader_roots[ri].lines.remove(li);
if self.context.leader_roots[ri].lines.is_empty()
@@ -1024,10 +1034,7 @@ impl crate::entities::traits::Grippable for MultiLeader {
}
impl crate::entities::traits::PropertyEditable for MultiLeader {
fn geometry_properties(
&self,
_text_style_names: &[String],
) -> PropSection {
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
properties(self)
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
@@ -1088,9 +1095,7 @@ impl MultiLeaderTess for MultiLeader {
// (ByLayer/ByBlock/Default) fall through to the entity's already-resolved
// pixel width.
let leader_lw_px = match ml.line_weight {
acadrust::types::LineWeight::Value(v) if v >= 0 => {
(v as f32 / 100.0) * (96.0 / 25.4)
}
acadrust::types::LineWeight::Value(v) if v >= 0 => (v as f32 / 100.0) * (96.0 / 25.4),
_ => line_weight_px,
};
// ml.line_type_handle — resolve via line_types table by handle and apply
@@ -1104,7 +1109,9 @@ impl MultiLeaderTess for MultiLeader {
.find(|lt| lt.handle == h)
.map(|lt| lt.name.clone());
match name {
Some(n) => crate::scene::render::resolve_pattern(&document.line_types, &n, lt_scale),
Some(n) => {
crate::scene::render::resolve_pattern(&document.line_types, &n, lt_scale)
}
None => (0.0, [0.0; 8]),
}
}
@@ -1163,7 +1170,11 @@ impl MultiLeaderTess for MultiLeader {
.copied()
.or_else(|| ml.context.leader_roots.first().map(|r| r.connection_point))
.unwrap_or(text_loc_w);
let text_sign_w: f64 = if text_loc_w.x >= leader_ref_w.x { 1.0 } else { -1.0 };
let text_sign_w: f64 = if text_loc_w.x >= leader_ref_w.x {
1.0
} else {
-1.0
};
for root in &ml.context.leader_roots {
let cp = &root.connection_point;
@@ -1183,7 +1194,8 @@ impl MultiLeaderTess for MultiLeader {
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);
}
@@ -1251,8 +1263,7 @@ impl MultiLeaderTess for MultiLeader {
points.push(landing_end);
// Continue the landing to the text's near edge so it stays
// attached as the text moves.
if ml.content_type == LeaderContentType::MText
&& !ml.context.text_string.is_empty()
if ml.content_type == LeaderContentType::MText && !ml.context.text_string.is_empty()
{
let tx = (ml.context.text_location.x - ox) as f32;
let ty = (ml.context.text_location.y - oy) as f32;
@@ -1520,10 +1531,7 @@ impl MultiLeaderTess for MultiLeader {
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px,
snap_pts: vec![(
Vec3::new(local_ins_x, local_ins_y, z),
SnapHint::Node,
)],
snap_pts: vec![(Vec3::new(local_ins_x, local_ins_y, z), SnapHint::Node)],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
@@ -1563,10 +1571,7 @@ impl MultiLeaderTess for MultiLeader {
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: 1.0,
snap_pts: vec![(
Vec3::new(local_ins_x, local_ins_y, z),
SnapHint::Node,
)],
snap_pts: vec![(Vec3::new(local_ins_x, local_ins_y, z), SnapHint::Node)],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
@@ -1604,10 +1609,7 @@ impl MultiLeaderTess for MultiLeader {
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px,
snap_pts: vec![(
Vec3::new(local_ins_x, local_ins_y, z),
SnapHint::Node,
)],
snap_pts: vec![(Vec3::new(local_ins_x, local_ins_y, z), SnapHint::Node)],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
+14 -16
View File
@@ -3,7 +3,7 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
use crate::entities::traits::{TruckConvertible};
use crate::entities::traits::TruckConvertible;
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::wire_model::SnapHint;
@@ -56,15 +56,15 @@ fn to_truck(ole: &Ole2Frame) -> TruckEntity {
}
fn grips(ole: &Ole2Frame) -> Vec<GripDef> {
let ul = Vec3::new(
ole.upper_left_corner.x as f32,
ole.upper_left_corner.y as f32,
ole.upper_left_corner.z as f32,
let ul = glam::DVec3::new(
ole.upper_left_corner.x,
ole.upper_left_corner.y,
ole.upper_left_corner.z,
);
let lr = Vec3::new(
ole.lower_right_corner.x as f32,
ole.lower_right_corner.y as f32,
ole.lower_right_corner.z as f32,
let lr = glam::DVec3::new(
ole.lower_right_corner.x,
ole.lower_right_corner.y,
ole.lower_right_corner.z,
);
let center = (ul + lr) * 0.5;
vec![
@@ -181,7 +181,10 @@ impl TruckConvertible for Ole2Frame {
crate::impl_entity_basics!(Ole2Frame);
impl crate::entities::traits::FallbackTess for Ole2Frame {
fn fallback_geometry(&self, world_offset: [f64; 3]) -> crate::scene::tess_util::FallbackGeometry {
fn fallback_geometry(
&self,
world_offset: [f64; 3],
) -> crate::scene::tess_util::FallbackGeometry {
// OLE objects carry a bounding rectangle in model space.
// Render a simple X-through-rectangle placeholder.
let [ox, oy, oz] = world_offset;
@@ -193,12 +196,7 @@ impl crate::entities::traits::FallbackTess for Ole2Frame {
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
// Degenerate / unknown size — show a small cross.
let s = 0.5_f32;
return (
vec![[-s, 0.0, 0.0], [s, 0.0, 0.0]],
vec![],
vec![],
vec![],
);
return (vec![[-s, 0.0, 0.0], [s, 0.0, 0.0]], vec![], vec![], vec![]);
}
let pts = vec![
// Outer rectangle
+1 -6
View File
@@ -132,11 +132,7 @@ fn to_truck(pt: &Point, document: &acadrust::CadDocument) -> TruckEntity {
}
fn grips(pt: &Point) -> Vec<GripDef> {
let p = Vec3::new(
pt.location.x as f32,
pt.location.y as f32,
pt.location.z as f32,
);
let p = glam::DVec3::new(pt.location.x, pt.location.y, pt.location.z);
vec![square_grip(0, p)]
}
@@ -196,4 +192,3 @@ impl TruckConvertible for Point {
}
crate::impl_entity_basics!(Point);
+51 -56
View File
@@ -1,5 +1,4 @@
use acadrust::entities::{Polyline, Polyline2D, Polyline3D};
use glam::Vec3;
use truck_modeling::{builder, Edge, Point3, Wire};
use crate::command::EntityTransform;
@@ -47,11 +46,7 @@ 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,
),
glam::DVec3::new(v.location.x, v.location.y, v.location.z),
)
})
.collect()
@@ -74,23 +69,25 @@ impl Grippable for Polyline {
}
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem { label: "Remove Vertex", action: GripMenuAction::RemoveVertex },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
GripMenuItem {
label: "Remove Vertex",
action: GripMenuAction::RemoveVertex,
},
]
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let n = self.vertices.len();
match action {
@@ -197,12 +194,8 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
if pl.thickness.abs() > 1e-10 {
let (nx, ny, nz) = normal;
let t = pl.thickness;
let off = |p: [f64; 3]| -> [f64; 3] {
[p[0] + t * nx, p[1] + t * ny, p[2] + t * nz]
};
let to_f32 = |p: [f64; 3]| -> [f32; 3] {
[p[0] as f32, p[1] as f32, p[2] as f32]
};
let off = |p: [f64; 3]| -> [f64; 3] { [p[0] + t * nx, p[1] + t * ny, p[2] + t * nz] };
let to_f32 = |p: [f64; 3]| -> [f32; 3] { [p[0] as f32, p[1] as f32, p[2] as f32] };
let mut path: Vec<[f64; 3]> = Vec::new();
let mut kv: Vec<[f64; 3]> = Vec::new();
let mut tgs: Vec<TangentGeom> = Vec::new();
@@ -322,11 +315,11 @@ impl TruckConvertible for Polyline2D {
impl Grippable for Polyline2D {
fn grips(&self) -> Vec<GripDef> {
let elev = self.elevation as f32;
let elev = self.elevation;
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, glam::DVec3::new(v.location.x, v.location.y, elev)))
.collect()
}
@@ -345,23 +338,25 @@ impl Grippable for Polyline2D {
}
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem { label: "Remove Vertex", action: GripMenuAction::RemoveVertex },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
GripMenuItem {
label: "Remove Vertex",
action: GripMenuAction::RemoveVertex,
},
]
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let n = self.vertices.len();
let elev = self.elevation;
@@ -535,11 +530,7 @@ 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,
),
glam::DVec3::new(v.position.x, v.position.y, v.position.z),
)
})
.collect()
@@ -562,23 +553,25 @@ impl Grippable for Polyline3D {
}
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem { label: "Remove Vertex", action: GripMenuAction::RemoveVertex },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
GripMenuItem {
label: "Remove Vertex",
action: GripMenuAction::RemoveVertex,
},
]
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let n = self.vertices.len();
match action {
@@ -713,7 +706,9 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::Polyline2D) -> Vec<Vec<[f32; 2
}
let p0 = [v0.location.x as f32, v0.location.y as f32];
let p1 = [v1.location.x as f32, v1.location.y as f32];
if let Some(poly) = crate::entities::common::polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32) {
if let Some(poly) =
crate::entities::common::polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32)
{
out.push(poly);
}
}
+17 -26
View File
@@ -1,5 +1,4 @@
use acadrust::entities::{RasterImage, Wipeout};
use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
@@ -42,10 +41,6 @@ fn image_corners(
]
}
fn to_f32_3(p: [f64; 3]) -> [f32; 3] {
[p[0] as f32, p[1] as f32, p[2] as f32]
}
/// Rectangle border + X diagonals — used as a placeholder for images.
fn image_wire(corners: [[f64; 3]; 4], with_x: bool) -> Vec<[f64; 3]> {
let [p0, p1, p2, p3] = corners;
@@ -139,10 +134,10 @@ impl Grippable for RasterImage {
self.size.y,
);
vec![
square_grip(0, Vec3::from(to_f32_3(corners[0]))),
center_grip(1, Vec3::from(to_f32_3(corners[1]))),
center_grip(2, Vec3::from(to_f32_3(corners[2]))),
center_grip(3, Vec3::from(to_f32_3(corners[3]))),
square_grip(0, glam::DVec3::from(corners[0])),
center_grip(1, glam::DVec3::from(corners[1])),
center_grip(2, glam::DVec3::from(corners[2])),
center_grip(3, glam::DVec3::from(corners[3])),
]
}
@@ -329,9 +324,9 @@ impl Grippable for Wipeout {
);
if is_polygon {
let ox = self.insertion_point.x as f32;
let oy = self.insertion_point.y as f32;
let oz = self.insertion_point.z as f32;
let ox = self.insertion_point.x;
let oy = self.insertion_point.y;
let oz = self.insertion_point.z;
// Same image-pixel-space → WCS mapping as `to_truck` so grips
// sit exactly on the rendered polygon vertices.
self.clip_boundary_vertices
@@ -340,13 +335,13 @@ impl Grippable for Wipeout {
.map(|(i, v)| {
let cx = v.x + self.size.x * 0.5;
let cy = self.size.y * 0.5 - v.y;
let wx = (self.u_vector.x * cx + self.v_vector.x * cy) as f32;
let wy = (self.u_vector.y * cx + self.v_vector.y * cy) as f32;
let wz = (self.u_vector.z * cx + self.v_vector.z * cy) as f32;
let wx = self.u_vector.x * cx + self.v_vector.x * cy;
let wy = self.u_vector.y * cx + self.v_vector.y * cy;
let wz = self.u_vector.z * cx + self.v_vector.z * cy;
if i == 0 {
square_grip(i, Vec3::new(ox + wx, oy + wy, oz + wz))
square_grip(i, glam::DVec3::new(ox + wx, oy + wy, oz + wz))
} else {
center_grip(i, Vec3::new(ox + wx, oy + wy, oz + wz))
center_grip(i, glam::DVec3::new(ox + wx, oy + wy, oz + wz))
}
})
.collect()
@@ -359,10 +354,10 @@ impl Grippable for Wipeout {
self.size.y,
);
vec![
square_grip(0, Vec3::from(to_f32_3(corners[0]))),
center_grip(1, Vec3::from(to_f32_3(corners[1]))),
center_grip(2, Vec3::from(to_f32_3(corners[2]))),
center_grip(3, Vec3::from(to_f32_3(corners[3]))),
square_grip(0, glam::DVec3::from(corners[0])),
center_grip(1, glam::DVec3::from(corners[1])),
center_grip(2, glam::DVec3::from(corners[2])),
center_grip(3, glam::DVec3::from(corners[3])),
]
}
}
@@ -451,11 +446,7 @@ impl PropertyEditable for Wipeout {
"wo_class_version",
self.class_version.to_string(),
),
ro(
"Clip Mode",
"wo_clip_mode",
format!("{:?}", self.clip_mode),
),
ro("Clip Mode", "wo_clip_mode", format!("{:?}", self.clip_mode)),
ro(
"Definition",
"wo_def_handle",
+10 -11
View File
@@ -1,5 +1,4 @@
use acadrust::entities::{Ray, XLine};
use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{center_grip, edit_prop as edit, square_grip};
@@ -47,13 +46,13 @@ impl Grippable for Ray {
// Grip 1: a point along the direction (changes direction)
let guide_dist = 10.0_f64;
vec![
square_grip(0, Vec3::new(bp.x as f32, bp.y as f32, bp.z as f32)),
square_grip(0, glam::DVec3::new(bp.x, bp.y, bp.z)),
center_grip(
1,
Vec3::new(
(bp.x + dir.x * guide_dist) as f32,
(bp.y + dir.y * guide_dist) as f32,
(bp.z + dir.z * guide_dist) as f32,
glam::DVec3::new(
bp.x + dir.x * guide_dist,
bp.y + dir.y * guide_dist,
bp.z + dir.z * guide_dist,
),
),
]
@@ -185,13 +184,13 @@ impl Grippable for XLine {
let dir = &self.direction;
let guide_dist = 10.0_f64;
vec![
square_grip(0, Vec3::new(bp.x as f32, bp.y as f32, bp.z as f32)),
square_grip(0, glam::DVec3::new(bp.x, bp.y, bp.z)),
center_grip(
1,
Vec3::new(
(bp.x + dir.x * guide_dist) as f32,
(bp.y + dir.y * guide_dist) as f32,
(bp.z + dir.z * guide_dist) as f32,
glam::DVec3::new(
bp.x + dir.x * guide_dist,
bp.y + dir.y * guide_dist,
bp.z + dir.z * guide_dist,
),
),
]
+9 -5
View File
@@ -31,7 +31,11 @@ fn shape_marker(
oblique_angle: f64,
) -> Vec<[f64; 3]> {
let s = size.abs().max(0.001) * 0.5;
let rx = s * if rel_x_scale.abs() < 1e-9 { 1.0 } else { rel_x_scale };
let rx = s * if rel_x_scale.abs() < 1e-9 {
1.0
} else {
rel_x_scale
};
let ry = s;
let local = [(0.0, ry), (rx, 0.0), (0.0, -ry), (-rx, 0.0)];
let (sin_r, cos_r) = (rotation.sin(), rotation.cos());
@@ -86,10 +90,10 @@ impl Grippable for Shape {
fn grips(&self) -> Vec<GripDef> {
vec![square_grip(
0,
Vec3::new(
self.insertion_point.x as f32,
self.insertion_point.y as f32,
self.insertion_point.z as f32,
glam::DVec3::new(
self.insertion_point.x,
self.insertion_point.y,
self.insertion_point.z,
),
)]
}
+8 -4
View File
@@ -18,6 +18,10 @@ fn v3(v: &acadrust::types::Vector3) -> [f64; 3] {
[v.x, v.y, v.z]
}
fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 {
glam::DVec3::new(v.x, v.y, v.z)
}
fn v3f32(v: &acadrust::types::Vector3) -> [f32; 3] {
[v.x as f32, v.y as f32, v.z as f32]
}
@@ -66,10 +70,10 @@ impl TruckConvertible for Solid {
impl Grippable for Solid {
fn grips(&self) -> Vec<GripDef> {
vec![
square_grip(0, Vec3::from(v3f32(&self.first_corner))),
square_grip(1, Vec3::from(v3f32(&self.second_corner))),
square_grip(2, Vec3::from(v3f32(&self.third_corner))),
square_grip(3, Vec3::from(v3f32(&self.fourth_corner))),
square_grip(0, dvec3(&self.first_corner)),
square_grip(1, dvec3(&self.second_corner)),
square_grip(2, dvec3(&self.third_corner)),
square_grip(3, dvec3(&self.fourth_corner)),
]
}
+7 -11
View File
@@ -15,8 +15,8 @@ use crate::scene::object::{GripApply, GripDef, PropSection};
// ── shared helpers ────────────────────────────────────────────────────────────
fn por_to_vec3(v: &acadrust::types::Vector3) -> Vec3 {
Vec3::new(v.x as f32, v.y as f32, v.z as f32)
fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 {
glam::DVec3::new(v.x, v.y, v.z)
}
fn translate_wires(wires: &mut Vec<acadrust::entities::Wire>, d: Vec3) {
@@ -44,7 +44,7 @@ fn acis_size_str(has_data: bool, sat_len: usize, sab_len: usize, is_binary: bool
impl Grippable for Solid3D {
fn grips(&self) -> Vec<GripDef> {
vec![center_grip(0, por_to_vec3(&self.point_of_reference))]
vec![center_grip(0, dvec3(&self.point_of_reference))]
}
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
@@ -120,7 +120,7 @@ impl PropertyEditable for Solid3D {
impl Grippable for Region {
fn grips(&self) -> Vec<GripDef> {
vec![center_grip(0, por_to_vec3(&self.point_of_reference))]
vec![center_grip(0, dvec3(&self.point_of_reference))]
}
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
@@ -188,7 +188,7 @@ impl PropertyEditable for Region {
impl Grippable for Body {
fn grips(&self) -> Vec<GripDef> {
vec![center_grip(0, por_to_vec3(&self.point_of_reference))]
vec![center_grip(0, dvec3(&self.point_of_reference))]
}
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
@@ -260,9 +260,9 @@ impl PropertyEditable for Body {
// to repeat a three-arm `match entity` block at every callsite — the
// helpers below collapse those to a single call.
use acadrust::{types::Vector3, EntityType};
use crate::scene::mesh_model::MeshLodSet;
use crate::scene::solid3d_tess;
use acadrust::{types::Vector3, EntityType};
/// `point_of_reference` of an ACIS-backed volume entity, if applicable.
pub fn point_of_reference(e: &EntityType) -> Option<&Vector3> {
@@ -288,11 +288,7 @@ pub fn fallback_wires(e: &EntityType) -> Option<&[acadrust::entities::Wire]> {
/// Run the appropriate `solid3d_tess::tessellate_*` for the entity,
/// returning `None` for non-volume entities or when the kernel fails.
pub fn tessellate_volume(
e: &EntityType,
color: [f32; 4],
facet_res: f64,
) -> Option<MeshLodSet> {
pub fn tessellate_volume(e: &EntityType, color: [f32; 4], facet_res: f64) -> Option<MeshLodSet> {
match e {
EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color, facet_res),
EntityType::Region(r) => solid3d_tess::tessellate_region(r, color, facet_res),
+23 -25
View File
@@ -1,5 +1,4 @@
use acadrust::entities::Spline;
use glam::Vec3;
use truck_modeling::{
base::{BoundedCurve, ParametricCurve, Vector4},
builder, BSplineCurve, Curve, Edge, KnotVec, NurbsCurve, Point3, Wire,
@@ -64,10 +63,7 @@ fn to_truck(spl: &Spline) -> TruckEntity {
} else {
&spl.control_points
};
let key_vertices: Vec<[f64; 3]> = snap_source
.iter()
.map(|p| [p.x, p.y, p.z])
.collect();
let key_vertices: Vec<[f64; 3]> = snap_source.iter().map(|p| [p.x, p.y, p.z]).collect();
let is_closed = spl.flags.closed || spl.flags.periodic;
let gap = {
@@ -106,7 +102,7 @@ fn grips(spline: &Spline) -> Vec<GripDef> {
.control_points
.iter()
.enumerate()
.map(|(i, p)| square_grip(i, Vec3::new(p.x as f32, p.y as f32, p.z as f32)))
.map(|(i, p)| square_grip(i, glam::DVec3::new(p.x, p.y, p.z)))
.collect()
}
@@ -168,23 +164,28 @@ impl crate::entities::traits::Grippable for Spline {
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem { label: "Remove Vertex", action: GripMenuAction::RemoveVertex },
GripMenuItem { label: "Refine Vertices", action: GripMenuAction::RefineVertices },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
GripMenuItem {
label: "Remove Vertex",
action: GripMenuAction::RemoveVertex,
},
GripMenuItem {
label: "Refine Vertices",
action: GripMenuAction::RefineVertices,
},
]
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let n = self.control_points.len();
let min_cv = (self.degree as usize).saturating_add(1).max(2);
@@ -203,7 +204,8 @@ impl crate::entities::traits::Grippable for Spline {
);
self.control_points.insert(i1, mid);
if !self.weights.is_empty() && self.weights.len() == n {
let w = (self.weights[grip_id] + self.weights[i1.min(self.weights.len() - 1)]) * 0.5;
let w = (self.weights[grip_id] + self.weights[i1.min(self.weights.len() - 1)])
* 0.5;
self.weights.insert(i1, w);
}
// Clear knots so to_truck rebuilds a uniform knot vector
@@ -255,10 +257,7 @@ impl crate::entities::traits::Grippable for Spline {
}
impl crate::entities::traits::PropertyEditable for Spline {
fn geometry_properties(
&self,
_text_style_names: &[String],
) -> PropSection {
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
properties(self)
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
@@ -271,4 +270,3 @@ impl crate::entities::traits::Transformable for Spline {
apply_transform(self, t);
}
}
+164 -102
View File
@@ -3,12 +3,14 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{ro_prop as ro, square_grip};
use crate::entities::text_support::{layout_mtext, MTextRenderOpts, MTextVAnchor, ResolvedTextStyle};
use crate::entities::text_support::{
layout_mtext, MTextRenderOpts, MTextVAnchor, ResolvedTextStyle,
};
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;
fn v3(v: &acadrust::types::Vector3) -> Vec3 {
Vec3::new(v.x as f32, v.y as f32, v.z as f32)
@@ -61,7 +63,11 @@ impl TruckConvertible for Table {
// twice we coalesce the segments by their (start, end) coordinates.
use rustc_hash::FxHashSet as HashSet;
let mut emitted: HashSet<(i32, i32, i32, i32)> = HashSet::default();
let try_add = |a: Vec3, b: Vec3, vis: bool, emitted: &mut HashSet<(i32, i32, i32, i32)>, pts: &mut Vec<[f32; 3]>| {
let try_add = |a: Vec3,
b: Vec3,
vis: bool,
emitted: &mut HashSet<(i32, i32, i32, i32)>,
pts: &mut Vec<[f32; 3]>| {
if !vis {
return;
}
@@ -90,11 +96,9 @@ impl TruckConvertible for Table {
.unwrap_or(row_top + row.height as f32);
for (ci, cell) in row.cells.iter().enumerate() {
let col_left = col_offsets[ci];
let col_right = col_offsets
.get(ci + 1)
.copied()
.unwrap_or(col_left
+ self.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0));
let col_right = col_offsets.get(ci + 1).copied().unwrap_or(
col_left + self.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0),
);
// Default to visible when no style override is present.
let (top_vis, right_vis, bottom_vis, left_vis) = cell
.style
@@ -160,18 +164,17 @@ impl TruckConvertible for Table {
};
// Build a ResolvedTextStyle for the cell — needed by the shared MText
// pipeline so inline `\W`, `\Q`, etc. compose with the style baseline.
let resolved_style_for_handle = |handle: Option<acadrust::Handle>,
font_name: String|
-> ResolvedTextStyle {
let style = handle.and_then(|h| lookup_style(h));
ResolvedTextStyle {
font_name,
width_factor: style.map(|s| s.width_factor as f32).unwrap_or(1.0),
oblique_angle: style.map(|s| s.oblique_angle as f32).unwrap_or(0.0),
is_backward: style.map(|s| s.is_backward()).unwrap_or(false),
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
}
};
let resolved_style_for_handle =
|handle: Option<acadrust::Handle>, font_name: String| -> ResolvedTextStyle {
let style = handle.and_then(|h| lookup_style(h));
ResolvedTextStyle {
font_name,
width_factor: style.map(|s| s.width_factor as f32).unwrap_or(1.0),
oblique_angle: style.map(|s| s.oblique_angle as f32).unwrap_or(0.0),
is_backward: style.map(|s| s.is_backward()).unwrap_or(false),
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
}
};
for (ri, row) in self.rows.iter().enumerate() {
let row_top = row_offsets[ri];
@@ -182,20 +185,19 @@ impl TruckConvertible for Table {
let row_mid = (row_top + row_bot) * 0.5;
// Pick the appropriate row_style from TableStyle for this row's role.
let row_style: Option<&acadrust::objects::RowCellStyle> = table_style
.map(|ts| {
let kind = match (title_suppressed, header_suppressed, ri) {
(false, _, 0) => 0, // title
(false, false, 1) => 1, // header
(true, false, 0) => 1, // header pulled up
_ => 2, // data
};
match kind {
0 => &ts.title_row_style,
1 => &ts.header_row_style,
_ => &ts.data_row_style,
}
});
let row_style: Option<&acadrust::objects::RowCellStyle> = table_style.map(|ts| {
let kind = match (title_suppressed, header_suppressed, ri) {
(false, _, 0) => 0, // title
(false, false, 1) => 1, // header
(true, false, 0) => 1, // header pulled up
_ => 2, // data
};
match kind {
0 => &ts.title_row_style,
1 => &ts.header_row_style,
_ => &ts.data_row_style,
}
});
for (ci, cell) in row.cells.iter().enumerate() {
let text = cell.text_value();
@@ -212,7 +214,12 @@ impl TruckConvertible for Table {
let cell_h = content
.map(|c| c.text_height)
.filter(|h| *h > 1e-6)
.or_else(|| cell.style.as_ref().map(|s| s.text_height).filter(|h| *h > 1e-6))
.or_else(|| {
cell.style
.as_ref()
.map(|s| s.text_height)
.filter(|h| *h > 1e-6)
})
.or_else(|| row_style.map(|s| s.text_height).filter(|h| *h > 1e-6))
.map(|h| h as f32)
.unwrap_or(0.18);
@@ -223,8 +230,7 @@ impl TruckConvertible for Table {
.and_then(|c| c.text_style_handle)
.or_else(|| cell.style.as_ref().and_then(|s| s.text_style_handle))
.or_else(|| row_style.and_then(|s| s.text_style_handle));
let font_owned =
font_for_handle(style_handle).unwrap_or_else(|| "txt".to_string());
let font_owned = font_for_handle(style_handle).unwrap_or_else(|| "txt".to_string());
let resolved = resolved_style_for_handle(style_handle, font_owned);
// Alignment resolution: cell.style.alignment (1-9) overrides;
@@ -254,8 +260,7 @@ impl TruckConvertible for Table {
let text_origin = origin + h * x_offset + v_down * y_offset;
// Content rotation (radians) on top of table cell rotation.
let rot = content.map(|c| c.rotation as f32).unwrap_or(0.0)
+ cell.rotation as f32;
let rot = content.map(|c| c.rotation as f32).unwrap_or(0.0) + cell.rotation as f32;
let layout = layout_mtext(&MTextRenderOpts {
value: text,
insertion: [text_origin.x as f64, text_origin.y as f64, origin.z as f64],
@@ -378,12 +383,13 @@ pub fn tessellate_table(
};
let v_down = Vec3::new(h.y, -h.x, 0.0);
// Flow direction: `Up` stacks rows upward instead of downward.
let table_style: Option<&acadrust::objects::TableStyle> = tab.table_style_handle.and_then(|h| {
document.objects.get(&h).and_then(|obj| match obj {
acadrust::objects::ObjectType::TableStyle(ts) => Some(ts),
_ => None,
})
});
let table_style: Option<&acadrust::objects::TableStyle> =
tab.table_style_handle.and_then(|h| {
document.objects.get(&h).and_then(|obj| match obj {
acadrust::objects::ObjectType::TableStyle(ts) => Some(ts),
_ => None,
})
});
let flow_up = matches!(
table_style.map(|t| t.flow_direction),
Some(acadrust::objects::TableFlowDirection::Up)
@@ -411,7 +417,9 @@ pub fn tessellate_table(
let title_suppressed = table_style.map(|t| t.title_suppressed).unwrap_or(false);
let header_suppressed = table_style.map(|t| t.header_suppressed).unwrap_or(false);
let h_margin = table_style.map(|t| t.horizontal_margin as f32).unwrap_or(0.0);
let h_margin = table_style
.map(|t| t.horizontal_margin as f32)
.unwrap_or(0.0);
let v_margin = table_style.map(|t| t.vertical_margin as f32).unwrap_or(0.0);
let lookup_style = |hh: acadrust::Handle| -> Option<&acadrust::tables::TextStyle> {
@@ -444,28 +452,27 @@ pub fn tessellate_table(
let mut emitted: rustc_hash::FxHashSet<(i32, i32, i32, i32)> = rustc_hash::FxHashSet::default();
let sel_col = WireModel::SELECTED;
let mut add_edge =
|a: Vec3, b: Vec3, col: [f32; 4], lw: f32| {
let k = (
(a.x * 1000.0) as i32,
(a.y * 1000.0) as i32,
(b.x * 1000.0) as i32,
(b.y * 1000.0) as i32,
);
let kr = (k.2, k.3, k.0, k.1);
if emitted.contains(&k) || emitted.contains(&kr) {
return;
}
emitted.insert(k);
let entry = borders
.entry((key4(col), (lw * 100.0) as u32))
.or_insert_with(|| (col, lw, Vec::new()));
if !entry.2.is_empty() {
entry.2.push([f32::NAN; 3]);
}
entry.2.push(rel(a));
entry.2.push(rel(b));
};
let mut add_edge = |a: Vec3, b: Vec3, col: [f32; 4], lw: f32| {
let k = (
(a.x * 1000.0) as i32,
(a.y * 1000.0) as i32,
(b.x * 1000.0) as i32,
(b.y * 1000.0) as i32,
);
let kr = (k.2, k.3, k.0, k.1);
if emitted.contains(&k) || emitted.contains(&kr) {
return;
}
emitted.insert(k);
let entry = borders
.entry((key4(col), (lw * 100.0) as u32))
.or_insert_with(|| (col, lw, Vec::new()));
if !entry.2.is_empty() {
entry.2.push([f32::NAN; 3]);
}
entry.2.push(rel(a));
entry.2.push(rel(b));
};
for (ri, row) in tab.rows.iter().enumerate() {
let row_top = row_offsets[ri];
@@ -507,7 +514,10 @@ pub fn tessellate_table(
};
if fill_on {
let col = resolve_col(&fill_color, entity_color);
let buf = &mut fills.entry(key4(col)).or_insert_with(|| (col, Vec::new())).1;
let buf = &mut fills
.entry(key4(col))
.or_insert_with(|| (col, Vec::new()))
.1;
for v in [bl, br_, tr, bl, tr, tl] {
buf.push(rel(v));
}
@@ -526,7 +536,11 @@ pub fn tessellate_table(
};
(
!b.invisible,
if selected { sel_col } else { resolve_col(&b.color, entity_color) },
if selected {
sel_col
} else {
resolve_col(&b.color, entity_color)
},
lw_px(&b.line_weight),
)
} else if let Some(rs) = row_style {
@@ -538,21 +552,37 @@ pub fn tessellate_table(
};
(
!b.is_invisible,
if selected { sel_col } else { resolve_col(&b.color, entity_color) },
if selected {
sel_col
} else {
resolve_col(&b.color, entity_color)
},
lw_px(&b.line_weight),
)
} else {
(true, if selected { sel_col } else { entity_color }, line_weight_px)
(
true,
if selected { sel_col } else { entity_color },
line_weight_px,
)
}
};
let (tv, tc, tw) = edge(0);
if tv { add_edge(tl, tr, tc, tw); }
if tv {
add_edge(tl, tr, tc, tw);
}
let (rv, rc, rw) = edge(1);
if rv { add_edge(tr, br_, rc, rw); }
if rv {
add_edge(tr, br_, rc, rw);
}
let (bv, bc, bw) = edge(2);
if bv { add_edge(bl, br_, bc, bw); }
if bv {
add_edge(bl, br_, bc, bw);
}
let (lv, lc, lw) = edge(3);
if lv { add_edge(tl, bl, lc, lw); }
if lv {
add_edge(tl, bl, lc, lw);
}
// ── Text ──────────────────────────────────────────────────────
let text = cell.text_value();
@@ -563,12 +593,25 @@ pub fn tessellate_table(
let cell_h = content
.map(|c| c.text_height)
.filter(|h| *h > 1e-6)
.or_else(|| cell.style.as_ref().map(|s| s.text_height).filter(|h| *h > 1e-6))
.or_else(|| {
cell.style
.as_ref()
.map(|s| s.text_height)
.filter(|h| *h > 1e-6)
})
.or_else(|| row_style.map(|s| s.text_height).filter(|h| *h > 1e-6))
.map(|h| h as f32)
.unwrap_or(0.18);
let m_h = if h_margin > 1e-6 { h_margin } else { cell_h * 0.5 };
let m_v = if v_margin > 1e-6 { v_margin } else { cell_h * 0.5 };
let m_h = if h_margin > 1e-6 {
h_margin
} else {
cell_h * 0.5
};
let m_v = if v_margin > 1e-6 {
v_margin
} else {
cell_h * 0.5
};
let style_handle = content
.and_then(|c| c.text_style_handle)
@@ -621,7 +664,10 @@ pub fn tessellate_table(
} else {
entity_color
};
let buf = &mut texts.entry(key4(tcol)).or_insert_with(|| (tcol, Vec::new())).1;
let buf = &mut texts
.entry(key4(tcol))
.or_insert_with(|| (tcol, Vec::new()))
.1;
for ts in &layout.strokes {
let sx = ts.origin[0] as f32;
let sy = ts.origin[1] as f32;
@@ -641,25 +687,26 @@ pub fn tessellate_table(
}
let name = tab.common.handle.value().to_string();
let mk = |color: [f32; 4], points: Vec<[f32; 3]>, fill_tris: Vec<[f32; 3]>, lw: f32| -> WireModel {
WireModel {
name: name.clone(),
points,
color,
selected,
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: lw,
aci: 0,
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris,
}
};
let mk =
|color: [f32; 4], points: Vec<[f32; 3]>, fill_tris: Vec<[f32; 3]>, lw: f32| -> WireModel {
WireModel {
name: name.clone(),
points,
color,
selected,
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: lw,
aci: 0,
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris,
}
};
let mut out: Vec<WireModel> = Vec::new();
// Fills first (drawn under borders/text).
@@ -683,7 +730,14 @@ pub fn tessellate_table(
impl Grippable for Table {
fn grips(&self) -> Vec<GripDef> {
vec![square_grip(0, v3(&self.insertion_point))]
vec![square_grip(
0,
glam::DVec3::new(
self.insertion_point.x,
self.insertion_point.y,
self.insertion_point.z,
),
)]
}
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
@@ -742,7 +796,11 @@ impl PropertyEditable for Table {
"tbl_block_rec_handle",
fmt_h(&self.block_record_handle),
),
ro("Data Version", "tbl_data_version", self.data_version.to_string()),
ro(
"Data Version",
"tbl_data_version",
self.data_version.to_string(),
),
ro(
"Value Flags",
"tbl_value_flags",
@@ -756,7 +814,11 @@ impl PropertyEditable for Table {
ro(
"Override Border Color",
"tbl_override_border_color",
if self.override_border_color { "Yes" } else { "No" },
if self.override_border_color {
"Yes"
} else {
"No"
},
),
ro(
"Override Border LW",
+23 -17
View File
@@ -141,7 +141,11 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
&t.value,
);
TruckEntity {
object: TruckObject::Text(vec![TextStroke { strokes, origin, color: None }]),
object: TruckObject::Text(vec![TextStroke {
strokes,
origin,
color: None,
}]),
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],
@@ -150,10 +154,10 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
}
fn grips(t: &Text) -> Vec<GripDef> {
let p = Vec3::new(
t.insertion_point.x as f32,
t.insertion_point.y as f32,
t.insertion_point.z as f32,
let p = glam::DVec3::new(
t.insertion_point.x,
t.insertion_point.y,
t.insertion_point.z,
);
vec![square_grip(0, p)]
}
@@ -316,23 +320,25 @@ impl Grippable for Text {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Move with Text", action: GripMenuAction::MoveWithText },
GripMenuItem { label: "Rotate", action: GripMenuAction::RotateText },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Move with Text",
action: GripMenuAction::MoveWithText,
},
GripMenuItem {
label: "Rotate",
action: GripMenuAction::RotateText,
},
]
}
fn apply_grip_menu(
&mut self,
_grip_id: usize,
_action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, _grip_id: usize, _action: crate::scene::object::GripMenuAction) {
// Move-with-Text falls through to Stretch (single grip moves
// the whole text); Rotate needs a follow-up angle handled by
// `apply_grip_menu_value`.
+9 -5
View File
@@ -244,7 +244,11 @@ impl TruckConvertible for Tolerance {
let origin = [self.insertion_point.x, self.insertion_point.y];
Some(TruckEntity {
object: TruckObject::Text(vec![TextStroke { strokes, origin, color: None }]),
object: TruckObject::Text(vec![TextStroke {
strokes,
origin,
color: None,
}]),
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],
@@ -259,10 +263,10 @@ impl Grippable for Tolerance {
fn grips(&self) -> Vec<GripDef> {
vec![square_grip(
0,
Vec3::new(
self.insertion_point.x as f32,
self.insertion_point.y as f32,
self.insertion_point.z as f32,
glam::DVec3::new(
self.insertion_point.x,
self.insertion_point.y,
self.insertion_point.z,
),
)]
}
+7 -9
View File
@@ -46,10 +46,7 @@ impl TruckConvertible for Underlay {
if !self.clip_boundary_vertices.is_empty() {
// Draw clip boundary polygon + close it.
let world_verts = self.world_clip_boundary();
let mut pts: Vec<[f64; 3]> = world_verts
.iter()
.map(|v| [v.x, v.y, v.z])
.collect();
let mut pts: Vec<[f64; 3]> = world_verts.iter().map(|v| [v.x, v.y, v.z]).collect();
// Close polygon.
if let Some(&first) = pts.first() {
pts.push(first);
@@ -81,16 +78,17 @@ impl TruckConvertible for Underlay {
impl Grippable for Underlay {
fn grips(&self) -> Vec<GripDef> {
let origin = Vec3::from(v3f32(&self.insertion_point));
let origin = glam::DVec3::new(
self.insertion_point.x,
self.insertion_point.y,
self.insertion_point.z,
);
let mut grips = vec![square_grip(0, origin)];
if !self.clip_boundary_vertices.is_empty() {
let world_verts = self.world_clip_boundary();
for (i, v) in world_verts.iter().enumerate() {
grips.push(center_grip(
i + 1,
Vec3::new(v.x as f32, v.y as f32, v.z as f32),
));
grips.push(center_grip(i + 1, glam::DVec3::new(v.x, v.y, v.z)));
}
}
+16 -27
View File
@@ -1,5 +1,4 @@
use acadrust::entities::{Viewport, ViewportRenderMode};
use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{
@@ -105,27 +104,22 @@ const STD_VIEWS: &[&str] = &[
];
fn grips(vp: &Viewport) -> Vec<GripDef> {
let cx = vp.center.x as f32;
let cy = vp.center.y as f32;
let cz = vp.center.z as f32;
let hw = (vp.width / 2.0) as f32;
let hh = (vp.height / 2.0) as f32;
let (cx, cy, cz) = (vp.center.x, vp.center.y, vp.center.z);
let hw = vp.width / 2.0;
let hh = vp.height / 2.0;
vec![
center_grip(0, Vec3::new(cx, cy, cz)),
square_grip(1, Vec3::new(cx + hw, cy + hh, cz)),
square_grip(2, Vec3::new(cx - hw, cy + hh, cz)),
square_grip(3, Vec3::new(cx - hw, cy - hh, cz)),
square_grip(4, Vec3::new(cx + hw, cy - hh, cz)),
center_grip(0, glam::DVec3::new(cx, cy, cz)),
square_grip(1, glam::DVec3::new(cx + hw, cy + hh, cz)),
square_grip(2, glam::DVec3::new(cx - hw, cy + hh, cz)),
square_grip(3, glam::DVec3::new(cx - hw, cy - hh, cz)),
square_grip(4, glam::DVec3::new(cx + hw, cy - hh, cz)),
]
}
fn properties(vp: &Viewport) -> PropSection {
let scale_opts: Vec<String> = STANDARD_SCALES.iter().map(|(s, _)| s.to_string()).collect();
let effective_scale = crate::scene::vp_effective_scale(
vp.custom_scale,
vp.view_height,
vp.height,
);
let effective_scale =
crate::scene::vp_effective_scale(vp.custom_scale, vp.view_height, vp.height);
let current_scale_label = scale_label(effective_scale);
let view_opts: Vec<String> = STD_VIEWS.iter().map(|s| s.to_string()).collect();
@@ -256,11 +250,7 @@ fn properties(vp: &Viewport) -> PropSection {
edit("Twist Angle", "vp_twist", vp.twist_angle.to_degrees()),
edit("Front Clip Z", "vp_front_clip", vp.front_clip_z),
edit("Back Clip Z", "vp_back_clip", vp.back_clip_z),
edit(
"Circle Sides",
"vp_circle_sides",
vp.circle_sides as f64,
),
edit("Circle Sides", "vp_circle_sides", vp.circle_sides as f64),
ro(
"Grid Flags",
"vp_grid_flags",
@@ -276,11 +266,7 @@ fn properties(vp: &Viewport) -> PropSection {
format!("{:X}", vp.base_ucs_handle.value())
},
),
ro(
"UCS Ortho",
"vp_ucs_ortho",
vp.ucs_ortho_type.to_string(),
),
ro("UCS Ortho", "vp_ucs_ortho", vp.ucs_ortho_type.to_string()),
ro(
"Background",
"vp_background_handle",
@@ -560,7 +546,10 @@ fn apply_transform(vp: &mut Viewport, t: &EntityTransform) {
crate::impl_entity_basics!(Viewport);
impl crate::entities::traits::FallbackTess for Viewport {
fn fallback_geometry(&self, world_offset: [f64; 3]) -> crate::scene::tess_util::FallbackGeometry {
fn fallback_geometry(
&self,
world_offset: [f64; 3],
) -> crate::scene::tess_util::FallbackGeometry {
let [ox, oy, oz] = world_offset;
let cx = (self.center.x - ox) as f32;
let cy = (self.center.y - oy) as f32;
+8 -8
View File
@@ -40,7 +40,7 @@ pub fn grips_to_screen(
grips
.iter()
.map(|g| {
let ndc = view_proj.project_point3(g.world);
let ndc = view_proj.project_point3(g.world.as_vec3());
let screen = Point::new(
bounds.x + (ndc.x + 1.0) * 0.5 * bounds.width,
bounds.y + (1.0 - ndc.y) * 0.5 * bounds.height,
@@ -64,8 +64,8 @@ pub fn grips_to_screen_paper(
.iter()
.map(|g| {
let screen = Point::new(
(g.world.x - tx + half_w) / (2.0 * half_w) * bounds.width,
(ty + half_h - g.world.y) / (2.0 * half_h) * bounds.height,
(g.world.x as f32 - tx + half_w) / (2.0 * half_w) * bounds.width,
(ty + half_h - g.world.y as f32) / (2.0 * half_h) * bounds.height,
);
(g.id, screen, g.is_midpoint, g.shape, g.dir)
})
@@ -87,15 +87,15 @@ pub fn find_hit_grip_paper(
for g in grips {
let screen = Point::new(
(g.world.x - tx + half_w) / (2.0 * half_w) * bounds.width,
(ty + half_h - g.world.y) / (2.0 * half_h) * bounds.height,
(g.world.x as f32 - tx + half_w) / (2.0 * half_w) * bounds.width,
(ty + half_h - g.world.y as f32) / (2.0 * half_h) * bounds.height,
);
let dx = screen.x - cursor.x;
let dy = screen.y - cursor.y;
let d = (dx * dx + dy * dy).sqrt();
if d < best_dist {
best_dist = d;
best = Some((g.id, g.is_midpoint, g.world));
best = Some((g.id, g.is_midpoint, g.world.as_vec3()));
}
}
best
@@ -113,7 +113,7 @@ pub fn find_hit_grip(
let mut best: Option<(usize, bool, Vec3)> = None;
for g in grips {
let ndc = view_proj.project_point3(g.world);
let ndc = view_proj.project_point3(g.world.as_vec3());
let screen = Point::new(
(ndc.x + 1.0) * 0.5 * bounds.width,
(1.0 - ndc.y) * 0.5 * bounds.height,
@@ -123,7 +123,7 @@ pub fn find_hit_grip(
let d = (dx * dx + dy * dy).sqrt();
if d < best_dist {
best_dist = d;
best = Some((g.id, g.is_midpoint, g.world));
best = Some((g.id, g.is_midpoint, g.world.as_vec3()));
}
}
best
+6 -2
View File
@@ -75,8 +75,12 @@ pub enum GripShape {
pub struct GripDef {
/// Object-local identifier (stable index, unique per object instance).
pub id: usize,
/// World-space position of the grip.
pub world: Vec3,
/// World-space position of the grip, in f64. Entity coordinates can sit at
/// UTM magnitudes (1e7); casting to f32 before the world-offset subtraction
/// loses ~1 drawing unit and draws the grip visibly off the wire. Producers
/// fill this straight from the f64 entity data; the offset is subtracted in
/// f64 and only then cast for screen-space math.
pub world: glam::DVec3,
/// `true` → midpoint / centre grip (drags the whole shape).
/// `false` → endpoint / vertex grip (moves a single point).
pub is_midpoint: bool,