forked from 1b-agent/opencadstudio-upstream
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
2baabdf4a0
commit
bddc050b3e
39 files changed
+26674
-26113
No files matched your search
@@ -0,0 +1,319 @@
|
||||
// Auto-split from scene/mod.rs. Pure text-move; behaviour unchanged.
|
||||
use super::*;
|
||||
|
||||
impl Scene {
|
||||
// ── Modify (transform / copy) ─────────────────────────────────────────
|
||||
|
||||
pub fn transform_entities(&mut self, handles: &[Handle], t: &EntityTransform) {
|
||||
// MIRRTEXT (header.mirror_text): when false AutoCAD positions text /
|
||||
// mtext / shape by the mirror but keeps the original rotation +
|
||||
// oblique so the text stays right-reading. Capture before the
|
||||
// transform and re-apply afterwards.
|
||||
let preserve_text_orientation =
|
||||
matches!(t, EntityTransform::Mirror { .. }) && !self.document.header.mirror_text;
|
||||
let mut text_orient_backup: Vec<(Handle, f64, f64, f64)> = Vec::new();
|
||||
if preserve_text_orientation {
|
||||
for &h in handles {
|
||||
if let Some(entity) = self.document.get_entity(h) {
|
||||
match entity {
|
||||
EntityType::Text(t) => {
|
||||
text_orient_backup.push((h, t.rotation, t.oblique_angle, 0.0))
|
||||
}
|
||||
EntityType::MText(m) => {
|
||||
text_orient_backup.push((h, m.rotation, 0.0, 0.0))
|
||||
}
|
||||
EntityType::Shape(s) => text_orient_backup.push((
|
||||
h,
|
||||
s.rotation,
|
||||
s.oblique_angle,
|
||||
s.relative_x_scale,
|
||||
)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// A dimension's final geometry is baked into a per-instance `*D`
|
||||
// block, and the render draws those sub-entities directly (not the
|
||||
// definition points). Transform them with the dimension, or it would
|
||||
// stay drawn in place while only its def points move.
|
||||
let dim_block_subs: Vec<Handle> = handles
|
||||
.iter()
|
||||
.filter_map(|&h| match self.document.get_entity(h) {
|
||||
Some(EntityType::Dimension(d)) => {
|
||||
let bn = d.base().block_name.clone();
|
||||
if bn.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(bn)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(|bn| {
|
||||
self.document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|br| br.name.eq_ignore_ascii_case(&bn))
|
||||
.map(|br| br.entity_handles.clone())
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
for &h in handles {
|
||||
if let Some(entity) = self.document.get_entity_mut(h) {
|
||||
view::dispatch::apply_transform(entity, t);
|
||||
}
|
||||
if self.hatches.contains_key(&h) {
|
||||
let existing_color = self.hatches[&h].color;
|
||||
let new_model = match self.document.get_entity(h) {
|
||||
Some(EntityType::Hatch(dxf)) => {
|
||||
Self::hatch_model_from_dxf(dxf, existing_color)
|
||||
}
|
||||
// A DXF SOLID renders as a solid-fill hatch; rebuild it from
|
||||
// the moved corners so the fill follows the transform.
|
||||
Some(EntityType::Solid(s)) => {
|
||||
Some(Self::solid_hatch_model(s, existing_color))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(model) = new_model {
|
||||
self.hatches.insert(h, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
if preserve_text_orientation {
|
||||
for (h, rot, oblique, x_scale) in text_orient_backup {
|
||||
if let Some(entity) = self.document.get_entity_mut(h) {
|
||||
match entity {
|
||||
EntityType::Text(t) => {
|
||||
t.rotation = rot;
|
||||
t.oblique_angle = oblique;
|
||||
}
|
||||
EntityType::MText(m) => {
|
||||
m.rotation = rot;
|
||||
}
|
||||
EntityType::Shape(s) => {
|
||||
s.rotation = rot;
|
||||
s.oblique_angle = oblique;
|
||||
s.relative_x_scale = x_scale;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move the baked dimension-block sub-entities too (collected above).
|
||||
for h in &dim_block_subs {
|
||||
if let Some(entity) = self.document.get_entity_mut(*h) {
|
||||
view::dispatch::apply_transform(entity, t);
|
||||
}
|
||||
}
|
||||
// Only the transformed entities changed (a top-level move/rotate/scale/
|
||||
// mirror never edits a block definition) — re-tessellate just those and
|
||||
// keep the block cache + every other entity's memoized wires.
|
||||
for &h in handles {
|
||||
self.mark_entity_dirty(h);
|
||||
}
|
||||
self.bump_geometry_no_blocks();
|
||||
}
|
||||
|
||||
/// Give a freshly-cloned entity brand-new handles for every *inline*
|
||||
/// sub-entity that stores one (INSERT attributes, 3D-polyline vertices).
|
||||
/// `document.add_entity` only assigns the top-level handle, so without this
|
||||
/// a copy keeps its source's sub-handles — duplicate handles that corrupt
|
||||
/// the saved DWG (file won't reopen in other CAD apps). Vertices that don't
|
||||
/// store a handle (LwPolyline / heavy 2D polyline) get one from the writer,
|
||||
/// so they need no fix-up here. (#129)
|
||||
pub(super) fn reset_clone_subhandles(doc: &mut acadrust::CadDocument, entity: &mut EntityType) {
|
||||
match entity {
|
||||
EntityType::Insert(ins) => {
|
||||
for att in ins.attributes.iter_mut() {
|
||||
att.common.handle = doc.allocate_handle();
|
||||
}
|
||||
}
|
||||
EntityType::Polyline3D(p) => {
|
||||
for v in p.vertices.iter_mut() {
|
||||
v.handle = doc.allocate_handle();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a freshly-cloned entity, allocating a new handle for it *and* every
|
||||
/// inline sub-entity so the copy never shares a handle with its source.
|
||||
/// Use this (not `add_entity`) whenever inserting a duplicate. (#129)
|
||||
pub fn add_entity_clone(&mut self, mut entity: EntityType) -> Handle {
|
||||
Self::reset_clone_subhandles(&mut self.document, &mut entity);
|
||||
entity.common_mut().handle = Handle::NULL;
|
||||
self.add_entity(entity)
|
||||
}
|
||||
|
||||
/// Duplicate the anonymous block `src_name`, transforming every sub-entity
|
||||
/// by `t`, and return the new block's name. A dimension's drawn geometry
|
||||
/// lives in such a baked `*D` block, so a copied dimension needs its own
|
||||
/// transformed block — otherwise it still references the source block and
|
||||
/// renders on top of the original instead of at the copy. Returns None when
|
||||
/// the source block is missing or empty. (#161)
|
||||
fn clone_transformed_block(&mut self, src_name: &str, t: &EntityTransform) -> Option<String> {
|
||||
let sub_handles = self
|
||||
.document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|br| br.name.eq_ignore_ascii_case(src_name))
|
||||
.map(|br| br.entity_handles.clone())?;
|
||||
if sub_handles.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Smallest free `*D<n>` anonymous name.
|
||||
let mut n = 0u64;
|
||||
let new_name = loop {
|
||||
let cand = format!("*D{n}");
|
||||
if self.document.block_records.get(&cand).is_none() {
|
||||
break cand;
|
||||
}
|
||||
n += 1;
|
||||
};
|
||||
let next = self.document.next_handle();
|
||||
let br_handle = Handle::new(next);
|
||||
let block_handle = Handle::new(next + 1);
|
||||
let end_handle = Handle::new(next + 2);
|
||||
let mut br = acadrust::tables::BlockRecord::new(&new_name);
|
||||
br.handle = br_handle;
|
||||
br.block_entity_handle = block_handle;
|
||||
br.block_end_handle = end_handle;
|
||||
self.document.block_records.add(br).ok()?;
|
||||
let mut block = Block::new(&new_name, acadrust::types::Vector3::ZERO);
|
||||
block.common.handle = block_handle;
|
||||
block.common.owner_handle = br_handle;
|
||||
self.document.add_entity(EntityType::Block(block)).ok()?;
|
||||
let mut block_end = BlockEnd::new();
|
||||
block_end.common.handle = end_handle;
|
||||
block_end.common.owner_handle = br_handle;
|
||||
self.document.add_entity(EntityType::BlockEnd(block_end)).ok()?;
|
||||
for sh in sub_handles {
|
||||
if let Some(mut sub) = self.document.get_entity(sh).cloned() {
|
||||
view::dispatch::apply_transform(&mut sub, t);
|
||||
Self::reset_clone_subhandles(&mut self.document, &mut sub);
|
||||
sub.common_mut().handle = Handle::NULL;
|
||||
sub.common_mut().owner_handle = br_handle;
|
||||
let _ = self.document.add_entity(sub);
|
||||
}
|
||||
}
|
||||
Some(new_name)
|
||||
}
|
||||
|
||||
pub fn copy_entities(&mut self, handles: &[Handle], t: &EntityTransform) -> Vec<Handle> {
|
||||
let clones: Vec<EntityType> = handles
|
||||
.iter()
|
||||
.filter_map(|&h| self.document.get_entity(h).cloned())
|
||||
.collect();
|
||||
let mut new_handles = Vec::with_capacity(clones.len());
|
||||
for mut entity in clones {
|
||||
view::dispatch::apply_transform(&mut entity, t);
|
||||
// A dimension draws from its baked `*D` block; give the copy its own
|
||||
// transformed block so it lands at the copy position rather than
|
||||
// rendering on top of the source. (#161)
|
||||
if let EntityType::Dimension(d) = &entity {
|
||||
let bn = d.base().block_name.clone();
|
||||
if !bn.trim().is_empty() {
|
||||
if let Some(new_bn) = self.clone_transformed_block(&bn, t) {
|
||||
if let EntityType::Dimension(d) = &mut entity {
|
||||
d.base_mut().block_name = new_bn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::reset_clone_subhandles(&mut self.document, &mut entity);
|
||||
entity.common_mut().handle = Handle::NULL;
|
||||
let h = self.document.add_entity(entity).unwrap_or(Handle::NULL);
|
||||
if !h.is_null() {
|
||||
let new_model = match self.document.get_entity(h) {
|
||||
Some(EntityType::Hatch(dxf)) => {
|
||||
let color = convert::tess_util::aci_to_rgba(&dxf.common.color);
|
||||
Self::hatch_model_from_dxf(dxf, color)
|
||||
}
|
||||
Some(EntityType::Solid(s)) => {
|
||||
let color = convert::tess_util::aci_to_rgba(&s.common.color);
|
||||
Some(Self::solid_hatch_model(s, color))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(model) = new_model {
|
||||
self.hatches.insert(h, model);
|
||||
}
|
||||
}
|
||||
new_handles.push(h);
|
||||
}
|
||||
// The copies are new handles (natural memo misses, tessellated fresh)
|
||||
// and reference only already-cached blocks — no block defn changes.
|
||||
self.bump_geometry_no_blocks();
|
||||
new_handles
|
||||
}
|
||||
|
||||
// ── Grip editing ──────────────────────────────────────────────────────
|
||||
|
||||
pub fn apply_grip(&mut self, handle: Handle, grip_id: usize, apply: GripApply) {
|
||||
// For Solid3D / Region / Body, record the old point_of_reference so we
|
||||
// can translate the pre-tessellated MeshModel by the same delta after
|
||||
// the grip is applied (the ACIS data itself is not modified).
|
||||
let old_por: Option<[f64; 3]> = self
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(crate::entities::solid3d::point_of_reference)
|
||||
.map(|p| [p.x, p.y, p.z]);
|
||||
|
||||
if let Some(entity) = self.document.get_entity_mut(handle) {
|
||||
view::dispatch::apply_grip(entity, grip_id, apply);
|
||||
}
|
||||
|
||||
// Translate MeshModel vertices by the same delta the grip applied.
|
||||
if let Some(old) = old_por {
|
||||
let new_por: Option<[f64; 3]> = self
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(crate::entities::solid3d::point_of_reference)
|
||||
.map(|p| [p.x, p.y, p.z]);
|
||||
if let Some(new) = new_por {
|
||||
let dx = (new[0] - old[0]) as f32;
|
||||
let dy = (new[1] - old[1]) as f32;
|
||||
let dz = (new[2] - old[2]) as f32;
|
||||
if let Some(set) = self.meshes.get_mut(&handle) {
|
||||
for lod in &mut set.lods {
|
||||
for v in &mut lod.verts {
|
||||
v[0] += dx;
|
||||
v[1] += dy;
|
||||
v[2] += dz;
|
||||
}
|
||||
}
|
||||
set.world_aabb[0] += dx;
|
||||
set.world_aabb[1] += dy;
|
||||
set.world_aabb[2] += dx;
|
||||
set.world_aabb[3] += dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild GPU hatch/solid model when a boundary vertex or corner moves.
|
||||
match self.document.get_entity(handle) {
|
||||
Some(EntityType::Hatch(dxf)) => {
|
||||
let color = convert::tess_util::aci_to_rgba(&dxf.common.color);
|
||||
if let Some(model) = Self::hatch_model_from_dxf(dxf, color) {
|
||||
self.hatches.insert(handle, model);
|
||||
} else {
|
||||
self.hatches.remove(&handle);
|
||||
}
|
||||
}
|
||||
Some(EntityType::Solid(solid)) => {
|
||||
let color = convert::tess_util::aci_to_rgba(&solid.common.color);
|
||||
self.hatches
|
||||
.insert(handle, Self::solid_hatch_model(solid, color));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// NOTE: no `bump_geometry()` here. The grip-drag caller hides the
|
||||
// edited entity and previews it as an overlay during the drag (so a
|
||||
// move doesn't re-tessellate the whole model), then bumps once on
|
||||
// commit. Any other caller must bump geometry itself.
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user