forked from 1b-agent/opencadstudio-upstream
refactor(scene): group modules into role-based subfolders
Move the 30 flat modules under src/scene/ into six folders by role: convert (entity→geometry), text (fonts/shaping), model (render models), pick (hit-test/selection/grips/xclip), view (camera/transform/render), cache (block + property caches). pipeline/ is unchanged. Full path migration — all `scene::X` references become `scene::<group>::X`, no compatibility re-exports. render.rs items that were `pub(super)` (parent was scene) become `pub(in crate::scene)` to keep their original scene-wide visibility. No behavior change; build and tests are green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd048165a9
commit
7fe7e0230d
160 files changed
+732
-715
No files matched your search
@@ -0,0 +1,202 @@
|
||||
use acadrust::types::{Transform, Vector3};
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
|
||||
#[inline]
|
||||
fn to_v3(v: Vec3) -> Vector3 {
|
||||
Vector3::new(v.x as f64, v.y as f64, v.z as f64)
|
||||
}
|
||||
|
||||
pub fn apply_standard_transform<T>(entity: &mut T, center: Vec3, angle_rad: f32)
|
||||
where
|
||||
T: acadrust::Entity,
|
||||
{
|
||||
let z_axis = Vector3::new(0.0, 0.0, 1.0);
|
||||
let t = Transform::from_translation(to_v3(-center))
|
||||
.then(&Transform::from_rotation(z_axis, angle_rad as f64))
|
||||
.then(&Transform::from_translation(to_v3(center)));
|
||||
entity.apply_transform(&t);
|
||||
}
|
||||
|
||||
pub fn apply_standard_scale<T>(entity: &mut T, center: Vec3, factor: f32)
|
||||
where
|
||||
T: acadrust::Entity,
|
||||
{
|
||||
let s = factor as f64;
|
||||
let t = Transform::from_scaling_with_origin(Vector3::new(s, s, s), to_v3(center));
|
||||
entity.apply_transform(&t);
|
||||
}
|
||||
|
||||
pub fn apply_standard_entity_transform<T, F>(entity: &mut T, t: &EntityTransform, mirror: F)
|
||||
where
|
||||
T: acadrust::Entity,
|
||||
F: FnOnce(&mut T, Vec3, Vec3),
|
||||
{
|
||||
match t {
|
||||
EntityTransform::Translate(d) => entity.translate(to_v3(*d)),
|
||||
EntityTransform::Rotate { center, angle_rad } => {
|
||||
apply_standard_transform(entity, *center, *angle_rad)
|
||||
}
|
||||
EntityTransform::Scale { center, factor } => apply_standard_scale(entity, *center, *factor),
|
||||
EntityTransform::Mirror { p1, p2 } => mirror(entity, *p1, *p2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reflection across the world-XY line through `p1`→`p2` as an acadrust
|
||||
/// `Transform`, for delegating MIRROR to acadrust's entity-aware
|
||||
/// `apply_transform` paths (which handle direction flags, stored-angle
|
||||
/// conventions and bulges themselves). Degenerate line → identity.
|
||||
pub fn reflection_about_xy_line(p1: Vec3, p2: Vec3) -> acadrust::types::Transform {
|
||||
use acadrust::types::{Matrix4, Transform};
|
||||
let dx = (p2.x - p1.x) as f64;
|
||||
let dy = (p2.y - p1.y) as f64;
|
||||
if dx * dx + dy * dy < 1e-12 {
|
||||
return Transform::identity();
|
||||
}
|
||||
let ang = dy.atan2(dx);
|
||||
let m = Matrix4::translation(p1.x as f64, p1.y as f64, 0.0)
|
||||
* Matrix4::rotation_z(ang)
|
||||
* Matrix4::scaling(1.0, -1.0, 1.0)
|
||||
* Matrix4::rotation_z(-ang)
|
||||
* Matrix4::translation(-(p1.x as f64), -(p1.y as f64), 0.0);
|
||||
Transform::from_matrix(m)
|
||||
}
|
||||
|
||||
pub fn reflect_xy_point(x: &mut f64, y: &mut f64, p1: Vec3, p2: Vec3) {
|
||||
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 {
|
||||
return;
|
||||
}
|
||||
let rx = *x - p1.x as f64;
|
||||
let ry = *y - p1.y as f64;
|
||||
let dot = rx * ax + ry * ay;
|
||||
let mx = 2.0 * dot * ax / len2 - rx;
|
||||
let my = 2.0 * dot * ay / len2 - ry;
|
||||
*x = p1.x as f64 + mx;
|
||||
*y = p1.y as f64 + my;
|
||||
}
|
||||
|
||||
pub fn mirror_xy_line(line: &mut acadrust::entities::Line, p1: Vec3, p2: Vec3) {
|
||||
reflect_xy_point(&mut line.start.x, &mut line.start.y, p1, p2);
|
||||
reflect_xy_point(&mut line.end.x, &mut line.end.y, p1, p2);
|
||||
}
|
||||
|
||||
/// DXF arbitrary-axis algorithm — returns the OCS X and Y basis vectors in WCS
|
||||
/// for a given entity normal vector.
|
||||
///
|
||||
/// Returns (Ax, Ay) each as (x, y, z) tuples. When normal ≈ (0,0,1) the
|
||||
/// function returns the standard basis ((1,0,0), (0,1,0)) without any
|
||||
/// cross-product computation.
|
||||
pub fn ocs_axes(normal: (f64, f64, f64)) -> ((f64, f64, f64), (f64, f64, f64)) {
|
||||
let (nx, ny, nz) = normal;
|
||||
// Fast path: normal is effectively Z-up, OCS = WCS.
|
||||
if nx.abs() < 1e-10 && ny.abs() < 1e-10 && (nz - 1.0).abs() < 1e-10 {
|
||||
return ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0));
|
||||
}
|
||||
// Arbitrary-axis algorithm (DXF spec).
|
||||
// When |Nx| < 1/64 and |Ny| < 1/64 use Wy=(0,1,0)×N, else Wx=(1,0,0)×N.
|
||||
let (ax_x, ax_y, ax_z) = if nx.abs() < 1.0 / 64.0 && ny.abs() < 1.0 / 64.0 {
|
||||
// (0,1,0) × (nx,ny,nz) = (nz, 0, -nx)
|
||||
let (x, y, z) = (nz, 0.0, -nx);
|
||||
let len = (x * x + y * y + z * z).sqrt().max(1e-12);
|
||||
(x / len, y / len, z / len)
|
||||
} else {
|
||||
// (1,0,0) × (nx,ny,nz) = (0, -nz, ny)
|
||||
let (x, y, z) = (0.0, -nz, ny);
|
||||
let len = (x * x + y * y + z * z).sqrt().max(1e-12);
|
||||
(x / len, y / len, z / len)
|
||||
};
|
||||
// Ay = N × Ax
|
||||
let ay_x = ny * ax_z - nz * ax_y;
|
||||
let ay_y = nz * ax_x - nx * ax_z;
|
||||
let ay_z = nx * ax_y - ny * ax_x;
|
||||
((ax_x, ax_y, ax_z), (ay_x, ay_y, ay_z))
|
||||
}
|
||||
|
||||
/// Transform a single OCS point to WCS using the given normal vector.
|
||||
#[inline]
|
||||
pub fn ocs_point_to_wcs(ocs: (f64, f64, f64), normal: (f64, f64, f64)) -> (f64, f64, f64) {
|
||||
let (ax, ay) = ocs_axes(normal);
|
||||
let (ox, oy, oz) = ocs;
|
||||
let (nx, ny, nz) = normal;
|
||||
(
|
||||
ox * ax.0 + oy * ay.0 + oz * nx,
|
||||
ox * ax.1 + oy * ay.1 + oz * ny,
|
||||
ox * ax.2 + oy * ay.2 + oz * nz,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod mirror_delegation_tests {
|
||||
use super::*;
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::traits::Transformable;
|
||||
use acadrust::entities::hatch::{BoundaryEdge, BoundaryPath, CircularArcEdge, LineEdge};
|
||||
use acadrust::entities::Hatch;
|
||||
use acadrust::types::Vector2;
|
||||
|
||||
// MIRROR on a hatch goes through reflection_about_xy_line + acadrust's
|
||||
// transform_hatch. Mirror a Line→Arc→Line path across a vertical line and
|
||||
// assert the arc stays endpoint-continuous, flips its direction flag, and
|
||||
// keeps its sweep magnitude (the stored-angle conventions the old
|
||||
// hand-rolled closure violated).
|
||||
#[test]
|
||||
fn hatch_mirror_keeps_arc_continuous_and_sweep() {
|
||||
let mut path = BoundaryPath::new();
|
||||
path.edges.push(BoundaryEdge::Line(LineEdge {
|
||||
start: Vector2::new(0.0, -1.0),
|
||||
end: Vector2::new(2.0, 0.0),
|
||||
}));
|
||||
path.edges.push(BoundaryEdge::CircularArc(CircularArcEdge {
|
||||
center: Vector2::new(1.0, 0.0),
|
||||
radius: 1.0,
|
||||
start_angle: 0.0,
|
||||
end_angle: std::f64::consts::PI,
|
||||
counter_clockwise: true,
|
||||
}));
|
||||
path.edges.push(BoundaryEdge::Line(LineEdge {
|
||||
start: Vector2::new(0.0, 0.0),
|
||||
end: Vector2::new(0.0, -1.0),
|
||||
}));
|
||||
let mut h = Hatch::new();
|
||||
h.paths.push(path);
|
||||
|
||||
// Mirror across the vertical line x = 5.
|
||||
h.apply_transform(&EntityTransform::Mirror {
|
||||
p1: Vec3::new(5.0, 0.0, 0.0),
|
||||
p2: Vec3::new(5.0, 1.0, 0.0),
|
||||
});
|
||||
|
||||
let edges = &h.paths[0].edges;
|
||||
let (l1, arc) = match (&edges[0], &edges[1]) {
|
||||
(BoundaryEdge::Line(a), BoundaryEdge::CircularArc(b)) => (a, b),
|
||||
_ => panic!("edge kinds changed"),
|
||||
};
|
||||
assert!(!arc.counter_clockwise, "mirror must flip the flag");
|
||||
let sweep = arc.end_angle - arc.start_angle;
|
||||
assert!(
|
||||
(sweep - std::f64::consts::PI).abs() < 1e-9,
|
||||
"stored sweep must stay π, got {sweep}"
|
||||
);
|
||||
// Stored-angle convention: true point of a CW edge is at -θ.
|
||||
let pt = |theta: f64| {
|
||||
let a = if arc.counter_clockwise { theta } else { -theta };
|
||||
(
|
||||
arc.center.x + arc.radius * a.cos(),
|
||||
arc.center.y + arc.radius * a.sin(),
|
||||
)
|
||||
};
|
||||
let (sx, sy) = pt(arc.start_angle);
|
||||
assert!(
|
||||
(sx - l1.end.x).abs() < 1e-9 && (sy - l1.end.y).abs() < 1e-9,
|
||||
"arc start {:?} must meet previous line end {:?}",
|
||||
(sx, sy),
|
||||
(l1.end.x, l1.end.y)
|
||||
);
|
||||
// Mirror of (2,0) across x=5 is (8,0).
|
||||
assert!((sx - 8.0).abs() < 1e-9 && sy.abs() < 1e-9);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user