forked from 1b-agent/opencadstudio-upstream
fix(explode): use codec block explosion for attributes and arrays
Delegate INSERT and MINSERT explosion to the document-aware codec implementation, removing duplicate attribute and base-point handling. Update workspace, kernel and graph pins together and cover rotated arrays with a nonzero base.
This commit is contained in:
1 parent
9c08e8a3ed
commit
8d287decd8
7 files changed
+75
-77
No files matched your search
Generated
+4
-4
@@ -4361,7 +4361,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadcodec"
|
name = "opencadcodec"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=ddf0fd2#ddf0fd2d7541bebce137c1bf960db54fcd12bcd2"
|
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=8c4b9a3#8c4b9a3783cf68a1cbb383eaa61cac043066cf22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
@@ -4384,7 +4384,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadgraph"
|
name = "opencadgraph"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=45c9649#45c96493fb55d8825561bca9e9d7e5e700dcd3fa"
|
source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=6a2babc#6a2babc82532143500d66a92ed9fbc14d14061fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"opencadkernel",
|
"opencadkernel",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4393,7 +4393,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadkernel"
|
name = "opencadkernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=b80079a#b80079a1e61b3f3519dc759e6553761e24787f67"
|
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=ee970e4#ee970e41decd4f96548f472e5078d8a4335f73ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
"opencadcodec",
|
"opencadcodec",
|
||||||
@@ -4404,7 +4404,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadkernel-constraints"
|
name = "opencadkernel-constraints"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=b80079a#b80079a1e61b3f3519dc759e6553761e24787f67"
|
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=ee970e4#ee970e41decd4f96548f472e5078d8a4335f73ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
]
|
]
|
||||||
|
|||||||
+4
-4
@@ -37,10 +37,10 @@ rfd = "0.17"
|
|||||||
clap = { version = "4", features = ["derive", "string"] }
|
clap = { version = "4", features = ["derive", "string"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ddf0fd2", features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8c4b9a3", features = ["serde"] }
|
||||||
kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "b80079a", features = ["acis", "offset"] }
|
kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "ee970e4", features = ["acis", "offset"] }
|
||||||
kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "b80079a" }
|
kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "ee970e4" }
|
||||||
graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "45c9649" }
|
graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "6a2babc" }
|
||||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||||
# Shared SAVE serialization dispatch + web-worker wire format: one
|
# Shared SAVE serialization dispatch + web-worker wire format: one
|
||||||
# implementation for the inline (fallback/native) path and the worker path.
|
# implementation for the inline (fallback/native) path and the worker path.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ serde_json = { version = "1", optional = true }
|
|||||||
# Pulled in only by the `host` feature, which adds the `opencadcodec`-typed
|
# Pulled in only by the `host` feature, which adds the `opencadcodec`-typed
|
||||||
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
||||||
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ddf0fd2", optional = true, features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8c4b9a3", optional = true, features = ["serde"] }
|
||||||
|
|
||||||
# Runtime IPC and serialization (host feature only).
|
# Runtime IPC and serialization (host feature only).
|
||||||
interprocess = { version = "2", optional = true }
|
interprocess = { version = "2", optional = true }
|
||||||
@@ -39,7 +39,7 @@ serde_json = "1"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
cargo-lock = "11"
|
cargo-lock = "11"
|
||||||
# opencadcodec is scanned at build time to generate the embedded type registry.
|
# opencadcodec is scanned at build time to generate the embedded type registry.
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ddf0fd2", features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8c4b9a3", features = ["serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
@@ -10,6 +10,6 @@ publish = false
|
|||||||
# exactly one implementation. No UI/app dependencies — only the codec + serde.
|
# exactly one implementation. No UI/app dependencies — only the codec + serde.
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ddf0fd2", features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8c4b9a3", features = ["serde"] }
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
@@ -8,7 +8,7 @@ publish = false
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ddf0fd2", features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8c4b9a3", features = ["serde"] }
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ use codec::entities::{
|
|||||||
};
|
};
|
||||||
use codec::entities::{Polyline, Polyline2D};
|
use codec::entities::{Polyline, Polyline2D};
|
||||||
use codec::tables::BlockRecord;
|
use codec::tables::BlockRecord;
|
||||||
use codec::types::{Transform, Vector3};
|
use codec::types::Vector3;
|
||||||
use codec::{CadDocument, Entity, EntityType, Handle};
|
use codec::{CadDocument, EntityType, Handle};
|
||||||
|
|
||||||
use crate::command::{CadCommand, CmdResult, WorkingPlane};
|
use crate::command::{CadCommand, CmdResult, WorkingPlane};
|
||||||
use crate::entities::curve::lwpolyline_world_xy;
|
use crate::entities::curve::lwpolyline_world_xy;
|
||||||
@@ -98,68 +98,11 @@ pub fn explode_entity(entity: &EntityType, document: &CadDocument) -> Vec<Entity
|
|||||||
{
|
{
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
EntityType::Insert(ins) => {
|
EntityType::Insert(ins) => ins
|
||||||
let explodable = document
|
.explode_from_document(document)
|
||||||
.block_records
|
|
||||||
.get(&ins.block_name)
|
|
||||||
.map_or(true, |br| br.explodable);
|
|
||||||
if !explodable {
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
// The codec applies only `Insert::get_transform`, which does
|
|
||||||
// not know the block's base point, so shift the definition's
|
|
||||||
// contents by -base first: get_transform(P - base) is exactly
|
|
||||||
// world = insert_point + Rot*Scale*(P - base), the placement
|
|
||||||
// the render path uses. MINSERT cells and nested references
|
|
||||||
// get the same treatment because the shift happens on the
|
|
||||||
// contents, not on the insert.
|
|
||||||
let base = crate::scene::render_graph::block_base_point(document, &ins.block_name);
|
|
||||||
let to_block = Transform::from_translation(Vector3::new(-base.x, -base.y, -base.z));
|
|
||||||
let mut attdefs: Vec<EntityType> = Vec::new();
|
|
||||||
let entities: Vec<EntityType> = document
|
|
||||||
.entities_in_block(&ins.block_name)
|
|
||||||
.map(|entity| {
|
|
||||||
let mut shifted = entity.clone();
|
|
||||||
Entity::apply_transform(shifted.as_entity_mut(), &to_block);
|
|
||||||
if let EntityType::AttributeDefinition(_) = &shifted {
|
|
||||||
attdefs.push(shifted.clone());
|
|
||||||
}
|
|
||||||
shifted
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let mut pieces: Vec<EntityType> = ins
|
|
||||||
.explode(&entities)
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(normalize_insert_entity)
|
.map(normalize_insert_entity)
|
||||||
.collect();
|
.collect(),
|
||||||
|
|
||||||
// Exploding an INSERT drops attribute values (ATTRIBs)
|
|
||||||
// and the attribute definitions (ATTDEF tags) reappear as ATTDEF entities.
|
|
||||||
// The codec's `ins.explode` skips `AttributeDefinition` entities, so we
|
|
||||||
// transform and append them here.
|
|
||||||
if !attdefs.is_empty() {
|
|
||||||
let insert_xform = ins.get_transform();
|
|
||||||
for ad_ent in &attdefs {
|
|
||||||
let mut exploded_ad = ad_ent.clone();
|
|
||||||
Entity::apply_transform(exploded_ad.as_entity_mut(), &insert_xform);
|
|
||||||
// Resolve common properties (layer "0", ByBlock color / line weight)
|
|
||||||
let common = exploded_ad.common_mut();
|
|
||||||
if common.layer == "0" {
|
|
||||||
common.layer = ins.common.layer.clone();
|
|
||||||
}
|
|
||||||
if common.color == codec::types::Color::ByBlock {
|
|
||||||
common.color = ins.common.color;
|
|
||||||
}
|
|
||||||
if common.line_weight == codec::types::LineWeight::ByBlock {
|
|
||||||
common.line_weight = ins.common.line_weight;
|
|
||||||
}
|
|
||||||
pieces.push(exploded_ad);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pieces
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EntityType::MLine(ml) => explode_mline(ml),
|
EntityType::MLine(ml) => explode_mline(ml),
|
||||||
EntityType::Dimension(dim) => explode_dimension(dim, document),
|
EntityType::Dimension(dim) => explode_dimension(dim, document),
|
||||||
EntityType::MText(t) => crate::entities::mtext::explode_mtext(t, document),
|
EntityType::MText(t) => crate::entities::mtext::explode_mtext(t, document),
|
||||||
|
|||||||
@@ -671,9 +671,8 @@ fn locked_block_definition_dialog_prefills_the_existing_definition_base_point()
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn locked_explode_places_geometry_at_convention_positions() {
|
fn locked_explode_places_geometry_at_convention_positions() {
|
||||||
// base-point: `explode_entity` shifts the definition's contents by -base before
|
// The document-aware codec explosion uses the definition's base point,
|
||||||
// handing them to the codec's explode, so the pieces land where the render
|
// so the pieces land where the render path puts them.
|
||||||
// path puts them (see the module header for the derivation).
|
|
||||||
let mut scene = scene_with_bp_test();
|
let mut scene = scene_with_bp_test();
|
||||||
let insert = placed_instance(&mut scene);
|
let insert = placed_instance(&mut scene);
|
||||||
let pieces = crate::modules::draw::modify::explode::explode_entity(
|
let pieces = crate::modules::draw::modify::explode::explode_entity(
|
||||||
@@ -739,6 +738,62 @@ fn locked_explode_places_geometry_at_convention_positions() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn locked_minsert_explode_restores_attdefs_in_each_rotated_cell() {
|
||||||
|
let scene = scene_with_bp_test();
|
||||||
|
let mut insert = Insert::new(BP_TEST, v(100.0, 100.0, 0.0))
|
||||||
|
.with_uniform_scale(2.0)
|
||||||
|
.with_rotation(std::f64::consts::FRAC_PI_2)
|
||||||
|
.with_array(2, 2, 10.0, 20.0);
|
||||||
|
insert
|
||||||
|
.attributes
|
||||||
|
.push(AttributeEntity::new("TAG1".into(), "Instance value".into()));
|
||||||
|
let pieces = crate::modules::draw::modify::explode::explode_entity(
|
||||||
|
&EntityType::Insert(insert),
|
||||||
|
&scene.document,
|
||||||
|
);
|
||||||
|
let definitions: Vec<_> = pieces
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entity| match entity {
|
||||||
|
EntityType::AttributeDefinition(definition) => Some(definition),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert_eq!(definitions.len(), 4);
|
||||||
|
for (definition, expected) in definitions.iter().zip([
|
||||||
|
[94.0, 104.0, 0.0],
|
||||||
|
[94.0, 114.0, 0.0],
|
||||||
|
[74.0, 104.0, 0.0],
|
||||||
|
[74.0, 114.0, 0.0],
|
||||||
|
]) {
|
||||||
|
assert_close(definition.insertion_point, expected, 1e-6, "array ATTDEF");
|
||||||
|
assert_eq!(definition.tag, "TAG1");
|
||||||
|
assert_eq!(definition.default_value, "");
|
||||||
|
}
|
||||||
|
let lines: Vec<_> = pieces
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entity| match entity {
|
||||||
|
EntityType::Line(line) => Some(line),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert_eq!(lines.len(), 4);
|
||||||
|
for (line, expected) in lines.iter().zip([
|
||||||
|
[100.0, 100.0, 0.0],
|
||||||
|
[100.0, 110.0, 0.0],
|
||||||
|
[80.0, 100.0, 0.0],
|
||||||
|
[80.0, 110.0, 0.0],
|
||||||
|
]) {
|
||||||
|
assert_close(line.start, expected, 1e-6, "array LINE");
|
||||||
|
}
|
||||||
|
assert_close(
|
||||||
|
block_attdefs(&scene.document, BP_TEST)[0].insertion_point,
|
||||||
|
[12.0, 8.0, 0.0],
|
||||||
|
1e-6,
|
||||||
|
"source ATTDEF stays in definition coordinates",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn locked_xclip_boundary_round_trips_through_the_base_point_convention() {
|
fn locked_xclip_boundary_round_trips_through_the_base_point_convention() {
|
||||||
// base-point follow-up fix: the XCLIP ring is stored as WCS points plus the
|
// base-point follow-up fix: the XCLIP ring is stored as WCS points plus the
|
||||||
|
|||||||
Reference in new issue
Block a user