Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions cadquery/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,26 +281,40 @@ def remove(self, name: str) -> "Assembly":
"""
Remove a part/subassembly from the current assembly.

:param name: Name of the part/subassembly to be removed
:param name: Name of the part/subassembly to be removed. The entire
assembly tree is searched, so the object does not need to be a
direct child.
:return: The modified assembly

*NOTE* This method can cause problems with deeply nested assemblies and does not remove
constraints associated with the removed part/subassembly.
"""

# Make sure the part/subassembly is actually part of the assembly
if name not in self.objects:
raise ValueError(f"No object with name '{name}' found in the assembly")
# Try exact key match first (supports full paths like "sub/part1"),
# then fall back to searching by simple name across the tree
if name in self.objects:
key = name
else:
key = None
for k, v in self.objects.items():
if v.name == name:
key = k
break

if key is None:
raise ValueError(
f"No object with name '{name}' found in the assembly"
)

# Get the part/assembly to be removed
to_remove = self.objects[name]
to_remove = self.objects[key]

# Remove the part/assembly from the parent's children list
if to_remove.parent:
to_remove.parent.children.remove(to_remove)

# Remove the part/assembly from the assembly's object dictionary
del self.objects[name]
del self.objects[key]

# Remove all descendants from the objects dictionary
for descendant_name in to_remove._flatten().keys():
Expand Down
25 changes: 25 additions & 0 deletions tests/test_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -2385,6 +2385,31 @@ def test_remove_without_parent():
assert len(assy.objects) == 1


def test_assembly_remove_nested():
"""
Tests the ability to remove a nested part from an assembly by name,
without knowing the full path (issue #2013).
"""

outer = cq.Assembly(name="outer")
inner = cq.Assembly(name="inner")
inner.add(cq.Workplane("XY").box(1, 1, 1), name="box_a")
inner.add(cq.Workplane("XY").box(2, 2, 2), name="box_b")
outer.add(inner)

# Should be: outer, inner, box_a, box_b
assert len(outer.objects) == 4

# Remove a nested part by simple name
outer.remove("box_a")

assert len(outer.objects) == 3
assert "box_a" not in [v.name for v in outer.objects.values()]

# The other nested part should still be there
assert "box_b" in [v.name for v in outer.objects.values()]


def test_step_color(tmpdir):
"""
Checks color handling for STEP export.
Expand Down