2026-04-02 18:51:38 +03:00
|
|
|
use acadrust::entities::{Ray, XLine};
|
|
|
|
|
|
|
|
|
|
use crate::command::EntityTransform;
|
refactor(grip): square-only marker set + oriented stretch handles
Phase 1 of bringing the grip vocabulary in line with the standard
CAD convention: drop the per-position-type shape encoding (Diamond
for translate, Circle for parameter) and use a plain square for
every endpoint / vertex / centre grip. The behavioural midpoint vs
vertex distinction stays — it lives on `GripDef::is_midpoint` and
drives `Translate` vs `Absolute` in the grip-edit dispatcher — only
the marker shape collapses.
- `GripShape` keeps `Square`, `Rectangle`, `Triangle`. Diamond /
Circle are gone.
- `GripDef` gains `dir: Option<[f32; 2]>` — a world-XY direction
vector consumed by `Rectangle` to orient the box along its
segment.
- `entities::common` exposes `square_grip`, `center_grip` (same
square marker, flagged as translate), `rectangle_grip(id, world,
dir)` for oriented mid-segment handles, and a kept-for-Phase-2
`triangle_grip`.
- `overlay::GripMarker` / `grips_to_screen[_paper]` thread `dir`
through to the canvas painter; the Rectangle path rotates the
box around the grip centre using the world-XY direction (flipping
the sin for screen-Y).
Every existing `diamond_grip(id, w)` call becomes `center_grip(id,
w)` — the visual change is square-replacing-diamond at line
midpoints / arc midpoints / circle centres / ellipse centres /
viewport / hatch loop centroids / dimension grips / image corners /
multileader / ray / underlay / ole2frame / solid3d.
`LwPolyline` grows a stretch handle on every segment (straight or
arc), drawn as the new oriented rectangle. The grip-edit path
translates both segment endpoints for straight segments and
adjusts the arc bulge from the new midpoint for arc segments.
2026-05-29 02:07:34 +03:00
|
|
|
use crate::entities::common::{center_grip, edit_prop as edit, square_grip};
|
2026-04-02 18:51:38 +03:00
|
|
|
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};
|
2026-04-02 18:51:38 +03:00
|
|
|
|
|
|
|
|
/// Display length used when rendering semi-infinite / infinite lines.
|
|
|
|
|
const DISPLAY_EXTENT: f64 = 1_000_000.0;
|
|
|
|
|
|
|
|
|
|
// ── Ray (semi-infinite line) ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
impl TruckConvertible for Ray {
|
|
|
|
|
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
|
|
|
|
let bp = self.base_point;
|
|
|
|
|
let dir = self.direction;
|
2026-04-27 18:04:35 +03:00
|
|
|
// Normalize direction to avoid f32 overflow when DXF stores
|
|
|
|
|
// unnormalized direction vectors (garbage data in some exporters).
|
|
|
|
|
let len = (dir.x * dir.x + dir.y * dir.y + dir.z * dir.z).sqrt();
|
|
|
|
|
if len < 1e-10 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let (nx, ny, nz) = (dir.x / len, dir.y / len, dir.z / len);
|
2026-05-12 23:47:40 +03:00
|
|
|
let far: [f64; 3] = [
|
|
|
|
|
bp.x + nx * DISPLAY_EXTENT,
|
|
|
|
|
bp.y + ny * DISPLAY_EXTENT,
|
|
|
|
|
bp.z + nz * DISPLAY_EXTENT,
|
2026-04-02 18:51:38 +03:00
|
|
|
];
|
2026-05-12 23:47:40 +03:00
|
|
|
let start: [f64; 3] = [bp.x, bp.y, bp.z];
|
2026-04-02 18:51:38 +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 18:51:38 +03:00
|
|
|
object: TruckObject::Lines(vec![start, far]),
|
|
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: vec![start],
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-04-02 18:51:38 +03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Grippable for Ray {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
|
|
|
let bp = &self.base_point;
|
|
|
|
|
let dir = &self.direction;
|
|
|
|
|
// Grip 0: base point (movable)
|
|
|
|
|
// Grip 1: a point along the direction (changes direction)
|
|
|
|
|
let guide_dist = 10.0_f64;
|
|
|
|
|
vec![
|
2026-06-11 00:04:37 +03:00
|
|
|
square_grip(0, glam::DVec3::new(bp.x, bp.y, bp.z)),
|
refactor(grip): square-only marker set + oriented stretch handles
Phase 1 of bringing the grip vocabulary in line with the standard
CAD convention: drop the per-position-type shape encoding (Diamond
for translate, Circle for parameter) and use a plain square for
every endpoint / vertex / centre grip. The behavioural midpoint vs
vertex distinction stays — it lives on `GripDef::is_midpoint` and
drives `Translate` vs `Absolute` in the grip-edit dispatcher — only
the marker shape collapses.
- `GripShape` keeps `Square`, `Rectangle`, `Triangle`. Diamond /
Circle are gone.
- `GripDef` gains `dir: Option<[f32; 2]>` — a world-XY direction
vector consumed by `Rectangle` to orient the box along its
segment.
- `entities::common` exposes `square_grip`, `center_grip` (same
square marker, flagged as translate), `rectangle_grip(id, world,
dir)` for oriented mid-segment handles, and a kept-for-Phase-2
`triangle_grip`.
- `overlay::GripMarker` / `grips_to_screen[_paper]` thread `dir`
through to the canvas painter; the Rectangle path rotates the
box around the grip centre using the world-XY direction (flipping
the sin for screen-Y).
Every existing `diamond_grip(id, w)` call becomes `center_grip(id,
w)` — the visual change is square-replacing-diamond at line
midpoints / arc midpoints / circle centres / ellipse centres /
viewport / hatch loop centroids / dimension grips / image corners /
multileader / ray / underlay / ole2frame / solid3d.
`LwPolyline` grows a stretch handle on every segment (straight or
arc), drawn as the new oriented rectangle. The grip-edit path
translates both segment endpoints for straight segments and
adjusts the arc bulge from the new midpoint for arc segments.
2026-05-29 02:07:34 +03:00
|
|
|
center_grip(
|
2026-04-02 18:51:38 +03:00
|
|
|
1,
|
2026-06-11 00:04:37 +03:00
|
|
|
glam::DVec3::new(
|
|
|
|
|
bp.x + dir.x * guide_dist,
|
|
|
|
|
bp.y + dir.y * guide_dist,
|
|
|
|
|
bp.z + dir.z * guide_dist,
|
2026-04-02 18:51:38 +03:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
match (grip_id, apply) {
|
|
|
|
|
(0, GripApply::Translate(d)) => {
|
|
|
|
|
self.base_point.x += d.x as f64;
|
|
|
|
|
self.base_point.y += d.y as f64;
|
|
|
|
|
self.base_point.z += d.z as f64;
|
|
|
|
|
}
|
|
|
|
|
(0, GripApply::Absolute(p)) => {
|
|
|
|
|
self.base_point.x = p.x as f64;
|
|
|
|
|
self.base_point.y = p.y as f64;
|
|
|
|
|
self.base_point.z = p.z as f64;
|
|
|
|
|
}
|
|
|
|
|
(1, GripApply::Absolute(p)) => {
|
|
|
|
|
// New direction = grip point - base point, normalized.
|
|
|
|
|
let dx = p.x as f64 - self.base_point.x;
|
|
|
|
|
let dy = p.y as f64 - self.base_point.y;
|
|
|
|
|
let dz = p.z as f64 - self.base_point.z;
|
|
|
|
|
let len = (dx * dx + dy * dy + dz * dz).sqrt();
|
|
|
|
|
if len > 1e-9 {
|
|
|
|
|
self.direction.x = dx / len;
|
|
|
|
|
self.direction.y = dy / len;
|
|
|
|
|
self.direction.z = dz / len;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Ray {
|
2026-07-02 22:46:19 +03:00
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
|
|
|
|
vec![PropSection {
|
2026-04-02 18:51:38 +03:00
|
|
|
title: "Geometry".into(),
|
|
|
|
|
props: vec![
|
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("Start X", "ray_bx", self.base_point.x),
|
|
|
|
|
edit("Start Y", "ray_by", self.base_point.y),
|
|
|
|
|
edit("Start Z", "ray_bz", self.base_point.z),
|
|
|
|
|
edit("Direction vector X", "ray_dx", self.direction.x),
|
|
|
|
|
edit("Direction vector Y", "ray_dy", self.direction.y),
|
|
|
|
|
edit("Direction vector Z", "ray_dz", self.direction.z),
|
2026-04-02 18:51:38 +03:00
|
|
|
],
|
2026-07-02 22:46:19 +03:00
|
|
|
}]
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
2026-05-12 10:36:15 +03:00
|
|
|
let Ok(v) = value.trim().parse::<f64>() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2026-04-02 18:51:38 +03:00
|
|
|
match field {
|
|
|
|
|
"ray_bx" => self.base_point.x = v,
|
|
|
|
|
"ray_by" => self.base_point.y = v,
|
|
|
|
|
"ray_bz" => self.base_point.z = v,
|
2026-05-12 10:36:15 +03:00
|
|
|
"ray_dx" => {
|
|
|
|
|
self.direction.x = v;
|
|
|
|
|
}
|
|
|
|
|
"ray_dy" => {
|
|
|
|
|
self.direction.y = v;
|
|
|
|
|
}
|
|
|
|
|
"ray_dz" => {
|
|
|
|
|
self.direction.z = v;
|
|
|
|
|
}
|
2026-04-02 18:51:38 +03:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transformable for Ray {
|
|
|
|
|
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| {
|
|
|
|
|
crate::scene::view::transform::reflect_xy_point(
|
2026-04-02 18:51:38 +03:00
|
|
|
&mut entity.base_point.x,
|
|
|
|
|
&mut entity.base_point.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
|
|
|
|
// Mirror the direction: negate the component perpendicular to mirror axis.
|
|
|
|
|
let ax = (p2.x - p1.x) as f64;
|
|
|
|
|
let ay = (p2.y - p1.y) as f64;
|
|
|
|
|
let len2 = ax * ax + ay * ay;
|
|
|
|
|
if len2 > 1e-12 {
|
|
|
|
|
let d = &mut entity.direction;
|
|
|
|
|
let dot = d.x * ax + d.y * ay;
|
|
|
|
|
d.x = 2.0 * dot * ax / len2 - d.x;
|
|
|
|
|
d.y = 2.0 * dot * ay / len2 - d.y;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── XLine (construction line, infinite) ──────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
impl TruckConvertible for XLine {
|
|
|
|
|
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
|
|
|
|
let bp = self.base_point;
|
|
|
|
|
let dir = self.direction;
|
2026-04-27 18:04:35 +03:00
|
|
|
let len = (dir.x * dir.x + dir.y * dir.y + dir.z * dir.z).sqrt();
|
|
|
|
|
if len < 1e-10 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let (nx, ny, nz) = (dir.x / len, dir.y / len, dir.z / len);
|
2026-05-12 23:47:40 +03:00
|
|
|
let far_pos: [f64; 3] = [
|
|
|
|
|
bp.x + nx * DISPLAY_EXTENT,
|
|
|
|
|
bp.y + ny * DISPLAY_EXTENT,
|
|
|
|
|
bp.z + nz * DISPLAY_EXTENT,
|
2026-04-02 18:51:38 +03:00
|
|
|
];
|
2026-05-12 23:47:40 +03:00
|
|
|
let far_neg: [f64; 3] = [
|
|
|
|
|
bp.x - nx * DISPLAY_EXTENT,
|
|
|
|
|
bp.y - ny * DISPLAY_EXTENT,
|
|
|
|
|
bp.z - nz * DISPLAY_EXTENT,
|
2026-04-02 18:51:38 +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 18:51:38 +03:00
|
|
|
object: TruckObject::Lines(vec![far_neg, far_pos]),
|
|
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: vec![],
|
2026-05-12 23:47:40 +03:00
|
|
|
key_vertices: vec![[bp.x, bp.y, bp.z]],
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-04-02 18:51:38 +03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Grippable for XLine {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
|
|
|
let bp = &self.base_point;
|
|
|
|
|
let dir = &self.direction;
|
|
|
|
|
let guide_dist = 10.0_f64;
|
|
|
|
|
vec![
|
2026-06-11 00:04:37 +03:00
|
|
|
square_grip(0, glam::DVec3::new(bp.x, bp.y, bp.z)),
|
refactor(grip): square-only marker set + oriented stretch handles
Phase 1 of bringing the grip vocabulary in line with the standard
CAD convention: drop the per-position-type shape encoding (Diamond
for translate, Circle for parameter) and use a plain square for
every endpoint / vertex / centre grip. The behavioural midpoint vs
vertex distinction stays — it lives on `GripDef::is_midpoint` and
drives `Translate` vs `Absolute` in the grip-edit dispatcher — only
the marker shape collapses.
- `GripShape` keeps `Square`, `Rectangle`, `Triangle`. Diamond /
Circle are gone.
- `GripDef` gains `dir: Option<[f32; 2]>` — a world-XY direction
vector consumed by `Rectangle` to orient the box along its
segment.
- `entities::common` exposes `square_grip`, `center_grip` (same
square marker, flagged as translate), `rectangle_grip(id, world,
dir)` for oriented mid-segment handles, and a kept-for-Phase-2
`triangle_grip`.
- `overlay::GripMarker` / `grips_to_screen[_paper]` thread `dir`
through to the canvas painter; the Rectangle path rotates the
box around the grip centre using the world-XY direction (flipping
the sin for screen-Y).
Every existing `diamond_grip(id, w)` call becomes `center_grip(id,
w)` — the visual change is square-replacing-diamond at line
midpoints / arc midpoints / circle centres / ellipse centres /
viewport / hatch loop centroids / dimension grips / image corners /
multileader / ray / underlay / ole2frame / solid3d.
`LwPolyline` grows a stretch handle on every segment (straight or
arc), drawn as the new oriented rectangle. The grip-edit path
translates both segment endpoints for straight segments and
adjusts the arc bulge from the new midpoint for arc segments.
2026-05-29 02:07:34 +03:00
|
|
|
center_grip(
|
2026-04-02 18:51:38 +03:00
|
|
|
1,
|
2026-06-11 00:04:37 +03:00
|
|
|
glam::DVec3::new(
|
|
|
|
|
bp.x + dir.x * guide_dist,
|
|
|
|
|
bp.y + dir.y * guide_dist,
|
|
|
|
|
bp.z + dir.z * guide_dist,
|
2026-04-02 18:51:38 +03:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
match (grip_id, apply) {
|
|
|
|
|
(0, GripApply::Translate(d)) => {
|
|
|
|
|
self.base_point.x += d.x as f64;
|
|
|
|
|
self.base_point.y += d.y as f64;
|
|
|
|
|
self.base_point.z += d.z as f64;
|
|
|
|
|
}
|
|
|
|
|
(0, GripApply::Absolute(p)) => {
|
|
|
|
|
self.base_point.x = p.x as f64;
|
|
|
|
|
self.base_point.y = p.y as f64;
|
|
|
|
|
self.base_point.z = p.z as f64;
|
|
|
|
|
}
|
|
|
|
|
(1, GripApply::Absolute(p)) => {
|
|
|
|
|
let dx = p.x as f64 - self.base_point.x;
|
|
|
|
|
let dy = p.y as f64 - self.base_point.y;
|
|
|
|
|
let dz = p.z as f64 - self.base_point.z;
|
|
|
|
|
let len = (dx * dx + dy * dy + dz * dz).sqrt();
|
|
|
|
|
if len > 1e-9 {
|
|
|
|
|
self.direction.x = dx / len;
|
|
|
|
|
self.direction.y = dy / len;
|
|
|
|
|
self.direction.z = dz / len;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for XLine {
|
2026-07-02 22:46:19 +03:00
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
|
|
|
|
vec![PropSection {
|
2026-04-02 18:51:38 +03:00
|
|
|
title: "Geometry".into(),
|
|
|
|
|
props: vec![
|
|
|
|
|
edit("Base X", "xl_bx", self.base_point.x),
|
|
|
|
|
edit("Base Y", "xl_by", self.base_point.y),
|
|
|
|
|
edit("Base Z", "xl_bz", self.base_point.z),
|
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("Direction vector X", "xl_dx", self.direction.x),
|
|
|
|
|
edit("Direction vector Y", "xl_dy", self.direction.y),
|
|
|
|
|
edit("Direction vector Z", "xl_dz", self.direction.z),
|
2026-04-02 18:51:38 +03:00
|
|
|
],
|
2026-07-02 22:46:19 +03:00
|
|
|
}]
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
2026-05-12 10:36:15 +03:00
|
|
|
let Ok(v) = value.trim().parse::<f64>() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2026-04-02 18:51:38 +03:00
|
|
|
match field {
|
|
|
|
|
"xl_bx" => self.base_point.x = v,
|
|
|
|
|
"xl_by" => self.base_point.y = v,
|
|
|
|
|
"xl_bz" => self.base_point.z = v,
|
2026-05-12 10:36:15 +03:00
|
|
|
"xl_dx" => {
|
|
|
|
|
self.direction.x = v;
|
|
|
|
|
}
|
|
|
|
|
"xl_dy" => {
|
|
|
|
|
self.direction.y = v;
|
|
|
|
|
}
|
|
|
|
|
"xl_dz" => {
|
|
|
|
|
self.direction.z = v;
|
|
|
|
|
}
|
2026-04-02 18:51:38 +03:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transformable for XLine {
|
|
|
|
|
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| {
|
|
|
|
|
crate::scene::view::transform::reflect_xy_point(
|
2026-04-02 18:51:38 +03:00
|
|
|
&mut entity.base_point.x,
|
|
|
|
|
&mut entity.base_point.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
|
|
|
|
let ax = (p2.x - p1.x) as f64;
|
|
|
|
|
let ay = (p2.y - p1.y) as f64;
|
|
|
|
|
let len2 = ax * ax + ay * ay;
|
|
|
|
|
if len2 > 1e-12 {
|
|
|
|
|
let d = &mut entity.direction;
|
|
|
|
|
let dot = d.x * ax + d.y * ay;
|
|
|
|
|
d.x = 2.0 * dot * ax / len2 - d.x;
|
|
|
|
|
d.y = 2.0 * dot * ay / len2 - d.y;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|