2026-05-05 20:12:44 +03:00
|
|
|
use acadrust::entities::{Ole2Frame, OleObjectType};
|
|
|
|
|
use glam::Vec3;
|
|
|
|
|
|
|
|
|
|
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, ro_prop as ro, square_grip};
|
2026-06-11 00:04:37 +03:00
|
|
|
use crate::entities::traits::TruckConvertible;
|
2026-05-05 20:12:44 +03:00
|
|
|
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
|
|
|
|
use crate::scene::object::{GripApply, GripDef, PropSection};
|
|
|
|
|
use crate::scene::wire_model::SnapHint;
|
|
|
|
|
|
|
|
|
|
fn to_truck(ole: &Ole2Frame) -> TruckEntity {
|
2026-05-12 23:47:40 +03:00
|
|
|
let x0 = ole.upper_left_corner.x;
|
|
|
|
|
let y0 = ole.lower_right_corner.y;
|
|
|
|
|
let x1 = ole.lower_right_corner.x;
|
|
|
|
|
let y1 = ole.upper_left_corner.y;
|
|
|
|
|
let z = ole.upper_left_corner.z;
|
2026-05-05 20:12:44 +03:00
|
|
|
|
|
|
|
|
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
|
2026-05-12 23:47:40 +03:00
|
|
|
let s = 0.5_f64;
|
2026-05-05 20:12:44 +03:00
|
|
|
return TruckEntity {
|
|
|
|
|
object: TruckObject::Lines(vec![[-s, 0.0, z], [s, 0.0, z]]),
|
|
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: vec![],
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-05-05 20:12:44 +03:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let cx = (x0 + x1) * 0.5;
|
|
|
|
|
let cy = (y0 + y1) * 0.5;
|
2026-05-12 23:47:40 +03:00
|
|
|
let pts: Vec<[f64; 3]> = vec![
|
2026-05-05 20:12:44 +03:00
|
|
|
// Rectangle
|
2026-05-12 10:36:15 +03:00
|
|
|
[x0, y0, z],
|
|
|
|
|
[x1, y0, z],
|
|
|
|
|
[x1, y0, z],
|
|
|
|
|
[x1, y1, z],
|
|
|
|
|
[x1, y1, z],
|
|
|
|
|
[x0, y1, z],
|
|
|
|
|
[x0, y1, z],
|
|
|
|
|
[x0, y0, z],
|
2026-05-05 20:12:44 +03:00
|
|
|
// Diagonal X
|
2026-05-12 10:36:15 +03:00
|
|
|
[x0, y0, z],
|
|
|
|
|
[x1, y1, z],
|
2026-05-12 23:47:40 +03:00
|
|
|
[f64::NAN; 3],
|
2026-05-12 10:36:15 +03:00
|
|
|
[x1, y0, z],
|
|
|
|
|
[x0, y1, z],
|
2026-05-05 20:12:44 +03:00
|
|
|
];
|
2026-05-12 23:47:40 +03:00
|
|
|
let center = Vec3::new(cx as f32, cy as f32, z as f32);
|
2026-05-05 20:12:44 +03:00
|
|
|
TruckEntity {
|
|
|
|
|
object: TruckObject::Lines(pts),
|
|
|
|
|
snap_pts: vec![(center, SnapHint::Center)],
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: vec![[x0, y0, z], [x1, y1, z]],
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-05-05 20:12:44 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn grips(ole: &Ole2Frame) -> Vec<GripDef> {
|
2026-06-11 00:04:37 +03:00
|
|
|
let ul = glam::DVec3::new(
|
|
|
|
|
ole.upper_left_corner.x,
|
|
|
|
|
ole.upper_left_corner.y,
|
|
|
|
|
ole.upper_left_corner.z,
|
2026-05-05 20:12:44 +03:00
|
|
|
);
|
2026-06-11 00:04:37 +03:00
|
|
|
let lr = glam::DVec3::new(
|
|
|
|
|
ole.lower_right_corner.x,
|
|
|
|
|
ole.lower_right_corner.y,
|
|
|
|
|
ole.lower_right_corner.z,
|
2026-05-05 20:12:44 +03:00
|
|
|
);
|
|
|
|
|
let center = (ul + lr) * 0.5;
|
2026-05-12 10:36:15 +03:00
|
|
|
vec![
|
|
|
|
|
square_grip(0, ul),
|
|
|
|
|
square_grip(1, lr),
|
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(2, center),
|
2026-05-12 10:36:15 +03:00
|
|
|
]
|
2026-05-05 20:12:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn properties(ole: &Ole2Frame) -> PropSection {
|
|
|
|
|
let type_str = match ole.ole_object_type {
|
|
|
|
|
OleObjectType::Link => "Link",
|
|
|
|
|
OleObjectType::Embedded => "Embedded",
|
|
|
|
|
OleObjectType::Static => "Static",
|
|
|
|
|
};
|
|
|
|
|
PropSection {
|
|
|
|
|
title: "Geometry".into(),
|
|
|
|
|
props: vec![
|
|
|
|
|
ro("Type", "ole_type", type_str),
|
|
|
|
|
edit("Upper Left X", "ole_ulx", ole.upper_left_corner.x),
|
|
|
|
|
edit("Upper Left Y", "ole_uly", ole.upper_left_corner.y),
|
|
|
|
|
edit("Lower Right X", "ole_lrx", ole.lower_right_corner.x),
|
|
|
|
|
edit("Lower Right Y", "ole_lry", ole.lower_right_corner.y),
|
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
|
|
|
ro("Version", "ole_version", ole.version.to_string()),
|
|
|
|
|
ro(
|
|
|
|
|
"Source App",
|
|
|
|
|
"ole_source_app",
|
|
|
|
|
if ole.source_application.is_empty() {
|
|
|
|
|
"(unknown)".to_string()
|
|
|
|
|
} else {
|
|
|
|
|
ole.source_application.clone()
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Data Size",
|
|
|
|
|
"ole_data_size",
|
|
|
|
|
format!("{} bytes", ole.binary_data.len()),
|
|
|
|
|
),
|
|
|
|
|
ro("DWG Mode", "ole_dwg_mode", ole.dwg_mode.to_string()),
|
|
|
|
|
ro(
|
|
|
|
|
"DWG Trailing Byte",
|
|
|
|
|
"ole_dwg_trailing",
|
|
|
|
|
format!("{:#04x}", ole.dwg_trailing_byte),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Paper Space",
|
|
|
|
|
"ole_paper_space",
|
|
|
|
|
if ole.is_paper_space { "Yes" } else { "No" },
|
|
|
|
|
),
|
2026-05-05 20:12:44 +03:00
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(ole: &mut Ole2Frame, field: &str, value: &str) {
|
2026-05-12 10:36:15 +03:00
|
|
|
let Ok(v) = value.trim().parse::<f64>() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2026-05-05 20:12:44 +03:00
|
|
|
match field {
|
|
|
|
|
"ole_ulx" => ole.upper_left_corner.x = v,
|
|
|
|
|
"ole_uly" => ole.upper_left_corner.y = v,
|
|
|
|
|
"ole_lrx" => ole.lower_right_corner.x = v,
|
|
|
|
|
"ole_lry" => ole.lower_right_corner.y = v,
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(ole: &mut Ole2Frame, grip_id: usize, apply: GripApply) {
|
|
|
|
|
match (grip_id, apply) {
|
|
|
|
|
(0, GripApply::Absolute(p)) => {
|
|
|
|
|
ole.upper_left_corner.x = p.x as f64;
|
|
|
|
|
ole.upper_left_corner.y = p.y as f64;
|
|
|
|
|
}
|
|
|
|
|
(1, GripApply::Absolute(p)) => {
|
|
|
|
|
ole.lower_right_corner.x = p.x as f64;
|
|
|
|
|
ole.lower_right_corner.y = p.y as f64;
|
|
|
|
|
}
|
|
|
|
|
(2, GripApply::Translate(d)) => {
|
|
|
|
|
ole.upper_left_corner.x += d.x as f64;
|
|
|
|
|
ole.upper_left_corner.y += d.y as f64;
|
|
|
|
|
ole.lower_right_corner.x += d.x as f64;
|
|
|
|
|
ole.lower_right_corner.y += d.y as f64;
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_transform(ole: &mut Ole2Frame, t: &EntityTransform) {
|
|
|
|
|
match t {
|
|
|
|
|
EntityTransform::Translate(d) => {
|
|
|
|
|
ole.upper_left_corner.x += d.x as f64;
|
|
|
|
|
ole.upper_left_corner.y += d.y as f64;
|
|
|
|
|
ole.upper_left_corner.z += d.z as f64;
|
|
|
|
|
ole.lower_right_corner.x += d.x as f64;
|
|
|
|
|
ole.lower_right_corner.y += d.y as f64;
|
|
|
|
|
ole.lower_right_corner.z += d.z as f64;
|
|
|
|
|
}
|
|
|
|
|
EntityTransform::Scale { center, factor } => {
|
|
|
|
|
let scale = |v: f64, c: f64| c + (v - c) * (*factor as f64);
|
|
|
|
|
ole.upper_left_corner.x = scale(ole.upper_left_corner.x, center.x as f64);
|
|
|
|
|
ole.upper_left_corner.y = scale(ole.upper_left_corner.y, center.y as f64);
|
|
|
|
|
ole.lower_right_corner.x = scale(ole.lower_right_corner.x, center.x as f64);
|
|
|
|
|
ole.lower_right_corner.y = scale(ole.lower_right_corner.y, center.y as f64);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TruckConvertible for Ole2Frame {
|
|
|
|
|
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
|
|
|
|
Some(to_truck(self))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
refactor(entities): collapse trait wrapper boilerplate via macro
Every entity file ended with the same three-trait wrapper block:
impl Grippable for T {
fn grips(&self) -> Vec<GripDef> { grips(self) }
fn apply_grip(&mut self, ...) { apply_grip(self, ...) }
}
impl PropertyEditable for T { ... }
impl Transformable for T { ... }
— 23 lines of pure delegation around five same-named free functions.
Replace with `crate::impl_entity_basics!(T);` (a macro in
entities/traits.rs that expands to the three trait impls).
Applied where the file already follows the free-fn delegation
convention (Arc, Circle, Ellipse, Insert, Leader, Line, LwPolyline,
MultiLeader, Ole2Frame, Point, Spline, Viewport). Files that inlined
the trait method bodies directly (Ray, XLine, Shape, Solid, Tolerance,
MLine, Mesh, Solid3D/Region/Body, Table, RasterImage, Wipeout,
Underlay, Hatch, Polyline, Polyline2D, Polyline3D, Dimension) left
alone — they already skip the indirection. Text/MText/Attribute use
the with-text-style flavour and will move once that variant is needed
beyond the existing two manual impls.
A second macro `impl_entity_basics_with_text_styles!` is included for
the text-like flavour but no entity uses it yet.
Net: -204 lines.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:16:06 +03:00
|
|
|
crate::impl_entity_basics!(Ole2Frame);
|
refactor(tess): move per-entity tessellation into entities/
Each entity's tessellation now lives alongside its definition in
entities/*.rs, attached to the entity type via a local trait:
LegacyTess — Viewport / Insert / Hatch / Ole2Frame
(fallback path for entities not on the truck pipeline)
LeaderTess — Leader
MultiLeaderTess — MultiLeader
DimensionTess — Dimension (carries its dim-only helpers too:
~1700 lines of dim_geometry, dim_text_*, format_*,
tolerance, suppression, etc.)
scene/tessellate.rs keeps only the cross-entity dispatcher plus the
shared dim machinery reused by leader/multileader/dimension
(ArrowKind, DimGeom, append_arrow, arrow_from_block, add_segment,
add_polyline, normalized_or, push_tri, color_or_inherit, entity_z,
offset_snap_pts). Cross-entity arc helpers + aci_to_rgba moved to
scene/tess_util.rs.
scene/tessellate.rs shrinks from ~3300 to 809 lines. Compile clean,
all 9 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:12:27 +03:00
|
|
|
|
2026-05-23 23:16:57 +03:00
|
|
|
impl crate::entities::traits::FallbackTess for Ole2Frame {
|
2026-06-11 00:04:37 +03:00
|
|
|
fn fallback_geometry(
|
|
|
|
|
&self,
|
|
|
|
|
world_offset: [f64; 3],
|
|
|
|
|
) -> crate::scene::tess_util::FallbackGeometry {
|
refactor(tess): move per-entity tessellation into entities/
Each entity's tessellation now lives alongside its definition in
entities/*.rs, attached to the entity type via a local trait:
LegacyTess — Viewport / Insert / Hatch / Ole2Frame
(fallback path for entities not on the truck pipeline)
LeaderTess — Leader
MultiLeaderTess — MultiLeader
DimensionTess — Dimension (carries its dim-only helpers too:
~1700 lines of dim_geometry, dim_text_*, format_*,
tolerance, suppression, etc.)
scene/tessellate.rs keeps only the cross-entity dispatcher plus the
shared dim machinery reused by leader/multileader/dimension
(ArrowKind, DimGeom, append_arrow, arrow_from_block, add_segment,
add_polyline, normalized_or, push_tri, color_or_inherit, entity_z,
offset_snap_pts). Cross-entity arc helpers + aci_to_rgba moved to
scene/tess_util.rs.
scene/tessellate.rs shrinks from ~3300 to 809 lines. Compile clean,
all 9 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:12:27 +03:00
|
|
|
// OLE objects carry a bounding rectangle in model space.
|
|
|
|
|
// Render a simple X-through-rectangle placeholder.
|
|
|
|
|
let [ox, oy, oz] = world_offset;
|
|
|
|
|
let x0 = (self.upper_left_corner.x - ox) as f32;
|
|
|
|
|
let y0 = (self.lower_right_corner.y - oy) as f32;
|
|
|
|
|
let x1 = (self.lower_right_corner.x - ox) as f32;
|
|
|
|
|
let y1 = (self.upper_left_corner.y - oy) as f32;
|
|
|
|
|
let z = (self.upper_left_corner.z - oz) as f32;
|
|
|
|
|
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
|
|
|
|
|
// Degenerate / unknown size — show a small cross.
|
|
|
|
|
let s = 0.5_f32;
|
2026-06-11 00:04:37 +03:00
|
|
|
return (vec![[-s, 0.0, 0.0], [s, 0.0, 0.0]], vec![], vec![], vec![]);
|
refactor(tess): move per-entity tessellation into entities/
Each entity's tessellation now lives alongside its definition in
entities/*.rs, attached to the entity type via a local trait:
LegacyTess — Viewport / Insert / Hatch / Ole2Frame
(fallback path for entities not on the truck pipeline)
LeaderTess — Leader
MultiLeaderTess — MultiLeader
DimensionTess — Dimension (carries its dim-only helpers too:
~1700 lines of dim_geometry, dim_text_*, format_*,
tolerance, suppression, etc.)
scene/tessellate.rs keeps only the cross-entity dispatcher plus the
shared dim machinery reused by leader/multileader/dimension
(ArrowKind, DimGeom, append_arrow, arrow_from_block, add_segment,
add_polyline, normalized_or, push_tri, color_or_inherit, entity_z,
offset_snap_pts). Cross-entity arc helpers + aci_to_rgba moved to
scene/tess_util.rs.
scene/tessellate.rs shrinks from ~3300 to 809 lines. Compile clean,
all 9 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:12:27 +03:00
|
|
|
}
|
|
|
|
|
let pts = vec![
|
|
|
|
|
// Outer rectangle
|
|
|
|
|
[x0, y0, z],
|
|
|
|
|
[x1, y0, z],
|
|
|
|
|
[x1, y0, z],
|
|
|
|
|
[x1, y1, z],
|
|
|
|
|
[x1, y1, z],
|
|
|
|
|
[x0, y1, z],
|
|
|
|
|
[x0, y1, z],
|
|
|
|
|
[x0, y0, z],
|
|
|
|
|
// Diagonal X
|
|
|
|
|
[x0, y0, z],
|
|
|
|
|
[x1, y1, z],
|
|
|
|
|
[f32::NAN, f32::NAN, f32::NAN],
|
|
|
|
|
[x1, y0, z],
|
|
|
|
|
[x0, y1, z],
|
|
|
|
|
];
|
|
|
|
|
(pts, vec![], vec![], vec![[x0, y0, z], [x1, y1, z]])
|
|
|
|
|
}
|
|
|
|
|
}
|