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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-16 11:33:46 +03:00
1 parent cd048165a9
commit 7fe7e0230d
160 files changed
+732 -715

No files matched your search

@@ -21,8 +21,8 @@ use std::sync::Arc;
use acadrust::types::{Color as AcadColor, LineWeight, Transform, Vector3};
use acadrust::{CadDocument, EntityType, Handle};
use crate::scene::tessellate;
use crate::scene::wire_model::{SnapHint, TangentGeom, WireModel};
use crate::scene::convert::tessellate;
use crate::scene::model::wire_model::{SnapHint, TangentGeom, WireModel};
const MAX_NESTING_DEPTH: usize = 32;
/// Skip wires whose world-AABB projects to fewer than this many pixels in
@@ -392,7 +392,7 @@ fn build_nested_ref(
// (`Batches::finalize`) so the same cached defn can serve renders
// against different backgrounds without rebuilding.
let (ins_color, ins_pat_len, ins_pat, ins_lw_px, _) =
crate::scene::render::render_style_for(doc, &EntityType::Insert(nested_ins.clone()));
crate::scene::view::render::render_style_for(doc, &EntityType::Insert(nested_ins.clone()));
let _ = bg_color;
NestedRef {
@@ -430,7 +430,7 @@ fn tessellate_sub_local(
// with the per-render bg, so the cache no longer has to rebuild on
// BACKGROUND / layout-switch — the dynamic adaptation tracks the
// live bg at render time.
let (sub_color, pat_len, pat, lw_px, aci) = crate::scene::render::render_style_for(doc, sub);
let (sub_color, pat_len, pat, lw_px, aci) = crate::scene::view::render::render_style_for(doc, sub);
let _ = bg_color;
let color_is_byblock = sub.common().color == AcadColor::ByBlock;
@@ -1087,7 +1087,7 @@ impl Batches {
// now so each render against a different bg gets the
// right pure-black ↔ pure-white flip without rebuilding
// the cached defn.
let color = crate::scene::render::adapt_to_bg(b.color, bg_color);
let color = crate::scene::view::render::adapt_to_bg(b.color, bg_color);
WireModel {
name: name.to_string(),
points: b.points,
+2
View File
@@ -0,0 +1,2 @@
pub mod block_cache;
pub mod properties;
+1 -1
View File
@@ -1,6 +1,6 @@
use acadrust::{EntityType, Handle};
use crate::scene::object::{PropSection, PropValue, Property};
use crate::scene::model::object::{PropSection, PropValue, Property};
pub fn general_section(entity: &EntityType) -> PropSection {
let common = entity.common();
@@ -5,7 +5,7 @@ use glam::Vec3;
use truck_modeling::{Edge, Solid, Vertex, Wire};
use crate::entities::traits::EntityTypeOps;
use crate::scene::wire_model::{SnapHint, TangentGeom};
use crate::scene::model::wire_model::{SnapHint, TangentGeom};
/// One group of glyph strokes with its world-space origin stored in f64.
/// Strokes are in glyph-local space (origin = [0,0]) so that the large
@@ -26,8 +26,8 @@ use acadrust::entities::acis::{
SatConeSurface, SatDocument, SatFace, SatPlaneSurface, SatSphereSurface, SatTorusSurface,
};
use crate::scene::mesh_model::{MeshLodSet, MeshModel};
use crate::scene::solid3d_tess::{
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
use crate::scene::convert::solid3d_tess::{
apply_body_transform, body_transform, collect_face_polygon, cone_axis_span, mesh_aabb,
};
@@ -302,7 +302,7 @@ fn torus_faces(torus: &SatTorusSurface) -> Vec<Face> {
/// Append meshes for every `spline-surface` face, reusing the truck
/// BSplineSurface grid sampler in `spline_tess`.
fn append_spline_faces(sat: &SatDocument, mesh: &mut MeshModel) {
use crate::scene::solid3d_tess::LodConfig;
use crate::scene::convert::solid3d_tess::LodConfig;
for face in sat.faces() {
let Some(surf_rec) = sat.resolve(face.surface()) else {
continue;
@@ -313,7 +313,7 @@ fn append_spline_faces(sat: &SatDocument, mesh: &mut MeshModel) {
let mut verts: Vec<[f32; 3]> = Vec::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
crate::scene::spline_tess::tess_spline_face(
crate::scene::convert::spline_tess::tess_spline_face(
sat,
&face,
LodConfig::HIGH,
+7
View File
@@ -0,0 +1,7 @@
pub mod acad_to_truck;
pub mod acis_to_truck;
pub mod truck_tess;
pub mod tessellate;
pub mod tess_util;
pub mod solid3d_tess;
pub mod spline_tess;
@@ -20,7 +20,7 @@ use acadrust::entities::acis::{
};
use acadrust::entities::{Body, Region, Solid3D};
use crate::scene::mesh_model::{MeshLodSet, MeshModel};
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
/// Per-LOD sampling density. Higher values = finer mesh = more triangles.
#[derive(Copy, Clone, Debug)]
@@ -107,7 +107,7 @@ fn tessellate_sat(
}
}
"spline-surface" => {
crate::scene::spline_tess::tess_spline_face(
crate::scene::convert::spline_tess::tess_spline_face(
sat,
&face,
lod,
@@ -141,7 +141,7 @@ fn tessellate_acis(
color: [f32; 4],
facet_res: f64,
) -> Option<MeshLodSet> {
if let Some(set) = crate::scene::acis_to_truck::tessellate_sat_truck(sat, name.clone(), color, facet_res) {
if let Some(set) = crate::scene::convert::acis_to_truck::tessellate_sat_truck(sat, name.clone(), color, facet_res) {
if std::env::var_os("OCS_TESS_DEBUG").is_some() {
let tris = set.lods.first().map(|m| m.indices.len() / 3).unwrap_or(0);
eprintln!("acis_tess[{name}]: truck ({tris} tris)");
@@ -10,7 +10,7 @@
use acadrust::entities::acis::{SatDocument, SatFace, SatRecord, SatToken};
use truck_modeling::{BSplineSurface, KnotVec, ParametricSurface, ParametricSurface3D, Point3};
use crate::scene::solid3d_tess::LodConfig;
use crate::scene::convert::solid3d_tess::LodConfig;
/// Tessellate one `spline-surface` face by sampling its B-spline surface.
/// Appends triangles to the shared mesh buffers; a no-op when the surface
@@ -1,5 +1,5 @@
// Shared helpers used by per-entity tessellation impls in `crate::entities`
// and the dispatcher in `crate::scene::tessellate`.
// and the dispatcher in `crate::scene::convert::tessellate`.
//
// Anything cross-entity (colour resolution, arc sampling, chord tolerance)
// lives here. Entity-specific helpers (dim format strings, mleader path
@@ -8,7 +8,7 @@
use acadrust::types::Color as AcadColor;
use glam::Vec3;
use crate::scene::wire_model::{SnapHint, TangentGeom, WireModel};
use crate::scene::model::wire_model::{SnapHint, TangentGeom, WireModel};
/// Output of the fallback per-entity geometry path used by entities not
/// covered by the truck topology pipeline (Viewport, Insert, Hatch
@@ -80,7 +80,7 @@ pub fn fill_chord_tol(radius: f64) -> f64 {
/// passes, load-time builds), falls back to [`fill_chord_tol`] so we
/// never under-sample.
pub fn wire_chord_tol(radius: f64) -> f64 {
match crate::scene::truck_tess::active_curve_tol() {
match crate::scene::convert::truck_tess::active_curve_tol() {
Some(t) => t.min(fill_chord_tol(radius)).max(1e-9),
None => fill_chord_tol(radius),
}
@@ -18,11 +18,11 @@ use acadrust::types::Color as AcadColor;
use acadrust::{CadDocument, EntityType, Handle};
use glam::Vec3;
use crate::scene::acad_to_truck::{convert, TruckObject};
use crate::scene::truck_tess::{
use crate::scene::convert::acad_to_truck::{convert, TruckObject};
use crate::scene::convert::truck_tess::{
tessellate_edge, tessellate_vertex, tessellate_wire, TruckTessResult,
};
use crate::scene::wire_model::{SnapHint, WireModel};
use crate::scene::model::wire_model::{SnapHint, WireModel};
// ── Public entry points ────────────────────────────────────────────────────
@@ -604,7 +604,7 @@ pub(crate) fn entity_z(entity: &EntityType) -> f32 {
// function stays as the dispatcher used by the main `tessellate()` path.
use crate::entities::traits::FallbackTess;
use crate::scene::tess_util::FallbackGeometry as Geometry;
use crate::scene::convert::tess_util::FallbackGeometry as Geometry;
fn fallback_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
match entity {
File renamed without changes.
+114 -132
View File
@@ -1,56 +1,38 @@
pub mod acad_to_truck;
pub mod block_cache;
mod camera;
pub mod complex_lt;
pub mod lff;
pub mod dispatch;
pub mod font_face;
pub mod grip;
pub mod hatch_model;
pub mod hatch_patterns;
pub mod hit_test;
pub mod image_model;
pub mod mesh_model;
pub mod model_solid;
pub mod object;
// Scene modules grouped by role:
// convert — DXF/ACIS entities → truck solids & tessellated geometry
// text — LFF stroke + TrueType font engines and shaping
// model — per-entity GPU render models (wire, hatch, mesh, image, object)
// pick — hit-testing, selection, grips, spatial index, xclip
// view — camera, transforms, viewport, render pipeline driver
// cache — block-definition and property caches
pub mod cache;
pub mod convert;
pub mod model;
pub mod pick;
pub mod pipeline;
pub mod properties;
pub mod quadtree;
pub mod text;
pub mod view;
/// Result of `Scene::entity_index()`. The wire path queries `tree` for
/// view-rect candidates and also always emits `unbounded_handles`
/// (entities with no usable bbox — legacy `UNBOUNDED_AABB` sentinel).
pub(super) struct EntityIndex {
pub tree: quadtree::QuadTree,
pub tree: pick::quadtree::QuadTree,
pub unbounded_handles: Vec<Handle>,
}
pub(crate) mod render;
mod selection;
pub mod acis_to_truck;
pub mod solid3d_tess;
pub mod spline_tess;
pub mod sysfont;
pub mod ttf_glyph;
pub mod tess_util;
pub mod tessellate;
pub mod transform;
pub mod truck_tess;
pub mod viewport_pane;
pub mod wire_model;
pub mod xclip;
use camera::Camera;
pub use camera::Projection;
pub use hatch_model::HatchModel;
pub use image_model::ImageModel;
pub use mesh_model::MeshLodSet;
pub use object::{GripApply, GripDef};
use view::camera::Camera;
pub use view::camera::Projection;
pub use model::hatch_model::HatchModel;
pub use model::image_model::ImageModel;
pub use model::mesh_model::MeshLodSet;
pub use model::object::{GripApply, GripDef};
pub use pipeline::uniforms::Uniforms;
pub use pipeline::viewcube::{
hit_test, hover_id, CubeRegion, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
};
pub use selection::SelectionState;
pub use wire_model::WireModel;
pub use pick::selection::SelectionState;
pub use model::wire_model::WireModel;
use crate::command::EntityTransform;
use acadrust::entities::{Block, BlockEnd, Insert as DxfInsert};
@@ -170,7 +152,7 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
//
// Cross-check the header against a per-entity AABB scan of MSPACE
// (same `bounding_box()` API and same SANE_EXTENT/zero-placeholder
// filters that `block_cache::build_defn` already uses for block defns)
// filters that `cache::block_cache::build_defn` already uses for block defns)
// and prefer the entity-scan when the header center drifts more than
// 10× its own half-span away from the entity centroid.
use rayon::prelude::*;
@@ -219,8 +201,8 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
} else {
[0.0; 3]
};
let (raw, ..) = render::render_style_for(doc, e);
let color = render::adapt_to_bg(raw, LOAD_BG);
let (raw, ..) = view::render::render_style_for(doc, e);
let color = view::render::adapt_to_bg(raw, LOAD_BG);
let model = match e {
EntityType::Hatch(dxf) => Scene::hatch_model_from_dxf(dxf, color, offset),
EntityType::Solid(solid) => Some(Scene::solid_hatch_model(solid, color, offset)),
@@ -250,8 +232,8 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
.par_iter()
.filter_map(|&handle| {
let e = doc.get_entity(handle)?;
let (raw, ..) = render::render_style_for(doc, e);
let color = render::adapt_to_bg(raw, LOAD_BG);
let (raw, ..) = view::render::render_style_for(doc, e);
let color = view::render::adapt_to_bg(raw, LOAD_BG);
crate::entities::solid3d::tessellate_volume(e, color, facet_res)
.map(|m| (handle, offset_mesh_lod_set(m, world_offset)))
})
@@ -268,7 +250,7 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
}
}
/// Mirrors `block_cache::SANE_EXTENT` — wire coords past this magnitude
/// Mirrors `cache::block_cache::SANE_EXTENT` — wire coords past this magnitude
/// are treated as corruption rather than precision-relevant geometry.
const WORLD_OFFSET_SANE_EXTENT: f64 = 1.0e8;
@@ -342,7 +324,7 @@ fn offset_centroid(
return None;
}
// Skip block-defn sentinels and AttributeDefinition — same as
// block_cache::build_defn. Their bboxes don't represent drawable
// cache::block_cache::build_defn. Their bboxes don't represent drawable
// MSPACE geometry.
if matches!(
e,
@@ -735,7 +717,7 @@ pub struct Scene {
/// Tessellated block definitions in block-local coords, keyed by geometry_epoch.
/// Lets Insert tessellation transform-copy cached wires instead of
/// clone+explode+re-tessellate per reference.
block_defn_cache: RefCell<Option<(u64, Arc<block_cache::BlockCache>)>>,
block_defn_cache: RefCell<Option<(u64, Arc<cache::block_cache::BlockCache>)>>,
/// Spatial index + always-emit list for top-level entities
/// (Phase 2.1). Lazily rebuilt by `entity_index()` on
/// `geometry_epoch` change. See `EntityIndex` for what each side
@@ -946,7 +928,7 @@ impl Scene {
/// Get (or build on miss) the block-definition cache for the current epoch.
/// Built single-threaded — recursive nested expansion makes parallelization
/// fiddly and the cache only rebuilds when geometry actually changes.
pub(super) fn block_cache_arc(&self) -> Arc<block_cache::BlockCache> {
pub(super) fn block_cache_arc(&self) -> Arc<cache::block_cache::BlockCache> {
{
let cache = self.block_defn_cache.borrow();
if let Some((epoch, ref arc)) = *cache {
@@ -965,7 +947,7 @@ impl Scene {
} else {
1.0
};
let built = block_cache::BlockCache::build(&self.document, anno, bg);
let built = cache::block_cache::BlockCache::build(&self.document, anno, bg);
let arc = Arc::new(built);
*self.block_defn_cache.borrow_mut() = Some((self.block_epoch, Arc::clone(&arc)));
arc
@@ -1035,7 +1017,7 @@ impl Scene {
.get_entity(handle)
.map(|e| self.render_style(e).0)
.unwrap_or([0.8, 0.8, 0.85, 1.0]);
if let Some(set) = crate::scene::model_solid::mesh_from_solid(&solid, color) {
if let Some(set) = crate::scene::model::model_solid::mesh_from_solid(&solid, color) {
self.meshes.insert(handle, set);
}
self.model_solids.insert(handle, solid);
@@ -1328,7 +1310,7 @@ impl Scene {
Some(HatchModel {
world_origin: [0.0, 0.0],
boundary: Arc::new(vec![[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]]),
pattern: crate::scene::hatch_model::HatchPattern::Solid,
pattern: crate::scene::model::hatch_model::HatchPattern::Solid,
name: "SOLID".to_string(),
color: self.paper_bg_color,
angle_offset: 0.0,
@@ -2115,7 +2097,7 @@ impl Scene {
.meshes
.iter()
.filter_map(|(h, set)| set.lods.first().map(|m| (*h, m)));
hit_test::mesh_click_hit(cursor, iter, view_proj, bounds)
pick::hit_test::mesh_click_hit(cursor, iter, view_proj, bounds)
}
/// Tessellate all non-invisible entities owned by `block_handle`.
@@ -2297,7 +2279,7 @@ impl Scene {
1.0
};
let blk_cache = self.block_cache_arc();
let blk_ref: &block_cache::BlockCache = &blk_cache;
let blk_ref: &cache::block_cache::BlockCache = &blk_cache;
// Zoom-adaptive curve sampling for top-level Edge tessellation. Target
// ~0.5 px chord height — far-out arcs that used to emit hundreds of
// segments now collapse to a handful. The guard clears the override
@@ -2306,11 +2288,11 @@ impl Scene {
struct CurveTolGuard;
impl Drop for CurveTolGuard {
fn drop(&mut self) {
crate::scene::truck_tess::set_curve_tol_override(None);
crate::scene::convert::truck_tess::set_curve_tol_override(None);
}
}
let _tol_guard = wpp.map(|w| {
crate::scene::truck_tess::set_curve_tol_override(Some((w * 0.5) as f64));
crate::scene::convert::truck_tess::set_curve_tol_override(Some((w * 0.5) as f64));
CurveTolGuard
});
// Per-entity tessellation memo (Phase 2.2) — only on the culled Model
@@ -2550,7 +2532,7 @@ impl Scene {
}
None => [-1.0, -1.0, 1.0, 1.0],
};
let mut tree = quadtree::QuadTree::new(root);
let mut tree = pick::quadtree::QuadTree::new(root);
for (h, ab) in items {
tree.insert(h, ab);
}
@@ -3021,7 +3003,7 @@ impl Scene {
continue;
}
let adapted = render::adapt_to_bg(wire.color, self.paper_bg_color);
let adapted = view::render::adapt_to_bg(wire.color, self.paper_bg_color);
let [r, g, b, a] = adapted;
let mut out = wire.clone();
out.points = clipped;
@@ -3521,7 +3503,7 @@ impl Scene {
let color = layer_entry
.map(|l| &l.color)
.unwrap_or(&acadrust::types::Color::WHITE);
let [r, g, b, _] = crate::scene::tess_util::aci_to_rgba(color);
let [r, g, b, _] = crate::scene::convert::tess_util::aci_to_rgba(color);
[r, g, b, 1.0]
}
@@ -3589,7 +3571,7 @@ impl Scene {
let local = EntityTransform::Translate(-base);
for (old_handle, mut entity) in source_entities {
dispatch::apply_transform(&mut entity, &local);
view::dispatch::apply_transform(&mut entity, &local);
entity = crate::modules::home::modify::explode::normalize_entity_for_block(entity);
entity.common_mut().handle = Handle::NULL;
entity.common_mut().owner_handle = br_handle;
@@ -3667,7 +3649,7 @@ impl Scene {
m.color = self.render_style(e).0;
if let EntityType::Hatch(dxf) = e {
match &mut m.pattern {
hatch_model::HatchPattern::Pattern(_) => {
model::hatch_model::HatchPattern::Pattern(_) => {
m.angle_offset = dxf.pattern_angle as f32;
let anno = if self.current_layout == "Model" {
self.annotation_scale
@@ -3676,10 +3658,10 @@ impl Scene {
};
m.scale = dxf.pattern_scale as f32 * anno;
}
hatch_model::HatchPattern::Gradient { angle_deg, .. } => {
model::hatch_model::HatchPattern::Gradient { angle_deg, .. } => {
*angle_deg = dxf.pattern_angle.to_degrees() as f32;
}
hatch_model::HatchPattern::Solid => {}
model::hatch_model::HatchPattern::Solid => {}
}
}
}
@@ -3708,8 +3690,8 @@ impl Scene {
let selected = self.selected.contains(&ins.common.handle);
// XCLIP: clip this insert's exploded hatch fills to the boundary,
// matching how the line geometry is clipped in expand_insert.
let clip_poly = xclip::insert_spatial_filter(&self.document, ins)
.map(|sf| xclip::world_clip_polygon(sf, ins, hatch_offset));
let clip_poly = pick::xclip::insert_spatial_filter(&self.document, ins)
.map(|sf| pick::xclip::world_clip_polygon(sf, ins, hatch_offset));
// Walk the full block tree: `explode_from_document` only descends
// one level, so nested INSERTs are re-exploded here. Each level
// bakes its transform into the children it returns, so nested
@@ -3740,7 +3722,7 @@ impl Scene {
Self::hatch_model_from_dxf(&dxf, color, hatch_offset)
{
if let Some(poly) = &clip_poly {
let clipped = xclip::clip_hatch_boundary(
let clipped = pick::xclip::clip_hatch_boundary(
&model.boundary,
model.world_origin,
poly,
@@ -3797,7 +3779,7 @@ impl Scene {
}
models.push(HatchModel {
boundary: Arc::new(boundary),
pattern: hatch_model::HatchPattern::Solid,
pattern: model::hatch_model::HatchPattern::Solid,
name: "SOLID".into(),
color,
angle_offset: 0.0,
@@ -3861,7 +3843,7 @@ impl Scene {
}
models.push(HatchModel {
boundary: Arc::new(boundary),
pattern: hatch_model::HatchPattern::Solid,
pattern: model::hatch_model::HatchPattern::Solid,
name: "WIPEOUT_FILL".into(),
color: fill_color,
angle_offset: 0.0,
@@ -3957,7 +3939,7 @@ impl Scene {
// even on large UTM-scale drawings.
let to_xy = |x: f64, y: f64| -> [f64; 2] {
let (wx, wy, _) =
crate::scene::transform::ocs_point_to_wcs((x, y, dxf.elevation), normal);
crate::scene::view::transform::ocs_point_to_wcs((x, y, dxf.elevation), normal);
[wx - ox, wy - oy]
};
if dxf.paths.is_empty() {
@@ -4006,10 +3988,10 @@ impl Scene {
boundary.push(to_xy(v0.x, v0.y));
continue;
};
let segs = tess_util::arc_segments(
let segs = convert::tess_util::arc_segments(
arc.radius,
arc.sweep.abs(),
tess_util::fill_chord_tol(arc.radius),
convert::tess_util::fill_chord_tol(arc.radius),
);
for j in 0..segs {
let s = arc.sample(j as f64 / segs as f64);
@@ -4027,15 +4009,15 @@ impl Scene {
boundary.push(to_xy(line.end.x, line.end.y));
}
BoundaryEdge::CircularArc(arc) => {
let (sa, span) = tess_util::arc_signed_span(
let (sa, span) = convert::tess_util::arc_signed_span(
arc.start_angle,
arc.end_angle,
arc.counter_clockwise,
);
let segs = tess_util::arc_segments(
let segs = convert::tess_util::arc_segments(
arc.radius,
span.abs(),
tess_util::fill_chord_tol(arc.radius),
convert::tess_util::fill_chord_tol(arc.radius),
);
for i in 0..=segs {
let t = sa + span * (i as f64 / segs as f64);
@@ -4054,15 +4036,15 @@ impl Scene {
.major_axis_endpoint
.y
.atan2(ell.major_axis_endpoint.x);
let (sa, span) = tess_util::arc_signed_span(
let (sa, span) = convert::tess_util::arc_signed_span(
ell.start_angle,
ell.end_angle,
ell.counter_clockwise,
);
let segs = tess_util::arc_segments(
let segs = convert::tess_util::arc_segments(
r_maj,
span.abs(),
tess_util::fill_chord_tol(r_maj),
convert::tess_util::fill_chord_tol(r_maj),
);
let (cr, sr) = (rot.cos(), rot.sin());
for i in 0..=segs {
@@ -4116,7 +4098,7 @@ impl Scene {
let diag = ((sp_max_x - sp_min_x).powi(2)
+ (sp_max_y - sp_min_y).powi(2))
.sqrt();
let tol = tess_util::fill_chord_tol(diag.max(1.0));
let tol = convert::tess_util::fill_chord_tol(diag.max(1.0));
let mut sampled = false;
if knot_ok {
@@ -4193,7 +4175,7 @@ impl Scene {
if boundary.is_empty() {
return None;
}
boundary.truncate(hatch_model::MAX_HATCH_BOUNDARY_VERTS);
boundary.truncate(model::hatch_model::MAX_HATCH_BOUNDARY_VERTS);
let pattern = if dxf.gradient_color.is_enabled() {
let color2 = dxf
@@ -4204,15 +4186,15 @@ impl Scene {
.map(|(r, g, b)| [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0])
.unwrap_or(color);
let angle_deg = dxf.pattern_angle.to_degrees() as f32;
hatch_model::HatchPattern::Gradient { angle_deg, color2 }
model::hatch_model::HatchPattern::Gradient { angle_deg, color2 }
} else if dxf.is_solid {
hatch_model::HatchPattern::Solid
model::hatch_model::HatchPattern::Solid
} else {
let pat_name = &dxf.pattern.name;
if let Some(entry) = crate::scene::hatch_patterns::find(pat_name) {
if let Some(entry) = crate::scene::model::hatch_patterns::find(pat_name) {
entry.gpu.clone()
} else {
hatch_model::HatchPattern::Pattern(vec![hatch_model::PatFamily {
model::hatch_model::HatchPattern::Pattern(vec![model::hatch_model::PatFamily {
angle_deg: 0.0,
x0: 0.0,
y0: 0.0,
@@ -4329,11 +4311,11 @@ impl Scene {
};
let model = match &kind {
EntityType::Hatch(dxf) => {
let color = tess_util::aci_to_rgba(&dxf.common.color);
let color = convert::tess_util::aci_to_rgba(&dxf.common.color);
Self::hatch_model_from_dxf(dxf, color, offset)
}
EntityType::Solid(solid) => {
let color = tess_util::aci_to_rgba(&solid.common.color);
let color = convert::tess_util::aci_to_rgba(&solid.common.color);
Some(Self::solid_hatch_model(solid, color, offset))
}
_ => None,
@@ -4415,7 +4397,7 @@ impl Scene {
];
HatchModel {
boundary: std::sync::Arc::new(boundary),
pattern: hatch_model::HatchPattern::Solid,
pattern: model::hatch_model::HatchPattern::Solid,
name: "SOLID".into(),
color,
angle_offset: 0.0,
@@ -4430,7 +4412,7 @@ impl Scene {
let mut dxf = DxfHatch::new();
dxf.is_solid = matches!(
model.pattern,
crate::scene::hatch_model::HatchPattern::Solid
crate::scene::model::hatch_model::HatchPattern::Solid
);
// The boundary points arrive in local render space (world_offset
// already subtracted). The stored DXF entity must hold WCS, so add the
@@ -4453,8 +4435,8 @@ impl Scene {
let mut path = BoundaryPath::external();
path.add_edge(BoundaryEdge::Polyline(edge));
dxf.paths.push(path);
if let Some(entry) = crate::scene::hatch_patterns::find(&model.name) {
dxf.pattern = crate::scene::hatch_patterns::build_dxf_pattern(entry);
if let Some(entry) = crate::scene::model::hatch_patterns::find(&model.name) {
dxf.pattern = crate::scene::model::hatch_patterns::build_dxf_pattern(entry);
}
dxf.pattern_angle = model.angle_offset as f64;
dxf.pattern_scale = if model.scale.abs() > 1e-6 {
@@ -4768,7 +4750,7 @@ impl Scene {
field: &str,
) -> Option<String> {
use crate::entities::traits::EntityTypeOps;
use crate::scene::object::PropValue;
use crate::scene::model::object::PropValue;
match field {
"layer" => Some(entity.common().layer.clone()),
"color" => Some(Self::format_color(entity.common().color)),
@@ -4992,7 +4974,7 @@ impl Scene {
}
for &h in handles {
if let Some(entity) = self.document.get_entity_mut(h) {
dispatch::apply_transform(entity, t);
view::dispatch::apply_transform(entity, t);
}
if self.hatches.contains_key(&h) {
let existing_color = self.hatches[&h].color;
@@ -5048,12 +5030,12 @@ impl Scene {
.collect();
let mut new_handles = Vec::with_capacity(clones.len());
for mut entity in clones {
dispatch::apply_transform(&mut entity, t);
view::dispatch::apply_transform(&mut entity, t);
entity.common_mut().handle = Handle::NULL;
let h = self.document.add_entity(entity).unwrap_or(Handle::NULL);
if !h.is_null() {
let new_model = if let Some(EntityType::Hatch(dxf)) = self.document.get_entity(h) {
let color = tess_util::aci_to_rgba(&dxf.common.color);
let color = convert::tess_util::aci_to_rgba(&dxf.common.color);
Self::hatch_model_from_dxf(dxf, color, hatch_offset)
} else {
None
@@ -5083,7 +5065,7 @@ impl Scene {
.map(|p| [p.x, p.y, p.z]);
if let Some(entity) = self.document.get_entity_mut(handle) {
dispatch::apply_grip(entity, grip_id, apply);
view::dispatch::apply_grip(entity, grip_id, apply);
}
// Translate MeshModel vertices by the same delta the grip applied.
@@ -5121,7 +5103,7 @@ impl Scene {
};
match self.document.get_entity(handle) {
Some(EntityType::Hatch(dxf)) => {
let color = tess_util::aci_to_rgba(&dxf.common.color);
let color = convert::tess_util::aci_to_rgba(&dxf.common.color);
if let Some(model) = Self::hatch_model_from_dxf(dxf, color, hatch_offset) {
self.hatches.insert(handle, model);
} else {
@@ -5129,7 +5111,7 @@ impl Scene {
}
}
Some(EntityType::Solid(solid)) => {
let color = tess_util::aci_to_rgba(&solid.common.color);
let color = convert::tess_util::aci_to_rgba(&solid.common.color);
self.hatches
.insert(handle, Self::solid_hatch_model(solid, color, hatch_offset));
}
@@ -5262,7 +5244,7 @@ impl Scene {
} else {
vd.x.atan2(-vd.y)
};
let rotation = camera::yaw_pitch_to_quat(yaw, pitch, 0.0);
let rotation = view::camera::yaw_pitch_to_quat(yaw, pitch, 0.0);
let view_right = rotation * glam::Vec3::X;
let view_up = rotation * glam::Vec3::Y;
let base = if model_space {
@@ -5288,7 +5270,7 @@ impl Scene {
cam.yaw = yaw;
cam.pitch = pitch;
cam.fov_y = fov_y;
cam.projection = camera::Projection::Orthographic;
cam.projection = view::camera::Projection::Orthographic;
drop(cam);
self.camera_generation += 1;
true
@@ -5350,7 +5332,7 @@ impl Scene {
} else {
vd.x.atan2(-vd.y)
};
let rotation = camera::yaw_pitch_to_quat(yaw, pitch, 0.0);
let rotation = view::camera::yaw_pitch_to_quat(yaw, pitch, 0.0);
let view_right = rotation * glam::Vec3::X;
let view_up = rotation * glam::Vec3::Y;
// view_target is WCS; wire-space subtracts world_offset.
@@ -5368,7 +5350,7 @@ impl Scene {
rotation,
distance,
fov_y,
projection: camera::Projection::Orthographic,
projection: view::camera::Projection::Orthographic,
yaw,
pitch,
})
@@ -5581,7 +5563,7 @@ impl Scene {
} else {
vd.x.atan2(-vd.y)
};
let rotation = camera::yaw_pitch_to_quat(yaw, pitch, 0.0);
let rotation = view::camera::yaw_pitch_to_quat(yaw, pitch, 0.0);
let view_right = rotation * glam::Vec3::X;
let view_up = rotation * glam::Vec3::Y;
@@ -5604,7 +5586,7 @@ impl Scene {
cam.yaw = yaw;
cam.pitch = pitch;
cam.fov_y = fov_y;
cam.projection = camera::Projection::Orthographic;
cam.projection = view::camera::Projection::Orthographic;
drop(cam);
self.camera_generation += 1;
@@ -6387,8 +6369,8 @@ impl Scene {
let mut sheet_cam = self.camera.borrow().clone();
sheet_cam.yaw = 0.0;
sheet_cam.pitch = std::f32::consts::FRAC_PI_2;
sheet_cam.rotation = camera::yaw_pitch_to_quat(0.0, std::f32::consts::FRAC_PI_2, 0.0);
sheet_cam.projection = camera::Projection::Orthographic;
sheet_cam.rotation = view::camera::yaw_pitch_to_quat(0.0, std::f32::consts::FRAC_PI_2, 0.0);
sheet_cam.projection = view::camera::Projection::Orthographic;
out.push(ViewportInstance {
handle: Handle::NULL,
tile_idx: None,
@@ -6523,7 +6505,7 @@ impl Scene {
let mut m = model.clone();
m.color = self.render_style(entity).0;
if let EntityType::Hatch(dxf) = entity {
if let hatch_model::HatchPattern::Pattern(_) = &m.pattern {
if let model::hatch_model::HatchPattern::Pattern(_) = &m.pattern {
m.angle_offset = dxf.pattern_angle as f32;
m.scale = dxf.pattern_scale as f32;
}
@@ -6576,7 +6558,7 @@ impl Scene {
}
models.push(HatchModel {
boundary: Arc::new(boundary),
pattern: hatch_model::HatchPattern::Solid,
pattern: model::hatch_model::HatchPattern::Solid,
name: "WIPEOUT_FILL".into(),
color: fill_color,
angle_offset: 0.0,
@@ -6592,7 +6574,7 @@ impl Scene {
/// Build a Camera oriented and scaled to match a paper-space Viewport entity.
/// Used by `active_viewports` to render model-space content through each
/// content viewport's own view direction and scale.
fn camera_for_viewport(&self, vp_handle: Handle) -> Option<camera::Camera> {
fn camera_for_viewport(&self, vp_handle: Handle) -> Option<view::camera::Camera> {
let vp = match self.document.get_entity(vp_handle) {
Some(EntityType::Viewport(vp)) => vp,
_ => return None,
@@ -6615,7 +6597,7 @@ impl Scene {
vd.x.atan2(-vd.y)
};
let rotation = camera::yaw_pitch_to_quat(yaw, pitch, 0.0);
let rotation = view::camera::yaw_pitch_to_quat(yaw, pitch, 0.0);
// view_target is in raw model/WCS coords; the GPU renderer works in
// wire-space (model - world_offset), so subtract world_offset here.
@@ -6671,12 +6653,12 @@ impl Scene {
// ortho_size = distance * tan(fov_y/2) => distance = view_height/2 / tan(fov_y/2)
let distance = ((view_height / 2.0) / (fov_y * 0.5).tan()).max(0.001);
Some(camera::Camera {
Some(view::camera::Camera {
target,
rotation,
distance,
fov_y,
projection: camera::Projection::Orthographic,
projection: view::camera::Projection::Orthographic,
yaw,
pitch,
})
@@ -6999,7 +6981,7 @@ fn tessellate_entity(
bg_color: [f32; 4],
anno_scale: f32,
e: &EntityType,
block_cache: Option<&block_cache::BlockCache>,
block_cache: Option<&cache::block_cache::BlockCache>,
// World-space XY view AABB (post `world_offset` subtraction). When
// `Some`, entities whose AABB doesn't intersect this rect are skipped.
view_aabb: Option<[f32; 4]>,
@@ -7021,14 +7003,14 @@ fn tessellate_entity(
let ab = entity_aabb(e, world_offset);
if ab != WireModel::UNBOUNDED_AABB {
if let Some(view) = view_aabb {
if block_cache::aabb_disjoint_xy(ab, view) {
if cache::block_cache::aabb_disjoint_xy(ab, view) {
return vec![];
}
}
if let Some(wpp) = world_per_pixel {
let w_px = (ab[2] - ab[0]).abs();
let h_px = (ab[3] - ab[1]).abs();
// Keep in sync with `block_cache::MIN_PIXEL_SIZE`.
// Keep in sync with `cache::block_cache::MIN_PIXEL_SIZE`.
// Text/MText have their own LOD ladder below
// (baseline-line / greek / full) and must reach it
// even when projected size is sub-5 px.
@@ -7053,8 +7035,8 @@ fn tessellate_entity(
// footprint doesn't drift when the camera
// crosses the LOD threshold. See #19.
let (entity_color, _, _, _, aci_idx) =
render::render_style_for(document, e);
let entity_color = render::adapt_to_bg(entity_color, bg_color);
view::render::render_style_for(document, e);
let entity_color = view::render::adapt_to_bg(entity_color, bg_color);
if is_3d_entity {
// `ab` is already in the local frame
// (entity_aabb subtracted world_offset
@@ -7116,7 +7098,7 @@ fn tessellate_entity(
} else {
(0.0_f32, [0.0f32; 8])
};
let mut wires = tessellate::tessellate(
let mut wires = convert::tessellate::tessellate(
document,
h,
e,
@@ -7136,10 +7118,10 @@ fn tessellate_entity(
}
let (entity_color, pattern_length, pattern, line_weight_px, aci) =
render::render_style_for(document, e);
let entity_color = render::adapt_to_bg(entity_color, bg_color);
view::render::render_style_for(document, e);
let entity_color = view::render::adapt_to_bg(entity_color, bg_color);
let lt_scale = document.header.linetype_scale as f32 * e.common().linetype_scale as f32;
let lt_name = render::linetype_name_for(document, e);
let lt_name = view::render::linetype_name_for(document, e);
// PSLTSCALE: scale linetype dashes by viewport anno_scale so they appear uniform in paper space.
let pslt_factor = if document.header.paper_space_linetype_scaling {
anno_scale
@@ -7315,8 +7297,8 @@ fn tessellate_entity(
if let EntityType::Insert(ins) = e {
// Resolve the INSERT's own style so ByBlock sub-entities can inherit it.
let (ins_color, ins_pat_len, ins_pat, ins_lw_px, _) = render::render_style_for(document, e);
let ins_color = render::adapt_to_bg(ins_color, bg_color);
let (ins_color, ins_pat_len, ins_pat, ins_lw_px, _) = view::render::render_style_for(document, e);
let ins_color = view::render::adapt_to_bg(ins_color, bg_color);
let [ox, oy, oz] = world_offset;
let ip = glam::Vec3::new(
(ins.insert_point.x - ox) as f32,
@@ -7332,7 +7314,7 @@ fn tessellate_entity(
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: 1.0,
snap_pts: vec![(ip, wire_model::SnapHint::Insertion)],
snap_pts: vec![(ip, model::wire_model::SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
@@ -7349,7 +7331,7 @@ fn tessellate_entity(
.get(&ins.block_name)
.map(|br| br.flags.is_xref || br.flags.is_xref_overlay)
.unwrap_or(false);
if let Some(mut wires) = block_cache::expand_insert(
if let Some(mut wires) = cache::block_cache::expand_insert(
cache,
ins,
h,
@@ -7368,9 +7350,9 @@ fn tessellate_entity(
// XCLIP: if this INSERT carries an enabled spatial filter,
// clip the expanded block geometry to the boundary polygon so
// only the portion inside the clip is drawn.
if let Some(sf) = xclip::insert_spatial_filter(document, ins) {
let poly = xclip::world_clip_polygon(sf, ins, world_offset);
xclip::clip_wires(&mut wires, &poly);
if let Some(sf) = pick::xclip::insert_spatial_filter(document, ins) {
let poly = pick::xclip::world_clip_polygon(sf, ins, world_offset);
pick::xclip::clip_wires(&mut wires, &poly);
}
// Per-INSERT attribute values. The block defn carries the
@@ -7413,7 +7395,7 @@ fn tessellate_entity(
.map(crate::modules::home::modify::explode::normalize_insert_entity)
.flat_map(|sub| {
let (sub_color, sub_pattern_length, sub_pattern, sub_line_weight_px, sub_aci) =
render::render_style_for_block_sub(
view::render::render_style_for_block_sub(
document,
&sub,
ins_color,
@@ -7421,16 +7403,16 @@ fn tessellate_entity(
ins_pat,
ins_lw_px,
);
let sub_color = render::adapt_to_bg(sub_color, bg_color);
let sub_color = view::render::adapt_to_bg(sub_color, bg_color);
let sub_color = if is_xref && !sel {
block_cache::fade_toward_bg(sub_color, bg_color)
cache::block_cache::fade_toward_bg(sub_color, bg_color)
} else {
sub_color
};
let sub_aabb = entity_aabb(&sub, world_offset);
let sub_pattern_length = sub_pattern_length * pslt_factor;
let sub_pattern = sub_pattern.map(|v| v * pslt_factor);
let mut wires = tessellate::tessellate(
let mut wires = convert::tessellate::tessellate(
document,
h,
&sub,
@@ -7591,7 +7573,7 @@ fn tessellate_entity(
}
}
let mut bases = tessellate::tessellate(
let mut bases = convert::tessellate::tessellate(
document,
h,
e,
@@ -7615,7 +7597,7 @@ fn tessellate_entity(
// this path.
if let Some(clt) = crate::linetypes::complex_lt(lt_name) {
if let Some(base) = bases.first() {
let mut wires = complex_lt::apply_along(
let mut wires = text::complex_lt::apply_along(
&base.name,
&base.points,
clt,
File renamed without changes.
@@ -7,7 +7,7 @@
use std::sync::OnceLock;
use crate::patterns::PatLineDef;
use crate::scene::hatch_model::{HatchPattern, PatFamily};
use crate::scene::model::hatch_model::{HatchPattern, PatFamily};
use acadrust::entities::{HatchPattern as DxfPattern, HatchPatternLine};
use acadrust::types::Vector2;
File renamed without changes.
File renamed without changes.
+7
View File
@@ -0,0 +1,7 @@
pub mod wire_model;
pub mod hatch_model;
pub mod hatch_patterns;
pub mod image_model;
pub mod mesh_model;
pub mod model_solid;
pub mod object;
@@ -8,7 +8,7 @@
use truck_modeling::{builder, Point3, Rad, Solid, Vector3, Wire};
use crate::scene::mesh_model::{MeshLodSet, MeshModel};
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
const FULL: f64 = 7.0; // > 2π → builder closes the revolution
@@ -99,7 +99,7 @@ pub fn torus_solid(center: [f64; 3], major: f64, minor: f64) -> Solid {
/// fallback draws these as a wireframe over the shaded mesh, and hit-testing
/// uses their points).
pub fn edge_wires(solid: &Solid) -> Vec<acadrust::entities::Wire> {
use crate::scene::truck_tess::{tessellate_edge, TruckTessResult};
use crate::scene::convert::truck_tess::{tessellate_edge, TruckTessResult};
use acadrust::types::Vector3;
let mut wires = Vec::new();
for shell in solid.boundaries() {
@@ -151,7 +151,7 @@ pub fn boolean(op: Bool, a: &Solid, b: &Solid) -> Option<Solid> {
/// Tessellate a truck `Solid` into a single-LOD `MeshLodSet` (world-space,
/// before world_offset is applied by the caller).
pub fn mesh_from_solid(solid: &Solid, color: [f32; 4]) -> Option<MeshLodSet> {
use crate::scene::truck_tess::{tessellate_solid, TruckTessResult};
use crate::scene::convert::truck_tess::{tessellate_solid, TruckTessResult};
match tessellate_solid(solid, [0.0; 3]) {
TruckTessResult::Mesh {
verts,
File renamed without changes.
File renamed without changes.
+1 -1
View File
@@ -4,7 +4,7 @@ use acadrust::Handle;
use glam::{Mat4, Vec3};
use iced::{Point, Rectangle};
use super::object::{GripDef, GripShape};
use crate::scene::model::object::{GripDef, GripShape};
/// Pixel radius for grip hit-detection.
pub const GRIP_THRESHOLD_PX: f32 = 8.0;
@@ -10,9 +10,9 @@ use acadrust::Handle;
use glam::{Mat4, Vec3};
use iced::{Point, Rectangle};
use super::hatch_model::HatchModel;
use super::mesh_model::MeshModel;
use super::wire_model::WireModel;
use crate::scene::model::hatch_model::HatchModel;
use crate::scene::model::mesh_model::MeshModel;
use crate::scene::model::wire_model::WireModel;
/// Pixel radius used for single-click wire detection.
const CLICK_THRESHOLD_PX: f32 = 8.0;
+5
View File
@@ -0,0 +1,5 @@
pub mod hit_test;
pub mod selection;
pub mod grip;
pub mod quadtree;
pub mod xclip;
File renamed without changes.
File renamed without changes.
@@ -15,7 +15,7 @@ use acadrust::objects::{ObjectType, SpatialFilter};
use acadrust::types::{Handle, Vector3};
use acadrust::CadDocument;
use crate::scene::wire_model::WireModel;
use crate::scene::model::wire_model::WireModel;
const NAN3: [f32; 3] = [f32::NAN, f32::NAN, f32::NAN];
@@ -113,7 +113,7 @@ pub fn clip_wires(wires: &mut Vec<WireModel>, poly: &[[f32; 2]]) {
/// Clip a hatch fill boundary to `poly`.
///
/// `boundary` is a hatch's NaN-separated loops in f32 offsets from
/// `world_origin` (the [`HatchModel`](crate::scene::hatch_model::HatchModel)
/// `world_origin` (the [`HatchModel`](crate::scene::model::hatch_model::HatchModel)
/// representation); `poly` is the clip ring in the same world space the hatch
/// occupies. Each loop is intersected with the clip polygon independently —
/// the even-odd island structure is preserved because intersecting every loop
+1 -1
View File
@@ -18,7 +18,7 @@
// render pass send the 3D side through a depth-only pipeline for
// HiddenLine while keeping the 2D side fully visible.
use crate::scene::wire_model::WireModel;
use crate::scene::model::wire_model::WireModel;
use iced::wgpu;
use iced::wgpu::util::DeviceExt;
+1 -1
View File
@@ -35,7 +35,7 @@
// InstanceBuffer + Boundary + Family + Dash. Both stages share group
// 1 with `read_only` access.
use crate::scene::hatch_model::{HatchModel, HatchPattern, PatFamily};
use crate::scene::model::hatch_model::{HatchModel, HatchPattern, PatFamily};
use iced::wgpu;
use iced::wgpu::util::DeviceExt;
+1 -1
View File
@@ -5,7 +5,7 @@
// binding 1 — BoundaryData (16384 bytes) boundary polygon vertices
// binding 2 — FamilyBatchData (1296 bytes) up to 16 line families + 128 dash values
use crate::scene::hatch_model::{HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS};
use crate::scene::model::hatch_model::{HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS};
use iced::wgpu;
use iced::wgpu::util::DeviceExt;
+1 -1
View File
@@ -5,7 +5,7 @@
// binding 1 — sampler (bilinear filtering)
// binding 2 — ImageParams (opacity uniform, 16 bytes)
use crate::scene::image_model::ImageModel;
use crate::scene::model::image_model::ImageModel;
use iced::wgpu;
use iced::wgpu::util::DeviceExt;
+1 -1
View File
@@ -7,7 +7,7 @@
// ------
// 40 B / vertex
use crate::scene::mesh_model::{MeshLodSet, MeshModel};
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
use iced::wgpu;
use iced::wgpu::util::DeviceExt;
+4 -4
View File
@@ -19,10 +19,10 @@ pub use uniforms::Uniforms;
pub use viewcube::ViewCubePipeline;
pub use wire_gpu::WireGpu;
use crate::scene::hatch_model::HatchModel;
use crate::scene::image_model::ImageModel;
use crate::scene::mesh_model::MeshLodSet;
use crate::scene::wire_model::WireModel;
use crate::scene::model::hatch_model::HatchModel;
use crate::scene::model::image_model::ImageModel;
use crate::scene::model::mesh_model::MeshLodSet;
use crate::scene::model::wire_model::WireModel;
/// MSAA sample count for the main drawing pipelines.
const MSAA_SAMPLES: u32 = 4;
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::scene::camera::Camera;
use crate::scene::view::camera::Camera;
use iced::Rectangle;
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
+1 -1
View File
@@ -22,7 +22,7 @@
// ------
// 76 B / instance
use crate::scene::wire_model::WireModel;
use crate::scene::model::wire_model::WireModel;
use iced::wgpu;
use rayon::prelude::*;
@@ -8,8 +8,8 @@
use crate::entities::text_support::resolve_dxf_special_chars;
use crate::linetypes::{ComplexLt, LtSegment};
use crate::scene::lff;
use crate::scene::wire_model::WireModel;
use crate::scene::text::lff;
use crate::scene::model::wire_model::WireModel;
// ── Public entry point ────────────────────────────────────────────────────
@@ -11,8 +11,8 @@
use std::ops::Deref;
use std::sync::Arc;
use crate::scene::lff::{self, Glyph};
use crate::scene::{sysfont, ttf_glyph};
use crate::scene::text::lff::{self, Glyph};
use crate::scene::text::{sysfont, ttf_glyph};
/// A glyph borrowed from a static LFF font or owned (cached `Arc`) from a TTF
/// face. Derefs to [`Glyph`] either way.
+28 -28
View File
@@ -24,32 +24,32 @@ use std::sync::{Mutex, OnceLock};
/// Every LibreCAD LFF font, keyed by its file stem (lower-case). Registered
/// under the upper-cased stem and the font's `# Name:` header.
const FONTS_SRC: &[(&str, &str)] = &[
("cyrillic_ii", include_str!("../../assets/fonts/cyrillic_ii.lff")),
("gothgbt", include_str!("../../assets/fonts/gothgbt.lff")),
("gothgrt", include_str!("../../assets/fonts/gothgrt.lff")),
("gothitt", include_str!("../../assets/fonts/gothitt.lff")),
("greekc", include_str!("../../assets/fonts/greekc.lff")),
("greeks", include_str!("../../assets/fonts/greeks.lff")),
("iso3098", include_str!("../../assets/fonts/iso3098.lff")),
("iso", include_str!("../../assets/fonts/iso.lff")),
("italicc", include_str!("../../assets/fonts/italicc.lff")),
("italict", include_str!("../../assets/fonts/italict.lff")),
("ltypeshp", include_str!("../../assets/fonts/ltypeshp.lff")),
("romanc", include_str!("../../assets/fonts/romanc.lff")),
("romand", include_str!("../../assets/fonts/romand.lff")),
("romans", include_str!("../../assets/fonts/romans.lff")),
("romant", include_str!("../../assets/fonts/romant.lff")),
("scriptc", include_str!("../../assets/fonts/scriptc.lff")),
("scripts", include_str!("../../assets/fonts/scripts.lff")),
("simplex", include_str!("../../assets/fonts/simplex.lff")),
("standard", include_str!("../../assets/fonts/standard.lff")),
("syastro", include_str!("../../assets/fonts/syastro.lff")),
("symap", include_str!("../../assets/fonts/symap.lff")),
("symath", include_str!("../../assets/fonts/symath.lff")),
("symbol", include_str!("../../assets/fonts/symbol.lff")),
("symeteo", include_str!("../../assets/fonts/symeteo.lff")),
("symusic", include_str!("../../assets/fonts/symusic.lff")),
("unicode", include_str!("../../assets/fonts/unicode.lff")),
("cyrillic_ii", include_str!("../../../assets/fonts/cyrillic_ii.lff")),
("gothgbt", include_str!("../../../assets/fonts/gothgbt.lff")),
("gothgrt", include_str!("../../../assets/fonts/gothgrt.lff")),
("gothitt", include_str!("../../../assets/fonts/gothitt.lff")),
("greekc", include_str!("../../../assets/fonts/greekc.lff")),
("greeks", include_str!("../../../assets/fonts/greeks.lff")),
("iso3098", include_str!("../../../assets/fonts/iso3098.lff")),
("iso", include_str!("../../../assets/fonts/iso.lff")),
("italicc", include_str!("../../../assets/fonts/italicc.lff")),
("italict", include_str!("../../../assets/fonts/italict.lff")),
("ltypeshp", include_str!("../../../assets/fonts/ltypeshp.lff")),
("romanc", include_str!("../../../assets/fonts/romanc.lff")),
("romand", include_str!("../../../assets/fonts/romand.lff")),
("romans", include_str!("../../../assets/fonts/romans.lff")),
("romant", include_str!("../../../assets/fonts/romant.lff")),
("scriptc", include_str!("../../../assets/fonts/scriptc.lff")),
("scripts", include_str!("../../../assets/fonts/scripts.lff")),
("simplex", include_str!("../../../assets/fonts/simplex.lff")),
("standard", include_str!("../../../assets/fonts/standard.lff")),
("syastro", include_str!("../../../assets/fonts/syastro.lff")),
("symap", include_str!("../../../assets/fonts/symap.lff")),
("symath", include_str!("../../../assets/fonts/symath.lff")),
("symbol", include_str!("../../../assets/fonts/symbol.lff")),
("symeteo", include_str!("../../../assets/fonts/symeteo.lff")),
("symusic", include_str!("../../../assets/fonts/symusic.lff")),
("unicode", include_str!("../../../assets/fonts/unicode.lff")),
];
/// AutoCAD / DXF SHX font names → LFF stem. Names that already match a stem
@@ -357,7 +357,7 @@ pub fn tessellate_text_run(
return vec![];
}
let face = crate::scene::font_face::Face::resolve(font_name);
let face = crate::scene::text::font_face::Face::resolve(font_name);
let scale = height / 9.0;
let wf = if width_factor < 0.0 {
width_factor.clamp(-100.0, -0.01)
@@ -407,7 +407,7 @@ pub fn tessellate_text_run(
return;
}
let family = ttf_family.unwrap_or("");
if let Some(run) = crate::scene::ttf_glyph::shape_run(family, seg) {
if let Some(run) = crate::scene::text::ttf_glyph::shape_run(family, seg) {
for g in &run.glyphs {
emit_glyph(out, &g.strokes, *cursor_x);
}
+5
View File
@@ -0,0 +1,5 @@
pub mod lff;
pub mod font_face;
pub mod sysfont;
pub mod ttf_glyph;
pub mod complex_lt;
File renamed without changes.
@@ -16,8 +16,8 @@
// boundary). Counters — the hole in "O" or "A" — come through as separate
// closed contours; the wire renderer simply draws every contour.
use crate::scene::lff::Glyph;
use crate::scene::sysfont;
use crate::scene::text::lff::Glyph;
use crate::scene::text::sysfont;
use rustc_hash::FxHashMap as HashMap;
use std::sync::{Arc, Mutex, OnceLock};
File renamed without changes.
@@ -5,8 +5,8 @@ use acadrust::{EntityType, Handle};
use crate::command::EntityTransform;
use crate::entities::traits::EntityTypeOps;
use crate::scene::object::{GripDef, PropSection};
use crate::scene::properties;
use crate::scene::model::object::{GripDef, PropSection};
use crate::scene::cache::properties;
pub fn grips(entity: &EntityType) -> Vec<GripDef> {
EntityTypeOps::grips(entity)
@@ -71,7 +71,7 @@ pub fn apply_geom_prop(entity: &mut EntityType, field: &str, value: &str) {
EntityTypeOps::apply_geom_prop(entity, field, value);
}
pub fn apply_grip(entity: &mut EntityType, grip_id: usize, apply: crate::scene::object::GripApply) {
pub fn apply_grip(entity: &mut EntityType, grip_id: usize, apply: crate::scene::model::object::GripApply) {
EntityTypeOps::apply_grip(entity, grip_id, apply);
}
+5
View File
@@ -0,0 +1,5 @@
pub mod camera;
pub mod transform;
pub mod viewport_pane;
pub mod render;
pub mod dispatch;
@@ -11,10 +11,10 @@ use iced::{Rectangle, Size};
use std::sync::Arc;
use super::pipeline::viewcube::{hover_id, VIEWCUBE_PX};
use super::pipeline::MultiPipeline;
use super::tess_util;
use super::{HatchModel, ImageModel, MeshLodSet, Scene, Uniforms, ViewportInstance, WireModel};
use crate::scene::pipeline::viewcube::{hover_id, VIEWCUBE_PX};
use crate::scene::pipeline::MultiPipeline;
use crate::scene::convert::tess_util;
use crate::scene::{HatchModel, ImageModel, MeshLodSet, Scene, Uniforms, ViewportInstance, WireModel};
// ── Camera hover state (shader::Program::State) ───────────────────────────
@@ -31,85 +31,85 @@ pub struct CameraState {
/// own inner `Pipeline` instance drawn into its own rectangle.
#[derive(Debug)]
pub struct ViewportData {
pub(super) wires: Arc<Vec<WireModel>>,
pub(in crate::scene) wires: Arc<Vec<WireModel>>,
/// Live command-preview / interim / grip-drag overlay wires. Kept out of
/// the main `wires` buffer so a drag re-uploads only this small set each
/// frame, never the resident base buffer. Drawn on top in the wire pass.
pub(super) preview_wires: Arc<Vec<WireModel>>,
pub(in crate::scene) preview_wires: Arc<Vec<WireModel>>,
/// 3DFACE entity wires — separated so they are uploaded to the dedicated
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
pub(super) face3d_wires: Arc<Vec<WireModel>>,
pub(in crate::scene) face3d_wires: Arc<Vec<WireModel>>,
/// Per-entity normalized draw-order depth (handle.value() → (0,1)), used
/// by the wire / face3d pipelines as a clip-z bias. WireModels carry no
/// depth field (84 construction sites); the bias is looked up by handle
/// at GPU-upload time from this map instead.
pub(super) draw_depths: Arc<rustc_hash::FxHashMap<u64, f32>>,
pub(super) hatches: Arc<Vec<HatchModel>>,
pub(in crate::scene) draw_depths: Arc<rustc_hash::FxHashMap<u64, f32>>,
pub(in crate::scene) hatches: Arc<Vec<HatchModel>>,
/// Wipeout fills — rendered in a separate pass AFTER wires.
pub(super) wipeout_hatches: Arc<Vec<HatchModel>>,
pub(super) images: Arc<Vec<ImageModel>>,
pub(super) meshes: Arc<Vec<MeshLodSet>>,
pub(super) uniforms: Uniforms,
pub(in crate::scene) wipeout_hatches: Arc<Vec<HatchModel>>,
pub(in crate::scene) images: Arc<Vec<ImageModel>>,
pub(in crate::scene) meshes: Arc<Vec<MeshLodSet>>,
pub(in crate::scene) uniforms: Uniforms,
/// Camera rotation matrix derived from the quaternion.
/// Used by the ViewCube pipeline — no gimbal lock.
pub(super) cam_rotation: Mat4,
pub(super) hover_region: Option<usize>,
pub(super) show_viewcube: bool,
pub(in crate::scene) cam_rotation: Mat4,
pub(in crate::scene) hover_region: Option<usize>,
pub(in crate::scene) show_viewcube: bool,
/// Header.fill_mode (FILLMODE): when false, hatch / wipeout / face3d-fill
/// uploads short-circuit so the renderer draws only wireframe.
pub(super) fill_mode: bool,
pub(in crate::scene) fill_mode: bool,
/// Per-view "Wireframe vs Solid" toggle. When `true`, 3D face fills
/// are dropped on the upload path so 3D faces draw as edges only.
/// Hatch / wipeout uploads are deliberately *not* gated by this flag —
/// the user toggle should only affect 3D solids, not 2D fills.
pub(super) view_wireframe: bool,
pub(in crate::scene) view_wireframe: bool,
/// Whether the active render mode wants 3D mesh fills uploaded. Off
/// in `Wireframe2D` / `Wireframe3D`; on for every shaded variant. Set
/// at the same point `view_wireframe` is computed so the two stay in
/// lock-step for the gating logic in `prepare()`.
pub(super) mesh_fill: bool,
pub(in crate::scene) mesh_fill: bool,
/// Whether the active render mode wants 3D mesh / face edges
/// rendered on top of fills. Most shaded modes turn this off; the
/// `*WithEdges` variants and the pure wireframes leave it on.
pub(super) show_3d_edges: bool,
pub(in crate::scene) show_3d_edges: bool,
/// HiddenLine routes 3D fills through a depth-only prepass so edges
/// occluded by closer geometry are culled by the LessEqual depth
/// test on the wire passes that follow.
pub(super) hidden_line: bool,
pub(super) geometry_epoch: u64,
pub(in crate::scene) hidden_line: bool,
pub(in crate::scene) geometry_epoch: u64,
/// Camera generation captured when this Primitive was assembled. Paired
/// with `geometry_epoch` so the per-frame scissor / LOD recompute runs.
pub(super) camera_generation: u64,
pub(in crate::scene) camera_generation: u64,
/// Content id of `wires` (Phase 3.2). Stable across a pure pan that reuses
/// the Model-tile tessellation, so `prepare` skips re-uploading the
/// world-space wire buffer when only the camera moved. Non-tile and
/// preview/interim frames carry a fresh id each time → always re-upload.
pub(super) wire_content_id: u64,
pub(in crate::scene) wire_content_id: u64,
/// `selected ∪ hover` handles for the GPU xray overlay. The highlight is no
/// longer baked into the wire tessellation, so `prepare` rebuilds the
/// selected-wire batch by filtering `wires` against this set.
pub(super) highlight_handles: Arc<rustc_hash::FxHashSet<acadrust::Handle>>,
pub(in crate::scene) highlight_handles: Arc<rustc_hash::FxHashSet<acadrust::Handle>>,
/// Bumped on selection / hover change. Paired with `wire_content_id` to
/// decide when the xray overlay batch needs rebuilding.
pub(super) selection_generation: u64,
pub(in crate::scene) selection_generation: u64,
/// Signature of the *selected set* only (not hover). Gates the static-buffer
/// re-upload (hatch tint, issue #71) so a hover doesn't re-upload every
/// hatch / face3d buffer on hatch-heavy drawings.
pub(super) selected_sig: u64,
pub(in crate::scene) selected_sig: u64,
/// Screen rectangle this viewport fills, **normalized** to the widget
/// bounds (each component in 0..1). A single full-widget view is
/// `(0, 0, 1, 1)`; tiled / floating viewports are sub-rectangles.
/// Normalized form lets `render()` derive the physical sub-clip from
/// the surface clip without needing the scale factor.
pub(super) screen_rect: Rectangle,
pub(in crate::scene) screen_rect: Rectangle,
}
#[derive(Debug)]
pub struct Primitive {
/// One entry per viewport drawn this frame (≥1).
pub(super) viewports: Vec<ViewportData>,
pub(in crate::scene) viewports: Vec<ViewportData>,
/// Background color used to clear each viewport's MSAA buffer.
pub(super) bg_color: [f32; 4],
pub(in crate::scene) bg_color: [f32; 4],
}
/// Flags the render pipeline consumes, derived from
@@ -428,7 +428,7 @@ fn crop_view_proj(view_proj: glam::Mat4, uo: f32, vo: f32, us: f32, vs: f32) ->
impl Scene {
/// Returns (entity_color, pattern_length, pattern, line_weight_px, aci).
pub(super) fn render_style(&self, e: &EntityType) -> ([f32; 4], f32, [f32; 8], f32, u8) {
pub(in crate::scene) fn render_style(&self, e: &EntityType) -> ([f32; 4], f32, [f32; 8], f32, u8) {
let (color, pl, pat, lw, aci) = render_style_for(&self.document, e);
let bg = if self.current_layout == "Model" {
self.bg_color
@@ -443,7 +443,7 @@ impl Scene {
/// Resolves the effective linetype name for an entity, falling back to the
/// layer's linetype when the entity's own linetype is "ByLayer".
pub(super) fn linetype_name_for<'a>(document: &'a CadDocument, e: &'a EntityType) -> &'a str {
pub(in crate::scene) fn linetype_name_for<'a>(document: &'a CadDocument, e: &'a EntityType) -> &'a str {
let elt = &e.common().linetype;
if elt.is_empty() || elt.eq_ignore_ascii_case("bylayer") {
document
@@ -458,7 +458,7 @@ pub(super) fn linetype_name_for<'a>(document: &'a CadDocument, e: &'a EntityType
/// Returns `(entity_color, pattern_length, pattern, line_weight_px, aci)` for
/// an entity, resolving ByLayer color and linetype from the document.
pub(super) fn render_style_for(
pub(in crate::scene) fn render_style_for(
document: &CadDocument,
e: &EntityType,
) -> ([f32; 4], f32, [f32; 8], f32, u8) {
@@ -570,7 +570,7 @@ impl Scene {
/// Model layout → one full-window viewport (more once tiled); paper
/// layout → one viewport per floating content viewport. Each entry is
/// rendered into its own screen rectangle by its own inner pipeline.
pub(super) fn build_viewports(
pub(in crate::scene) fn build_viewports(
&self,
bounds: Rectangle,
model_render_mode: acadrust::entities::ViewportRenderMode,
@@ -805,7 +805,7 @@ impl Scene {
/// The cube draws in the top-right of the *active model tile* (which fills
/// the canvas when there is a single tile), so the hover hit-test maps the
/// cursor into that tile's local space and uses the tile's dimensions.
pub(super) fn update_viewcube_state(
pub(in crate::scene) fn update_viewcube_state(
&self,
state: &mut CameraState,
bounds: Rectangle,
@@ -828,7 +828,7 @@ impl Scene {
}
}
pub(super) fn viewcube_mouse_interaction(&self, state: &CameraState) -> mouse::Interaction {
pub(in crate::scene) fn viewcube_mouse_interaction(&self, state: &CameraState) -> mouse::Interaction {
if state.hover_region.is_some() {
mouse::Interaction::Pointer
} else {
File renamed without changes.
@@ -7,7 +7,7 @@
//! dedicated inner `Pipeline` (kept in the `MultiPipeline` outer).
use super::render::{CameraState, Primitive};
use super::Scene;
use crate::scene::Scene;
use iced::widget::shader;
use iced::{mouse, Event, Rectangle};