mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Bonsai: uniquify drawing name on rename so SVGs don't overwrite (#5635)
A drawing's SVG filename is derived from its Name (get_default_drawing_path -> drawings/<name>.svg). New drawings are de-duplicated by ensure_unique_drawing_name in core.add_drawing, but the rename path core.update_drawing_name applied the raw name with no uniqueness check. Renaming one drawing to another's name then made both resolve to the same SVG path: update_drawing_name moved one SVG onto the other and pointed both IfcDocumentReference.Location values at the same file, overwriting the drawing and crossing the sheet references. Give ensure_unique_drawing_name an optional `ignore` entity (so a drawing can keep its own name) and call it from update_drawing_name with ignore=drawing, mirroring add_drawing. Default stays None, so existing callers are unchanged. Verified live in headless Blender: renaming a second drawing to a taken name now yields "<name>-X" and a distinct SVG path (paths no longer collide). Core test_drawing.py::TestUpdateDrawingName passes; the tool ensure_unique_drawing_name ignore case is confirmed live. Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -475,6 +475,11 @@ def remove_drawing(
|
||||
def update_drawing_name(
|
||||
ifc: type[tool.Ifc], drawing_tool: type[tool.Drawing], drawing: ifcopenshell.entity_instance, name: str
|
||||
) -> None:
|
||||
# A drawing's Name is used to derive its SVG filename, so two drawings
|
||||
# sharing a name would resolve to the same file and overwrite each other
|
||||
# (and tangle the sheet references). Uniquify the name on rename, just like
|
||||
# new drawings do, ignoring this drawing's own current name.
|
||||
name = drawing_tool.ensure_unique_drawing_name(name, ignore=drawing)
|
||||
if drawing_tool.get_name(drawing) != name:
|
||||
ifc.run("attribute.edit_attributes", product=drawing, attributes={"Name": name})
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ class Drawing:
|
||||
def ensure_annotation_in_drawing_plane(cls, obj, camera=None): pass
|
||||
def ensure_drawings_parent_document(cls): pass
|
||||
def ensure_drawings_parent_group(cls): pass
|
||||
def ensure_unique_drawing_name(cls, name): pass
|
||||
def ensure_unique_drawing_name(cls, name, ignore=None): pass
|
||||
def ensure_unique_identification(cls, identification): pass
|
||||
def export_font_size(cls, obj): pass
|
||||
def export_symbol(cls, obj): pass
|
||||
|
||||
@@ -617,8 +617,14 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
props.is_editing_product = True
|
||||
|
||||
@classmethod
|
||||
def ensure_unique_drawing_name(cls, name: str) -> str:
|
||||
names = [e.Name for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]
|
||||
def ensure_unique_drawing_name(
|
||||
cls, name: str, ignore: Optional[ifcopenshell.entity_instance] = None
|
||||
) -> str:
|
||||
names = [
|
||||
e.Name
|
||||
for e in tool.Ifc.get().by_type("IfcAnnotation")
|
||||
if e.ObjectType == "DRAWING" and e != ignore
|
||||
]
|
||||
while name in names:
|
||||
name += "-X"
|
||||
return name
|
||||
|
||||
@@ -533,6 +533,7 @@ class TestRemoveDrawing:
|
||||
|
||||
class TestUpdateDrawingName:
|
||||
def test_do_not_update_if_name_unchanged(self, ifc, drawing):
|
||||
drawing.ensure_unique_drawing_name("name", ignore="drawing").should_be_called().will_return("name")
|
||||
drawing.get_name("drawing").should_be_called().will_return("name")
|
||||
drawing.set_camera_name("drawing", "name").should_be_called()
|
||||
drawing.get_drawing_group("drawing").should_be_called().will_return("group")
|
||||
@@ -549,6 +550,7 @@ class TestUpdateDrawingName:
|
||||
subject.update_drawing_name(ifc, drawing, drawing="drawing", name="name")
|
||||
|
||||
def test_run(self, ifc, drawing):
|
||||
drawing.ensure_unique_drawing_name("name", ignore="drawing").should_be_called().will_return("name")
|
||||
drawing.get_name("drawing").should_be_called().will_return("oldname")
|
||||
ifc.run("attribute.edit_attributes", product="drawing", attributes={"Name": "name"}).should_be_called()
|
||||
drawing.set_camera_name("drawing", "name").should_be_called()
|
||||
|
||||
@@ -385,10 +385,12 @@ class TestEnsureUniqueDrawingName(NewFile):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR"
|
||||
ifc.createIfcAnnotation(Name="FOOBAR", ObjectType="DRAWING")
|
||||
foobar = ifc.createIfcAnnotation(Name="FOOBAR", ObjectType="DRAWING")
|
||||
assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR-X"
|
||||
ifc.createIfcAnnotation(Name="FOOBAR-X", ObjectType="DRAWING")
|
||||
assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR-X-X"
|
||||
# A drawing may keep its own name (e.g. when re-applying it on rename).
|
||||
assert subject.ensure_unique_drawing_name("FOOBAR", ignore=foobar) == "FOOBAR"
|
||||
|
||||
|
||||
class TestEnsureUniqueIdentification(NewFile):
|
||||
|
||||
Reference in New Issue
Block a user