forked from 1b-agent/opencadstudio-upstream
fix(blocks): one base point convention for creation, redefine, insert, explode, XCLIP and REFEDIT
world = insert_point + Rot * Scale * (P - base_point). Attribute placement, EXPLODE, XCLIP storage, REFEDIT's working copy, the UCS reframe of attributes and the dynamic-block visibility grip ignored the base point and were offset by Rot*Scale*base. They now share insert_transform_with_base. Creation stores a ZERO base and redefine resets it; a no-op redefine moves nothing; the Block Definition dialog prefills the base from the definition. XCLIP filters stored by older builds keep their old displacement.
This commit is contained in:
1 parent
a456367b26
commit
5dfa41c032
17 files changed
+1447
-19
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -947,6 +947,14 @@ impl Scene {
|
||||
&mut self,
|
||||
options: CreateBlockOptions,
|
||||
) -> Result<Handle, String> {
|
||||
// Base-point contract: the contents are stored *relative to the picked
|
||||
// base* because `world_to_block` already subtracts it, so the
|
||||
// definition's own `Block.base_point` stays ZERO and the picked base
|
||||
// becomes the block origin (inserting at a point puts that point where
|
||||
// the base was). Callers must not also store the picked base in the
|
||||
// definition — that would subtract it twice. Imported definitions,
|
||||
// whose geometry arrives relative to their own base point, keep that
|
||||
// base point and are never rewritten here.
|
||||
let name = options.name.trim();
|
||||
if name.is_empty() {
|
||||
return Err("Block name cannot be empty.".into());
|
||||
@@ -988,6 +996,11 @@ impl Scene {
|
||||
// Update existing Block entity fields
|
||||
if let Some(EntityType::Block(b)) = self.document.get_entity_mut(existing_blk_h) {
|
||||
b.description = options.description.clone();
|
||||
// The new contents are baked relative to the picked base (see
|
||||
// the contract above), so the definition's base point has to
|
||||
// go back to ZERO. Leaving an imported definition's non-zero
|
||||
// base point here would subtract the picked base twice.
|
||||
b.base_point = codec::types::Vector3::ZERO;
|
||||
}
|
||||
if options.annotative {
|
||||
let annotative_vals = vec![codec::xdata::XDataValue::Integer16(1)];
|
||||
|
||||
@@ -26,6 +26,8 @@ pub enum BlockScalePolicy {
|
||||
|
||||
// Topic submodules split out of this root (each contributes `impl Scene`
|
||||
// blocks and/or free functions). Pure text-move from the original mod.rs.
|
||||
#[cfg(test)]
|
||||
mod block_base_point_tests;
|
||||
mod boundary;
|
||||
mod camera_ops;
|
||||
pub(crate) mod centerline;
|
||||
|
||||
+7
-2
@@ -506,7 +506,11 @@ impl Scene {
|
||||
|
||||
// ATTRIBs live inline on each INSERT in that reference's owner space.
|
||||
// Conjugate the block-local reframe through the INSERT transform:
|
||||
// owner' = M · local_from_old · M⁻¹ · owner.
|
||||
// owner' = M · local_from_old · M⁻¹ · owner. M is the block-local →
|
||||
// world map the render path uses (`insert_transform`), because that is
|
||||
// the map the attribute positions on the INSERT were placed through;
|
||||
// the codec's base-ignoring transform would conjugate them through a
|
||||
// displaced frame whenever the definition has a non-zero base point.
|
||||
let references: Vec<(Handle, codec::types::Transform)> = self
|
||||
.document
|
||||
.entities()
|
||||
@@ -516,7 +520,8 @@ impl Scene {
|
||||
&& !reference.attributes.is_empty()
|
||||
&& !owned.contains(&reference.common.handle) =>
|
||||
{
|
||||
let insertion = reference.get_transform();
|
||||
let insertion =
|
||||
crate::scene::render_graph::insert_transform(&self.document, reference);
|
||||
let inverse = inverse_affine(&insertion)?;
|
||||
Some((
|
||||
reference.common.handle,
|
||||
|
||||
@@ -250,7 +250,12 @@ pub fn set_insert_clip(
|
||||
let Some(codec::EntityType::Insert(ins)) = doc.get_entity(insert) else {
|
||||
return false;
|
||||
};
|
||||
let t = ins.get_transform().matrix.m;
|
||||
// The stored inverse is the inverse of the transform every consumer
|
||||
// composes the boundary back with — the base-point-aware instance
|
||||
// transform the render path uses. Storing the codec's base-ignoring
|
||||
// inverse would displace the ring by exactly -base on the next render.
|
||||
let forward = crate::scene::render_graph::insert_transform(doc, ins);
|
||||
let t = forward.matrix.m;
|
||||
let forward = glam::DMat4::from_cols_array_2d(&[
|
||||
[t[0][0], t[1][0], t[2][0], t[3][0]],
|
||||
[t[0][1], t[1][1], t[2][1], t[3][1]],
|
||||
|
||||
@@ -688,7 +688,14 @@ pub(crate) fn clear_dim_picture_placement(entity: &mut EntityType) {
|
||||
}
|
||||
|
||||
pub fn insert_transform(document: &CadDocument, insert: &Insert) -> Transform {
|
||||
let base = block_base_point(document, &insert.block_name);
|
||||
insert_transform_with_base(insert, block_base_point(document, &insert.block_name))
|
||||
}
|
||||
|
||||
/// [`insert_transform`] for a caller that already resolved the block's base
|
||||
/// point (the `-INSERT` command builds the INSERT before the drawing is in
|
||||
/// scope), so the base-point convention lives in exactly one place:
|
||||
/// world = insert_point + Rot(rotation) * Scale * (P - base).
|
||||
pub fn insert_transform_with_base(insert: &Insert, base: Vector3) -> Transform {
|
||||
Transform::from_translation(Vector3::new(-base.x, -base.y, -base.z))
|
||||
.then(&insert.get_transform())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user