cad-editor/src/entities/ole2frame.rs

206 lines
6.8 KiB
Rust
Raw Normal View History

use acadrust::entities::{Ole2Frame, OleObjectType};
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};
use crate::entities::traits::TruckConvertible;
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::model::object::{GripApply, GripDef, PropSection};
use crate::scene::model::wire_model::SnapHint;
fn to_truck(ole: &Ole2Frame) -> TruckEntity {
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;
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
let s = 0.5_f64;
return TruckEntity {
object: TruckObject::Lines(vec![[-s, 0.0, z], [s, 0.0, z]]),
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices: vec![],
fill_tris: vec![],
};
}
let cx = (x0 + x1) * 0.5;
let cy = (y0 + y1) * 0.5;
let pts: Vec<[f64; 3]> = vec![
// 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],
// Diagonal X
2026-05-12 10:36:15 +03:00
[x0, y0, z],
[x1, y1, z],
[f64::NAN; 3],
2026-05-12 10:36:15 +03:00
[x1, y0, z],
[x0, y1, z],
];
let center = glam::DVec3::new(cx, cy, z);
TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![(center, SnapHint::Center)],
tangent_geoms: vec![],
key_vertices: vec![[x0, y0, z], [x1, y1, z]],
fill_tris: vec![],
}
}
fn grips(ole: &Ole2Frame) -> Vec<GripDef> {
let ul = glam::DVec3::new(
ole.upper_left_corner.x,
ole.upper_left_corner.y,
ole.upper_left_corner.z,
);
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;
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
]
}
fn properties(ole: &Ole2Frame) -> Vec<PropSection> {
let type_str = match ole.ole_object_type {
OleObjectType::Link => "Link",
OleObjectType::Embedded => "Embedded",
OleObjectType::Static => "Static",
};
let width = (ole.lower_right_corner.x - ole.upper_left_corner.x).abs();
let height = (ole.upper_left_corner.y - ole.lower_right_corner.y).abs();
vec![
PropSection {
title: "Geometry".into(),
props: vec![
edit("Position X", "ole_ulx", ole.upper_left_corner.x),
edit("Position Y", "ole_uly", ole.upper_left_corner.y),
edit("Position Z", "ole_ulz", ole.upper_left_corner.z),
ro("Width", "ole_width", format!("{:.4}", width)),
ro("Height", "ole_height", format!("{:.4}", height)),
ro("Scale width", "ole_scale_width", String::new()),
ro("Scale height", "ole_scale_height", String::new()),
ro("Lock aspect", "ole_lock_aspect", String::new()),
],
},
PropSection {
title: "Misc".into(),
props: vec![
ro("Type", "ole_type", type_str),
ro("Plot quality", "ole_plot_quality", String::new()),
],
},
]
}
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;
};
match field {
"ole_ulx" => ole.upper_left_corner.x = v,
"ole_uly" => ole.upper_left_corner.y = v,
"ole_ulz" => ole.upper_left_corner.z = 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))
}
}
crate::impl_entity_basics!(Ole2Frame);
impl crate::entities::traits::FallbackTess for Ole2Frame {
fn fallback_geometry(
&self,
) -> crate::scene::convert::tess_util::FallbackGeometry {
// OLE objects carry a bounding rectangle in model space.
// Render a simple X-through-rectangle placeholder.
let x0 = self.upper_left_corner.x;
let y0 = self.lower_right_corner.y;
let x1 = self.lower_right_corner.x;
let y1 = self.upper_left_corner.y;
let z = self.upper_left_corner.z;
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
// Degenerate / unknown size — show a small cross.
let s = 0.5_f64;
return (vec![[-s, 0.0, 0.0], [s, 0.0, 0.0]], vec![], vec![], vec![]);
}
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],
[f64::NAN, f64::NAN, f64::NAN],
[x1, y0, z],
[x0, y1, z],
];
(pts, vec![], vec![], vec![[x0, y0, z], [x1, y1, z]])
}
}