forked from 1b-agent/opencadstudio-upstream
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:
1 parent
ed29933d87
commit
25715d1b03
33 files changed
+898
-817
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
)]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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`.
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
)]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user