2026-04-02 19:06:20 +03:00
|
|
|
use acadrust::entities::MLine;
|
|
|
|
|
|
|
|
|
|
use crate::command::EntityTransform;
|
|
|
|
|
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip};
|
|
|
|
|
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
2026-06-16 11:33:46 +03:00
|
|
|
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
|
|
|
|
|
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
|
|
|
|
use crate::scene::model::wire_model::SnapHint;
|
2026-04-02 19:06:20 +03:00
|
|
|
|
2026-07-16 18:44:15 +03:00
|
|
|
/// One drawn line of a multiline: the polyline for a single style element (or
|
|
|
|
|
/// an end cap), tagged with the element's colour and linetype so the
|
|
|
|
|
/// tessellator can colour-bin and dash each one independently.
|
|
|
|
|
pub struct MLineLine {
|
|
|
|
|
pub points: Vec<[f64; 3]>,
|
|
|
|
|
pub color: acadrust::types::Color,
|
|
|
|
|
pub linetype: String,
|
|
|
|
|
}
|
2026-04-02 19:06:20 +03:00
|
|
|
|
2026-07-16 18:44:15 +03:00
|
|
|
/// Resolve a multiline into its per-element parallel lines in WCS.
|
|
|
|
|
///
|
|
|
|
|
/// Geometry comes from the referenced MLINESTYLE (element offsets, the
|
|
|
|
|
/// justification shift and the entity scale) rather than a fixed ±scale/2 guess,
|
|
|
|
|
/// so a custom style's offsets, colours and linetypes render the way the drawing
|
|
|
|
|
/// intends. Falls back to a ±0.5 two-line layout only when no MLINESTYLE can be
|
|
|
|
|
/// resolved (e.g. the style object is missing).
|
|
|
|
|
pub fn mline_lines(m: &MLine, document: &acadrust::CadDocument) -> Vec<MLineLine> {
|
|
|
|
|
use acadrust::entities::{MLineFlags, MLineJustification};
|
|
|
|
|
use acadrust::objects::ObjectType;
|
|
|
|
|
use acadrust::types::Color;
|
2026-04-02 19:06:20 +03:00
|
|
|
|
2026-07-16 18:44:15 +03:00
|
|
|
if m.vertices.is_empty() {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
2026-04-02 19:06:20 +03:00
|
|
|
|
2026-07-16 18:44:15 +03:00
|
|
|
// MLINESTYLE lookup: prefer the hard-pointer handle, fall back to the name.
|
|
|
|
|
let style = m
|
|
|
|
|
.style_handle
|
|
|
|
|
.and_then(|h| match document.objects.get(&h) {
|
|
|
|
|
Some(ObjectType::MLineStyle(s)) => Some(s),
|
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.or_else(|| {
|
|
|
|
|
document.objects.values().find_map(|o| match o {
|
|
|
|
|
ObjectType::MLineStyle(s) if s.name.eq_ignore_ascii_case(&m.style_name) => Some(s),
|
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
});
|
2026-04-02 19:06:20 +03:00
|
|
|
|
2026-07-16 18:44:15 +03:00
|
|
|
// (offset, colour, linetype) per element.
|
|
|
|
|
let elems: Vec<(f64, Color, String)> = match style {
|
|
|
|
|
Some(s) if !s.elements.is_empty() => s
|
|
|
|
|
.elements
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|e| (e.offset, e.color, e.linetype.clone()))
|
|
|
|
|
.collect(),
|
|
|
|
|
_ => vec![
|
|
|
|
|
(0.5, Color::ByLayer, "ByLayer".to_string()),
|
|
|
|
|
(-0.5, Color::ByLayer, "ByLayer".to_string()),
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Justification shifts every element so the picked path runs along the top /
|
|
|
|
|
// centre / bottom element of the style.
|
|
|
|
|
let mut lo = f64::INFINITY;
|
|
|
|
|
let mut hi = f64::NEG_INFINITY;
|
|
|
|
|
for (o, _, _) in &elems {
|
|
|
|
|
lo = lo.min(*o);
|
|
|
|
|
hi = hi.max(*o);
|
|
|
|
|
}
|
|
|
|
|
let shift = match m.justification {
|
|
|
|
|
MLineJustification::Top => -hi,
|
|
|
|
|
MLineJustification::Bottom => -lo,
|
|
|
|
|
MLineJustification::Zero => 0.0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let scale = m.scale_factor;
|
|
|
|
|
let closed = m.flags.contains(MLineFlags::CLOSED);
|
|
|
|
|
|
|
|
|
|
// Offset a vertex along its miter direction by `d` drawing units.
|
|
|
|
|
let off = |vi: usize, d: f64| -> [f64; 3] {
|
|
|
|
|
let v = &m.vertices[vi];
|
|
|
|
|
[
|
|
|
|
|
v.position.x + v.miter.x * d,
|
|
|
|
|
v.position.y + v.miter.y * d,
|
|
|
|
|
v.position.z + v.miter.z * d,
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut out: Vec<MLineLine> = Vec::with_capacity(elems.len() + 2);
|
|
|
|
|
for (offset, color, linetype) in &elems {
|
|
|
|
|
let d = (*offset + shift) * scale;
|
|
|
|
|
let mut pts: Vec<[f64; 3]> = (0..m.vertices.len()).map(|i| off(i, d)).collect();
|
|
|
|
|
if closed && m.vertices.len() >= 2 {
|
|
|
|
|
pts.push(off(0, d));
|
2026-04-02 19:06:20 +03:00
|
|
|
}
|
2026-07-16 18:44:15 +03:00
|
|
|
out.push(MLineLine {
|
|
|
|
|
points: pts,
|
|
|
|
|
color: *color,
|
|
|
|
|
linetype: linetype.clone(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-04-02 19:06:20 +03:00
|
|
|
|
2026-07-16 18:44:15 +03:00
|
|
|
// End caps: a segment across the full style width at the first / last vertex,
|
|
|
|
|
// drawn only when the style requests square caps, the style has width, and
|
|
|
|
|
// the multiline is open.
|
|
|
|
|
if let Some(s) = style {
|
|
|
|
|
let d_lo = (lo + shift) * scale;
|
|
|
|
|
let d_hi = (hi + shift) * scale;
|
|
|
|
|
if (d_hi - d_lo).abs() > 1e-9 && !closed {
|
|
|
|
|
let mut cap = |vi: usize| {
|
|
|
|
|
out.push(MLineLine {
|
|
|
|
|
points: vec![off(vi, d_lo), off(vi, d_hi)],
|
|
|
|
|
color: Color::ByLayer,
|
|
|
|
|
linetype: "ByLayer".to_string(),
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
if s.flags.start_square_cap {
|
|
|
|
|
cap(0);
|
|
|
|
|
}
|
|
|
|
|
if s.flags.end_square_cap {
|
|
|
|
|
cap(m.vertices.len() - 1);
|
2026-04-02 19:06:20 +03:00
|
|
|
}
|
2026-07-16 18:44:15 +03:00
|
|
|
}
|
|
|
|
|
}
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
|
2026-07-16 18:44:15 +03:00
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TruckConvertible for MLine {
|
|
|
|
|
fn to_truck(&self, document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
|
|
|
|
if self.vertices.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NaN-separated flat list of every element line (single-colour path used
|
|
|
|
|
// by pick and the edit commands; the coloured render is built in
|
|
|
|
|
// `tessellate`, which special-cases MLINE).
|
|
|
|
|
let lines = mline_lines(self, document);
|
|
|
|
|
let mut pts: Vec<[f64; 3]> = Vec::new();
|
|
|
|
|
for (i, l) in lines.iter().enumerate() {
|
|
|
|
|
if i > 0 {
|
|
|
|
|
pts.push([f64::NAN; 3]);
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
}
|
2026-07-16 18:44:15 +03:00
|
|
|
pts.extend_from_slice(&l.points);
|
2026-04-02 19:06:20 +03:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:47:40 +03:00
|
|
|
let key_verts: Vec<[f64; 3]> = self
|
2026-04-02 19:06:20 +03:00
|
|
|
.vertices
|
|
|
|
|
.iter()
|
2026-05-12 23:47:40 +03:00
|
|
|
.map(|v| [v.position.x, v.position.y, v.position.z])
|
2026-04-02 19:06:20 +03:00
|
|
|
.collect();
|
|
|
|
|
|
2026-05-02 19:59:37 +03:00
|
|
|
let snap_pts = self
|
|
|
|
|
.vertices
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|v| {
|
|
|
|
|
(
|
refactor(snap): producers emit f64 snap points (world_offset removal P3)
TruckEntity.snap_pts is now DVec3 and every entity producer builds snap
candidates straight from the acadrust f64 coordinates instead of casting
to f32 first (text, mtext, arc, circle, ellipse, point, shape, solid,
mline, mtext, attribute, tolerance, table, underlay, ole2frame, mesh,
multileader, dimension, block-cache). offset_snap_pts stays f64
throughout. Still offset-relative (inert) — but snap precision is no
longer capped at ~0.5 m by an early f32 cast, which fixes a latent
UTM-scale snap error and sets up the absolute-coordinate switch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 22:10:29 +03:00
|
|
|
glam::DVec3::new(v.position.x, v.position.y, v.position.z),
|
2026-05-02 19:59:37 +03:00
|
|
|
SnapHint::Node,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
2026-04-02 19:06:20 +03:00
|
|
|
Some(TruckEntity {
|
fix(pick): hit-test lineweight width and thickness walls
Selection tested a flat 8 px radius around each wire's centreline and
nothing else, so two things the user can see were not pickable.
A wire renders as a band `line_weight_px` wide, so pick now widens to that
rendered half-width via `pick_tolerance_px`, mirroring the wire shader's
`select(0.5, half_width, lwdisplay_enable)`. Behaviour is unchanged for
every standard weight (the widest, 2.11 mm, renders 7.97 px half-width —
just under the 8 px threshold); this keeps the two sides from drifting
apart if the display boost in `lineweight_to_px` ever changes.
Entities extruded by a DXF thickness draw their wall as four edges with
nothing in between, so the interior was a hole the cursor fell through.
WireModel gains `pick_tris`: triangles hit-testing treats as solid and the
renderer never uploads. Kept apart from `fill_tris` because that channel
reaches the GPU, and a wall that rendered shaded would change how every
thickness drawing looks. Ranked below `fill_tris` — those are drawn, these
are not, so where they overlap the drawn face is what the user means.
Walls are dropped when a wire is projected into a floating viewport:
`points` are reprojected and clipped into paper coords there, so cloned
model-space triangles would hit-test at a model-scale offset. Extruded
entities stay selectable by their edges, which do get projected.
The wall loop runs on every hover that misses everything else, so it
rejects on the wire's AABB first — an extruded circle is ~128 triangles
and a drawing full of thickness would otherwise project every wall on
each mouse move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:55 +03:00
|
|
|
pick_tris: Vec::new(),
|
2026-04-02 19:06:20 +03:00
|
|
|
object: TruckObject::Lines(pts),
|
2026-05-02 19:59:37 +03:00
|
|
|
snap_pts,
|
2026-04-02 19:06:20 +03:00
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: key_verts,
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-04-02 19:06:20 +03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Grippable for MLine {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
|
|
|
self.vertices
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.map(|(i, v)| {
|
|
|
|
|
square_grip(
|
|
|
|
|
i,
|
2026-06-11 00:04:37 +03:00
|
|
|
glam::DVec3::new(v.position.x, v.position.y, v.position.z),
|
2026-04-02 19:06:20 +03:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
if let Some(v) = self.vertices.get_mut(grip_id) {
|
|
|
|
|
match apply {
|
|
|
|
|
GripApply::Translate(d) => {
|
|
|
|
|
v.position.x += d.x as f64;
|
|
|
|
|
v.position.y += d.y as f64;
|
|
|
|
|
v.position.z += d.z as f64;
|
|
|
|
|
}
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
v.position.x = p.x as f64;
|
|
|
|
|
v.position.y = p.y as f64;
|
|
|
|
|
v.position.z = p.z as f64;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::model::object::GripMenuItem> {
|
|
|
|
|
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
vec![
|
2026-06-11 00:04:37 +03:00
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Stretch",
|
|
|
|
|
action: GripMenuAction::Stretch,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Add Vertex",
|
|
|
|
|
action: GripMenuAction::AddVertex,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Remove Vertex",
|
|
|
|
|
action: GripMenuAction::RemoveVertex,
|
|
|
|
|
},
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) {
|
|
|
|
|
use crate::scene::model::object::GripMenuAction as A;
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let n = self.vertices.len();
|
|
|
|
|
match action {
|
|
|
|
|
A::AddVertex if grip_id < n => {
|
|
|
|
|
let i1 = (grip_id + 1).min(n - 1);
|
|
|
|
|
if i1 == grip_id {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let v0 = &self.vertices[grip_id];
|
|
|
|
|
let v1 = &self.vertices[i1];
|
|
|
|
|
let mut new_v = v0.clone();
|
|
|
|
|
new_v.position.x = (v0.position.x + v1.position.x) * 0.5;
|
|
|
|
|
new_v.position.y = (v0.position.y + v1.position.y) * 0.5;
|
|
|
|
|
new_v.position.z = (v0.position.z + v1.position.z) * 0.5;
|
|
|
|
|
self.vertices.insert(i1, new_v);
|
|
|
|
|
}
|
|
|
|
|
A::RemoveVertex if grip_id < n && n > 2 => {
|
|
|
|
|
self.vertices.remove(grip_id);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-02 19:06:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for MLine {
|
2026-07-02 22:46:19 +03:00
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
feat(entities): surface remaining 2D + Region fields in properties
Closes out the 2D-entity audit (and the Region partner that ships next
to Solid3D / Body). Round-trip-only metadata fields plus a few editable
ones gain Properties-panel rows so the values the parser populates are
visible and don't get silently dropped on save:
- Hatch: seed_points count, pixel_size, normal
- Polyline2D: smooth_surface, default_start_width, default_end_width,
thickness, normal (with editors for the widths/thickness)
- Region: uid, silhouettes (shared file with Solid3D / Body)
- Underlay: clip_inverted
- RasterImage: class_version, definition_handle, definition_reactor_handle
- Wipeout: class_version, clip_mode, definition_handle,
definition_reactor_handle
- Leader: annotation_handle, override_color
- Tolerance: dimension_style_name, dimension_style_handle, dimension_gap,
text_height (editable), direction, normal
- MLine: style_handle, style_element_count, justification picker
- Ole2Frame: version, source_application, binary_data size, dwg_mode,
dwg_trailing_byte, is_paper_space
- Viewport: snap_base/spacing/angle, twist_angle, front/back clip Z,
circle_sides, grid_flags, grid_major, base_ucs_handle, ucs_ortho_type,
background_handle, shade_plot_handle, visual_style_handle,
default_lighting, default_lighting_type, ambient_color (with editors
for the snap / clip / circle_sides values)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:28:16 +03:00
|
|
|
let just_str = match self.justification {
|
|
|
|
|
acadrust::entities::MLineJustification::Top => "Top",
|
|
|
|
|
acadrust::entities::MLineJustification::Zero => "Zero",
|
|
|
|
|
acadrust::entities::MLineJustification::Bottom => "Bottom",
|
|
|
|
|
};
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
let cur = self.vertices.first();
|
|
|
|
|
let cur_x = cur.map(|v| v.position.x).unwrap_or(0.0);
|
|
|
|
|
let cur_y = cur.map(|v| v.position.y).unwrap_or(0.0);
|
|
|
|
|
let cur_z = cur.map(|v| v.position.z).unwrap_or(0.0);
|
|
|
|
|
vec![
|
|
|
|
|
PropSection {
|
|
|
|
|
title: "Geometry".into(),
|
|
|
|
|
props: vec![
|
|
|
|
|
ro("Vertex", "ml_vertex", if self.vertices.is_empty() { String::new() } else { "1".to_string() }),
|
|
|
|
|
edit("Vertex X", "ml_vertex_x", cur_x),
|
|
|
|
|
edit("Vertex Y", "ml_vertex_y", cur_y),
|
|
|
|
|
edit("Vertex Z", "ml_vertex_z", cur_z),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
PropSection {
|
|
|
|
|
title: "Misc".into(),
|
|
|
|
|
props: vec![
|
2026-07-03 08:06:00 +03:00
|
|
|
Property {
|
|
|
|
|
label: "Style".into(),
|
|
|
|
|
field: "ml_style",
|
|
|
|
|
value: PropValue::EditText(self.style_name.clone()),
|
|
|
|
|
},
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
Property {
|
|
|
|
|
label: "Style justification".into(),
|
|
|
|
|
field: "ml_justification",
|
|
|
|
|
value: PropValue::Choice {
|
|
|
|
|
selected: just_str.to_string(),
|
|
|
|
|
options: ["Top", "Zero", "Bottom"]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(str::to_string)
|
|
|
|
|
.collect(),
|
|
|
|
|
},
|
2026-04-02 19:06:20 +03:00
|
|
|
},
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
edit("Style scale", "ml_scale", self.scale_factor),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]
|
2026-04-02 19:06:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
|
|
|
|
match field {
|
|
|
|
|
"ml_closed" => {
|
|
|
|
|
let closed = if value == "toggle" {
|
|
|
|
|
!self.flags.contains(acadrust::entities::MLineFlags::CLOSED)
|
|
|
|
|
} else {
|
|
|
|
|
value == "true"
|
|
|
|
|
};
|
2026-05-12 10:36:15 +03:00
|
|
|
self.flags
|
|
|
|
|
.set(acadrust::entities::MLineFlags::CLOSED, closed);
|
2026-04-02 19:06:20 +03:00
|
|
|
return;
|
|
|
|
|
}
|
feat(entities): surface remaining 2D + Region fields in properties
Closes out the 2D-entity audit (and the Region partner that ships next
to Solid3D / Body). Round-trip-only metadata fields plus a few editable
ones gain Properties-panel rows so the values the parser populates are
visible and don't get silently dropped on save:
- Hatch: seed_points count, pixel_size, normal
- Polyline2D: smooth_surface, default_start_width, default_end_width,
thickness, normal (with editors for the widths/thickness)
- Region: uid, silhouettes (shared file with Solid3D / Body)
- Underlay: clip_inverted
- RasterImage: class_version, definition_handle, definition_reactor_handle
- Wipeout: class_version, clip_mode, definition_handle,
definition_reactor_handle
- Leader: annotation_handle, override_color
- Tolerance: dimension_style_name, dimension_style_handle, dimension_gap,
text_height (editable), direction, normal
- MLine: style_handle, style_element_count, justification picker
- Ole2Frame: version, source_application, binary_data size, dwg_mode,
dwg_trailing_byte, is_paper_space
- Viewport: snap_base/spacing/angle, twist_angle, front/back clip Z,
circle_sides, grid_flags, grid_major, base_ucs_handle, ucs_ortho_type,
background_handle, shade_plot_handle, visual_style_handle,
default_lighting, default_lighting_type, ambient_color (with editors
for the snap / clip / circle_sides values)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:28:16 +03:00
|
|
|
"ml_justification" => {
|
|
|
|
|
self.justification = match value {
|
|
|
|
|
"Top" => acadrust::entities::MLineJustification::Top,
|
|
|
|
|
"Bottom" => acadrust::entities::MLineJustification::Bottom,
|
|
|
|
|
_ => acadrust::entities::MLineJustification::Zero,
|
|
|
|
|
};
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-07-03 08:06:00 +03:00
|
|
|
"ml_style" => {
|
|
|
|
|
self.style_name = value.to_string();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-04-02 19:06:20 +03:00
|
|
|
_ => {}
|
|
|
|
|
}
|
2026-05-12 10:36:15 +03:00
|
|
|
let Ok(v) = value.trim().parse::<f64>() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
match field {
|
|
|
|
|
"ml_scale" if v != 0.0 => self.scale_factor = v,
|
|
|
|
|
"ml_vertex_x" => {
|
|
|
|
|
if let Some(vx) = self.vertices.first_mut() {
|
|
|
|
|
vx.position.x = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"ml_vertex_y" => {
|
|
|
|
|
if let Some(vx) = self.vertices.first_mut() {
|
|
|
|
|
vx.position.y = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"ml_vertex_z" => {
|
|
|
|
|
if let Some(vx) = self.vertices.first_mut() {
|
|
|
|
|
vx.position.z = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
2026-04-02 19:06:20 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transformable for MLine {
|
|
|
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
2026-06-16 11:33:46 +03:00
|
|
|
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
2026-04-02 19:06:20 +03:00
|
|
|
for v in &mut entity.vertices {
|
2026-06-16 11:33:46 +03:00
|
|
|
crate::scene::view::transform::reflect_xy_point(
|
2026-04-02 19:06:20 +03:00
|
|
|
&mut v.position.x,
|
|
|
|
|
&mut v.position.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-06-16 11:33:46 +03:00
|
|
|
crate::scene::view::transform::reflect_xy_point(
|
2026-04-02 19:06:20 +03:00
|
|
|
&mut entity.start_point.x,
|
|
|
|
|
&mut entity.start_point.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|