diff --git a/cadquery/occ_impl/importers/assembly.py b/cadquery/occ_impl/importers/assembly.py index 9bff94922..98dbc4de7 100644 --- a/cadquery/occ_impl/importers/assembly.py +++ b/cadquery/occ_impl/importers/assembly.py @@ -373,14 +373,16 @@ def _process_label(lbl: TDF_Label, parent: AssemblyProtocol): if shape_tool.IsTopLevel(top_level_label) and shape_tool.IsAssembly_s( top_level_label ): - # Set the name of the top-level assembly to match the top-level label - name_attr = TDataStd_Name() - top_level_label.FindAttribute(TDataStd_Name.GetID_s(), name_attr) - - # Manipulation of .objects is needed to maintain consistency - assy.objects.pop(assy.name) - assy.name = str(name_attr.Get().ToExtString()) - assy.objects[assy.name] = assy + # Set the name of the top-level assembly to match the top-level label. An + # assembly without children has no name attribute after an xml/xbf round + # trip, and FindAttribute segfaults on a label that does not have it. + name = _get_name(top_level_label) + + if name: + # Manipulation of .objects is needed to maintain consistency + assy.objects.pop(assy.name) + assy.name = name + assy.objects[assy.name] = assy if cq_color: assy.color = cq_color diff --git a/tests/test_assembly.py b/tests/test_assembly.py index 08d4a5f21..1492a5e12 100644 --- a/tests/test_assembly.py +++ b/tests/test_assembly.py @@ -2603,3 +2603,20 @@ def test_name_geometries(tmpdir): assert len([l for l in lines if "top_face" in l]) == 2 assert len([l for l in lines if "plane_" in l]) == 2 assert len([l for l in lines if "seg_" in l]) == 3 + + +@pytest.mark.parametrize("kind", ["step", "xml", "xbf"]) +def test_assembly_without_children_roundtrip(kind, tmpdir): + """ + An assembly holding a single shape and no children used to segfault when + loaded back from xml or xbf. + """ + + assy = cq.Assembly(box(1, 1, 1)) + + path = os.path.join(tmpdir, f"no_children.{kind}") + assy.export(path) + + loaded = cq.Assembly.load(path) + + assert loaded.toCompound().Volume() == approx(1)