forked from 1b-agent/opencadstudio-upstream
feat(python): block contents (create_entity(..., block=))
BlockEntityAdd adds a validated entity to a block definition (owner set by the host, same validation as a model-space entity, self-nesting inserts refused), reusing the scene's block-editor routing. Deleting a block member now also drops its handle from the block record. Real-IPC audit with an attribute definition inside a block, DWG/DXF persistence and undo; IPC and Python unit tests; docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d20d280fe
commit
b1edc9f578
8 files changed
+251
-7
No files matched your search
@@ -209,9 +209,20 @@ block, a viewport, a non-finite base point, renaming or deleting layout,
|
||||
anonymous or externally referenced blocks, and a case-only rename. Deleting also
|
||||
removes the definition's contents. Records list user blocks only (`handle`,
|
||||
`name`, `description`, `explodable`, `scale_uniformly`, `base_point`,
|
||||
`insert_count`, `entities` as `{handle, kind}`). Editing a definition's contents
|
||||
in place and creating attribute definitions inside a block from a script are not
|
||||
available yet. A DXF save drops the block description (cadcodec); DWG keeps it.
|
||||
`insert_count`, `entities` as `{handle, kind}`). A DXF save drops the block
|
||||
description (cadcodec); DWG keeps it.
|
||||
|
||||
**Block contents.** Add to a definition with `create_entity(kind, block="Part",
|
||||
...)`. It is validated exactly like a model-space entity and the host sets the
|
||||
owner (so attribute definitions work: `create_entity("AttributeDefinition",
|
||||
block="Part", tag=..., prompt=..., default_value=..., insertion_point=...,
|
||||
height=...)`). Members are read, edited (`entity.end = (8, 0, 0)` inside
|
||||
`doc.transaction(...)`) and deleted (`doc.delete_entity`) with the ordinary entity
|
||||
API, using the handles in `blocks["Part"]["entities"]`. Refused: an unknown,
|
||||
layout, anonymous or external block, an invalid entity, a viewport, raster image,
|
||||
block marker or nested attribute, an entity on a locked layer, and an insert that
|
||||
would nest a block inside itself directly or through other blocks. Adding while the
|
||||
block editor is open is refused. Each add or delete is one undo step.
|
||||
|
||||
### Historical command-replay experiment (not in the default build)
|
||||
|
||||
|
||||
@@ -473,11 +473,17 @@ class _Document:
|
||||
def transaction(self, label):
|
||||
return _Transaction(self, label)
|
||||
|
||||
def create_entity(self, kind, **properties):
|
||||
"""Create one mapped entity and return its live document descriptor."""
|
||||
def create_entity(self, kind, block=None, **properties):
|
||||
"""Create one mapped entity and return its live document descriptor.
|
||||
With `block="Name"` the entity is added to that block definition
|
||||
instead of the drawing (its owner is set by the host)."""
|
||||
if "kind" in properties or "handle" in properties:
|
||||
raise ValueError("kind and handle are managed by create_entity")
|
||||
handle = ocs.add(dict(kind=kind, **_coerce_points(kind, properties)))
|
||||
entity = dict(kind=kind, **_coerce_points(kind, properties))
|
||||
if block is not None:
|
||||
handle = ocs.add_to_block(str(block), entity)
|
||||
else:
|
||||
handle = ocs.add(entity)
|
||||
return self.entities[handle]
|
||||
|
||||
def delete_entity(self, entity):
|
||||
|
||||
@@ -4263,6 +4263,31 @@ mod ocs {
|
||||
.ok_or_else(|| vm.new_runtime_error("ocs: not running inside a PY_ command".to_owned()))?
|
||||
}
|
||||
|
||||
/// Add a new entity, built from `entity` like `add`, to the block definition
|
||||
/// `block`. Returns the new entity's handle.
|
||||
#[cfg(feature = "experimental-host-model")]
|
||||
#[pyfunction]
|
||||
fn add_to_block(block: String, entity: PyObjectRef, vm: &VirtualMachine) -> PyResult<u64> {
|
||||
let dict = entity.try_into_value::<rustpython_vm::builtins::PyDictRef>(vm)?;
|
||||
let mut built = dict_to_entity(&dict, vm)?;
|
||||
let result = host_ctx::with_host(|host| -> PyResult<Result<Handle, String>> {
|
||||
let owner = host
|
||||
.document()
|
||||
.block_records
|
||||
.get(block.trim())
|
||||
.map(|record| record.handle)
|
||||
.ok_or_else(|| vm.new_runtime_error(format!("ocs.add_to_block: block {block:?} does not exist")))?;
|
||||
built.common_mut().owner_handle = owner;
|
||||
ocs_plugin_api::entity_coverage::validate_new_canvas_entity(&built)
|
||||
.map_err(|error| vm.new_value_error(error))?;
|
||||
Ok(host.table_operation(ocs_plugin_api::host::TableOperation::BlockEntityAdd { block, entity: built }))
|
||||
})
|
||||
.ok_or_else(|| vm.new_runtime_error("ocs: not running inside a PY_ command".to_owned()))??;
|
||||
result
|
||||
.map(|handle| handle.value())
|
||||
.map_err(|error| vm.new_runtime_error(format!("ocs.add_to_block: {error}")))
|
||||
}
|
||||
|
||||
/// Update `handle` by merging `entity`'s keys onto its *current* value —
|
||||
/// a key `entity` doesn't mention is left exactly as it was, not reset to
|
||||
/// a type default (Felix's Phase 1 review: reconstructing from a partial
|
||||
|
||||
@@ -41,6 +41,7 @@ class DocumentModelTests(unittest.TestCase):
|
||||
style_operation=lambda kind, op, name, options: self.calls.append((kind, op, name, options)) or 9,
|
||||
block_records=lambda: [{"name": n, "entities": []} for n in ("Widget", "Gadget")],
|
||||
block_operation=lambda op, name, options: self.calls.append(("block", op, name, options)) or 9,
|
||||
add_to_block=lambda block, entity: self.calls.append(("add_to_block", block, entity)) or 1,
|
||||
)
|
||||
namespace = {"ocs": self.ocs}
|
||||
exec(MODEL.read_text(), namespace)
|
||||
@@ -96,6 +97,14 @@ class DocumentModelTests(unittest.TestCase):
|
||||
dim.set_current("Standard")
|
||||
self.assertEqual(self.calls[-1], ("dim", "set_current", "Standard", None))
|
||||
|
||||
def test_create_entity_in_block(self):
|
||||
self.doc.create_entity("Line", block="Widget", start={"x": 0, "y": 0, "z": 0}, end={"x": 1, "y": 0, "z": 0})
|
||||
self.assertEqual(self.calls[-1][:2], ("add_to_block", "Widget"))
|
||||
self.assertEqual(self.calls[-1][2]["kind"], "Line")
|
||||
self.assertNotIn("block", self.calls[-1][2])
|
||||
self.doc.create_entity("Line", start={"x": 0, "y": 0, "z": 0}, end={"x": 1, "y": 0, "z": 0})
|
||||
self.assertEqual(self.calls[-1][0], "add")
|
||||
|
||||
def test_blocks(self):
|
||||
blocks = self.doc.blocks
|
||||
self.assertIn("WIDGET", blocks)
|
||||
|
||||
Reference in new issue
Block a user