2026-05-06 10:00:37 +03:00
|
|
|
// Grippable + PropertyEditable for Solid3D, Region, Body.
|
|
|
|
|
//
|
|
|
|
|
// Geometry lives in ACIS data — we cannot edit it via the properties panel.
|
|
|
|
|
// We expose the point_of_reference as a translate grip and show ACIS size
|
|
|
|
|
// as read-only info. Grip translate also updates wire points so the wire
|
|
|
|
|
// fallback stays in sync; the caller (scene/mod.rs apply_grip) translates
|
|
|
|
|
// the MeshModel vertices to match.
|
|
|
|
|
|
|
|
|
|
use acadrust::entities::{Body, Region, Solid3D};
|
|
|
|
|
use glam::Vec3;
|
|
|
|
|
|
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, ro_prop as ro};
|
2026-05-06 10:00:37 +03:00
|
|
|
use crate::entities::traits::{Grippable, PropertyEditable};
|
|
|
|
|
use crate::scene::object::{GripApply, GripDef, PropSection};
|
|
|
|
|
|
|
|
|
|
// ── shared helpers ────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-06-11 00:04:37 +03:00
|
|
|
fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 {
|
|
|
|
|
glam::DVec3::new(v.x, v.y, v.z)
|
2026-05-06 10:00:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn translate_wires(wires: &mut Vec<acadrust::entities::Wire>, d: Vec3) {
|
|
|
|
|
for wire in wires.iter_mut() {
|
|
|
|
|
for pt in wire.points.iter_mut() {
|
|
|
|
|
pt.x += d.x as f64;
|
|
|
|
|
pt.y += d.y as f64;
|
|
|
|
|
pt.z += d.z as f64;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn acis_size_str(has_data: bool, sat_len: usize, sab_len: usize, is_binary: bool) -> String {
|
|
|
|
|
if !has_data {
|
|
|
|
|
return "none".to_string();
|
|
|
|
|
}
|
|
|
|
|
if is_binary {
|
|
|
|
|
format!("{} bytes (SAB)", sab_len)
|
|
|
|
|
} else {
|
|
|
|
|
format!("{} bytes (SAT)", sat_len)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Solid3D ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
impl Grippable for Solid3D {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
2026-06-11 00:04:37 +03:00
|
|
|
vec![center_grip(0, dvec3(&self.point_of_reference))]
|
2026-05-06 10:00:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
if grip_id != 0 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if let GripApply::Translate(d) = apply {
|
|
|
|
|
self.point_of_reference.x += d.x as f64;
|
|
|
|
|
self.point_of_reference.y += d.y as f64;
|
|
|
|
|
self.point_of_reference.z += d.z as f64;
|
|
|
|
|
translate_wires(&mut self.wires, d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Solid3D {
|
|
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
|
|
|
|
let size = acis_size_str(
|
|
|
|
|
self.acis_data.has_data(),
|
|
|
|
|
self.acis_data.sat_data.len(),
|
|
|
|
|
self.acis_data.sab_data.len(),
|
|
|
|
|
self.acis_data.is_binary,
|
|
|
|
|
);
|
|
|
|
|
PropSection {
|
|
|
|
|
title: "Geometry".into(),
|
|
|
|
|
props: vec![
|
2026-05-12 10:36:15 +03:00
|
|
|
ro(
|
|
|
|
|
"Ref Pt X",
|
|
|
|
|
"s3d_px",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.x),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Ref Pt Y",
|
|
|
|
|
"s3d_py",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.y),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Ref Pt Z",
|
|
|
|
|
"s3d_pz",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.z),
|
|
|
|
|
),
|
2026-05-06 10:00:37 +03:00
|
|
|
ro("ACIS Data", "s3d_acis", size),
|
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(
|
|
|
|
|
"UID",
|
|
|
|
|
"s3d_uid",
|
|
|
|
|
if self.uid.is_empty() {
|
|
|
|
|
"(none)".to_string()
|
|
|
|
|
} else {
|
|
|
|
|
self.uid.clone()
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Silhouettes",
|
|
|
|
|
"s3d_silhouettes",
|
|
|
|
|
self.silhouettes.len().to_string(),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"History",
|
|
|
|
|
"s3d_history",
|
|
|
|
|
match self.history_handle {
|
|
|
|
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
|
|
|
|
_ => "(none)".to_string(),
|
|
|
|
|
},
|
|
|
|
|
),
|
2026-05-06 10:00:37 +03:00
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Region ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
impl Grippable for Region {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
2026-06-11 00:04:37 +03:00
|
|
|
vec![center_grip(0, dvec3(&self.point_of_reference))]
|
2026-05-06 10:00:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
if grip_id != 0 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if let GripApply::Translate(d) = apply {
|
|
|
|
|
self.point_of_reference.x += d.x as f64;
|
|
|
|
|
self.point_of_reference.y += d.y as f64;
|
|
|
|
|
self.point_of_reference.z += d.z as f64;
|
|
|
|
|
translate_wires(&mut self.wires, d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Region {
|
|
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
|
|
|
|
let size = acis_size_str(
|
|
|
|
|
self.acis_data.has_data(),
|
|
|
|
|
self.acis_data.sat_data.len(),
|
|
|
|
|
self.acis_data.sab_data.len(),
|
|
|
|
|
self.acis_data.is_binary,
|
|
|
|
|
);
|
|
|
|
|
PropSection {
|
|
|
|
|
title: "Geometry".into(),
|
|
|
|
|
props: vec![
|
2026-05-12 10:36:15 +03:00
|
|
|
ro(
|
|
|
|
|
"Ref Pt X",
|
|
|
|
|
"rgn_px",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.x),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Ref Pt Y",
|
|
|
|
|
"rgn_py",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.y),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Ref Pt Z",
|
|
|
|
|
"rgn_pz",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.z),
|
|
|
|
|
),
|
2026-05-06 10:00:37 +03:00
|
|
|
ro("ACIS Data", "rgn_acis", size),
|
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(
|
|
|
|
|
"UID",
|
|
|
|
|
"rgn_uid",
|
|
|
|
|
if self.uid.is_empty() {
|
|
|
|
|
"(none)".to_string()
|
|
|
|
|
} else {
|
|
|
|
|
self.uid.clone()
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Silhouettes",
|
|
|
|
|
"rgn_silhouettes",
|
|
|
|
|
self.silhouettes.len().to_string(),
|
|
|
|
|
),
|
2026-05-06 10:00:37 +03:00
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Body ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
impl Grippable for Body {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
2026-06-11 00:04:37 +03:00
|
|
|
vec![center_grip(0, dvec3(&self.point_of_reference))]
|
2026-05-06 10:00:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
if grip_id != 0 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if let GripApply::Translate(d) = apply {
|
|
|
|
|
self.point_of_reference.x += d.x as f64;
|
|
|
|
|
self.point_of_reference.y += d.y as f64;
|
|
|
|
|
self.point_of_reference.z += d.z as f64;
|
|
|
|
|
translate_wires(&mut self.wires, d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Body {
|
|
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
|
|
|
|
let size = acis_size_str(
|
|
|
|
|
self.acis_data.has_data(),
|
|
|
|
|
self.acis_data.sat_data.len(),
|
|
|
|
|
self.acis_data.sab_data.len(),
|
|
|
|
|
self.acis_data.is_binary,
|
|
|
|
|
);
|
|
|
|
|
PropSection {
|
|
|
|
|
title: "Geometry".into(),
|
|
|
|
|
props: vec![
|
2026-05-12 10:36:15 +03:00
|
|
|
ro(
|
|
|
|
|
"Ref Pt X",
|
|
|
|
|
"bdy_px",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.x),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Ref Pt Y",
|
|
|
|
|
"bdy_py",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.y),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Ref Pt Z",
|
|
|
|
|
"bdy_pz",
|
|
|
|
|
format!("{:.4}", self.point_of_reference.z),
|
|
|
|
|
),
|
2026-05-06 10:00:37 +03:00
|
|
|
ro("ACIS Data", "bdy_acis", size),
|
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(
|
|
|
|
|
"UID",
|
|
|
|
|
"bdy_uid",
|
|
|
|
|
if self.uid.is_empty() {
|
|
|
|
|
"(none)".to_string()
|
|
|
|
|
} else {
|
|
|
|
|
self.uid.clone()
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Silhouettes",
|
|
|
|
|
"bdy_silhouettes",
|
|
|
|
|
self.silhouettes.len().to_string(),
|
|
|
|
|
),
|
2026-05-06 10:00:37 +03:00
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
|
|
|
|
}
|
2026-05-26 17:22:02 +03:00
|
|
|
|
|
|
|
|
// ── Accessors for the Solid3D / Region / Body trio ─────────────────────────
|
|
|
|
|
//
|
|
|
|
|
// These three entity types share a common subset of fields (ACIS data
|
|
|
|
|
// + point_of_reference + wires fallback). Code that needs to treat them
|
|
|
|
|
// uniformly (mesh tess dispatch, fallback wires, grip translate) used
|
|
|
|
|
// to repeat a three-arm `match entity` block at every callsite — the
|
|
|
|
|
// helpers below collapse those to a single call.
|
|
|
|
|
|
|
|
|
|
use crate::scene::mesh_model::MeshLodSet;
|
|
|
|
|
use crate::scene::solid3d_tess;
|
2026-06-11 00:04:37 +03:00
|
|
|
use acadrust::{types::Vector3, EntityType};
|
2026-05-26 17:22:02 +03:00
|
|
|
|
|
|
|
|
/// `point_of_reference` of an ACIS-backed volume entity, if applicable.
|
|
|
|
|
pub fn point_of_reference(e: &EntityType) -> Option<&Vector3> {
|
|
|
|
|
match e {
|
|
|
|
|
EntityType::Solid3D(s) => Some(&s.point_of_reference),
|
|
|
|
|
EntityType::Region(r) => Some(&r.point_of_reference),
|
|
|
|
|
EntityType::Body(b) => Some(&b.point_of_reference),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pre-stored edge-wire fallback list (used when the SAT/SAB kernel
|
|
|
|
|
/// can't produce a mesh — drawings authored by SOLVIEW / 3DPLOT carry
|
|
|
|
|
/// these explicitly).
|
|
|
|
|
pub fn fallback_wires(e: &EntityType) -> Option<&[acadrust::entities::Wire]> {
|
|
|
|
|
match e {
|
|
|
|
|
EntityType::Solid3D(s) => Some(&s.wires),
|
|
|
|
|
EntityType::Region(r) => Some(&r.wires),
|
|
|
|
|
EntityType::Body(b) => Some(&b.wires),
|
2026-06-14 20:11:44 +03:00
|
|
|
EntityType::Surface(s) => Some(&s.wires),
|
2026-05-26 17:22:02 +03:00
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Run the appropriate `solid3d_tess::tessellate_*` for the entity,
|
|
|
|
|
/// returning `None` for non-volume entities or when the kernel fails.
|
2026-06-11 00:04:37 +03:00
|
|
|
pub fn tessellate_volume(e: &EntityType, color: [f32; 4], facet_res: f64) -> Option<MeshLodSet> {
|
2026-05-26 17:22:02 +03:00
|
|
|
match e {
|
|
|
|
|
EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color, facet_res),
|
|
|
|
|
EntityType::Region(r) => solid3d_tess::tessellate_region(r, color, facet_res),
|
|
|
|
|
EntityType::Body(b) => solid3d_tess::tessellate_body(b, color, facet_res),
|
2026-06-14 20:11:44 +03:00
|
|
|
EntityType::Surface(s) => solid3d_tess::tessellate_surface(s, color, facet_res),
|
2026-05-26 17:22:02 +03:00
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|