forked from 1b-agent/opencadstudio-upstream
fix(plot): XCLIP also clips block hatches plotted through a viewport
Follow-up to 0bfd5dcc:
- A clipped block hatch kept its unclipped plane-local outline, which the
viewport plot projection prefers, so fills bled past the XCLIP boundary
on layouts. Rebuild it from the clipped outline.
- insert_spatial_filter / filter_handle run for every INSERT in each
render walk; resolve the xdictionary through the entity link or the
document index only, and leave the whole-object scan to set_insert_clip.
Fixes #1508
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fdfe2429e4
commit
51d26eb81f
3 files changed
+45
-13
No files matched your search
@@ -1633,6 +1633,36 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
model.boundary = std::sync::Arc::new(clipped);
|
model.boundary = std::sync::Arc::new(clipped);
|
||||||
}
|
}
|
||||||
|
if !context.clips.is_empty() {
|
||||||
|
// The plane-local outline is a second copy of the boundary
|
||||||
|
// that viewport plotting prefers; rebuild it from the
|
||||||
|
// clipped outline or the XCLIP is lost there. (#1508)
|
||||||
|
model.fill_plane_boundary = model.fill_plane.map(|plane| {
|
||||||
|
let origin = glam::DVec3::from_array(plane.origin);
|
||||||
|
let (x_axis, y_axis) = (
|
||||||
|
glam::DVec3::from_array(plane.x_axis),
|
||||||
|
glam::DVec3::from_array(plane.y_axis),
|
||||||
|
);
|
||||||
|
std::sync::Arc::new(
|
||||||
|
model
|
||||||
|
.boundary
|
||||||
|
.iter()
|
||||||
|
.map(|&[x, y]| {
|
||||||
|
if x.is_nan() || y.is_nan() {
|
||||||
|
return [f32::NAN, f32::NAN];
|
||||||
|
}
|
||||||
|
let world = glam::DVec3::new(
|
||||||
|
model.world_origin[0] + x as f64,
|
||||||
|
model.world_origin[1] + y as f64,
|
||||||
|
origin.z,
|
||||||
|
);
|
||||||
|
let local = world - origin;
|
||||||
|
[local.dot(x_axis) as f32, local.dot(y_axis) as f32]
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
if tint_selected && self.selected.contains(&context.root_handle) {
|
if tint_selected && self.selected.contains(&context.root_handle) {
|
||||||
model.color = [0.15, 0.55, 1.00, model.color[3]];
|
model.color = [0.15, 0.55, 1.00, model.color[3]];
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-10
@@ -46,6 +46,17 @@ fn resolve_extension_dictionary(doc: &CadDocument, owner: Handle) -> Option<Hand
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The INSERT's extension dictionary through its own link or the document's
|
||||||
|
/// index — both O(1). Render walks ask this for every INSERT, so it must not
|
||||||
|
/// fall back to [`resolve_extension_dictionary`]'s scan of every object.
|
||||||
|
fn linked_extension_dictionary(doc: &CadDocument, ins: &Insert) -> Option<Handle> {
|
||||||
|
ins.common
|
||||||
|
.xdictionary_handle
|
||||||
|
.filter(|h| !h.is_null())
|
||||||
|
.or_else(|| doc.extension_dictionary_handle(ins.common.handle))
|
||||||
|
.filter(|h| !h.is_null())
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve the enabled XCLIP spatial filter for `ins`, if any.
|
/// Resolve the enabled XCLIP spatial filter for `ins`, if any.
|
||||||
///
|
///
|
||||||
/// Walks the INSERT's extension dictionary: `xdictionary → ACAD_FILTER
|
/// Walks the INSERT's extension dictionary: `xdictionary → ACAD_FILTER
|
||||||
@@ -56,11 +67,7 @@ pub fn insert_spatial_filter<'a>(
|
|||||||
doc: &'a CadDocument,
|
doc: &'a CadDocument,
|
||||||
ins: &Insert,
|
ins: &Insert,
|
||||||
) -> Option<&'a SpatialFilter> {
|
) -> Option<&'a SpatialFilter> {
|
||||||
let xdict = ins
|
let xdict = linked_extension_dictionary(doc, ins)?;
|
||||||
.common
|
|
||||||
.xdictionary_handle
|
|
||||||
.filter(|h| !h.is_null())
|
|
||||||
.or_else(|| resolve_extension_dictionary(doc, ins.common.handle))?;
|
|
||||||
let acad_filter = dict_entry(doc, xdict, "ACAD_FILTER")?;
|
let acad_filter = dict_entry(doc, xdict, "ACAD_FILTER")?;
|
||||||
let spatial = dict_entry(doc, acad_filter, "SPATIAL")?;
|
let spatial = dict_entry(doc, acad_filter, "SPATIAL")?;
|
||||||
match doc.objects.get(&spatial)? {
|
match doc.objects.get(&spatial)? {
|
||||||
@@ -80,11 +87,7 @@ pub fn filter_handle(doc: &CadDocument, insert: Handle) -> Option<Handle> {
|
|||||||
let Some(codec::EntityType::Insert(ins)) = doc.get_entity(insert) else {
|
let Some(codec::EntityType::Insert(ins)) = doc.get_entity(insert) else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let xdict = ins
|
let xdict = linked_extension_dictionary(doc, ins)?;
|
||||||
.common
|
|
||||||
.xdictionary_handle
|
|
||||||
.filter(|h| !h.is_null())
|
|
||||||
.or_else(|| resolve_extension_dictionary(doc, insert))?;
|
|
||||||
let acad_filter = dict_entry(doc, xdict, "ACAD_FILTER")?;
|
let acad_filter = dict_entry(doc, xdict, "ACAD_FILTER")?;
|
||||||
let spatial = dict_entry(doc, acad_filter, "SPATIAL")?;
|
let spatial = dict_entry(doc, acad_filter, "SPATIAL")?;
|
||||||
matches!(doc.objects.get(&spatial), Some(ObjectType::SpatialFilter(_))).then_some(spatial)
|
matches!(doc.objects.get(&spatial), Some(ObjectType::SpatialFilter(_))).then_some(spatial)
|
||||||
|
|||||||
@@ -85,11 +85,10 @@ fn test_xclip_extension_dictionary_resolution() {
|
|||||||
let ins_handle = scene.document.allocate_handle();
|
let ins_handle = scene.document.allocate_handle();
|
||||||
let mut ins = Insert::new(block_name, Vector3::new(100.0, 50.0, 0.0));
|
let mut ins = Insert::new(block_name, Vector3::new(100.0, 50.0, 0.0));
|
||||||
ins.common.handle = ins_handle;
|
ins.common.handle = ins_handle;
|
||||||
// ins.common.xdictionary_handle left as None/null to test doc.extension_dictionary_handle fallback!
|
|
||||||
ins.common.xdictionary_handle = None;
|
|
||||||
|
|
||||||
// Create DictionaryWithDefault as the extension dictionary, owned by ins_handle:
|
// Create DictionaryWithDefault as the extension dictionary, owned by ins_handle:
|
||||||
let h_xdict = scene.document.allocate_handle();
|
let h_xdict = scene.document.allocate_handle();
|
||||||
|
ins.common.xdictionary_handle = Some(h_xdict);
|
||||||
let mut xdict = DictionaryWithDefault::new();
|
let mut xdict = DictionaryWithDefault::new();
|
||||||
xdict.handle = h_xdict;
|
xdict.handle = h_xdict;
|
||||||
xdict.owner = ins_handle;
|
xdict.owner = ins_handle;
|
||||||
@@ -114,7 +113,7 @@ fn test_xclip_extension_dictionary_resolution() {
|
|||||||
scene.document.objects.insert(h_spatial, ObjectType::SpatialFilter(sf));
|
scene.document.objects.insert(h_spatial, ObjectType::SpatialFilter(sf));
|
||||||
|
|
||||||
let resolved = insert_spatial_filter(&scene.document, &ins);
|
let resolved = insert_spatial_filter(&scene.document, &ins);
|
||||||
assert!(resolved.is_some(), "SpatialFilter should be resolved via extension dictionary fallback and DictionaryWithDefault");
|
assert!(resolved.is_some(), "SpatialFilter should be resolved through a DictionaryWithDefault with loosely written keys");
|
||||||
let sf_ref = resolved.unwrap();
|
let sf_ref = resolved.unwrap();
|
||||||
assert_eq!(sf_ref.handle, h_spatial);
|
assert_eq!(sf_ref.handle, h_spatial);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user