diff --git a/cadquery/occ_impl/importers/__init__.py b/cadquery/occ_impl/importers/__init__.py index f3dacfcd2..51eeb26e3 100644 --- a/cadquery/occ_impl/importers/__init__.py +++ b/cadquery/occ_impl/importers/__init__.py @@ -83,15 +83,9 @@ def importBin(fileName: str) -> "cq.Workplane": return cq.Workplane("XY").newObject([shape]) -# Loads a STEP file into a CQ.Workplane object -def importStep(fileName: str, unit: UnitLiterals = "MM") -> "cq.Workplane": +def _importStep(fileName: str, unit: UnitLiterals = "MM") -> list["Shape"]: """ - Accepts a file name and loads the STEP file into a cadquery Workplane - - :param fileName: The path and name of the STEP file to be imported - :param unit: Sets the target OpenCASCADE unit - OCCT scales from the file's - declared unit to this unit. Default "MM". - :type unit: UnitLiterals + Private helper for implementing different STEP importers. """ # Set the target cascade unit - OCCT scales from the file's declared unit to this unit @@ -105,16 +99,23 @@ def importStep(fileName: str, unit: UnitLiterals = "MM") -> "cq.Workplane": for i in range(reader.NbRootsForTransfer()): reader.TransferRoot(i + 1) - occ_shapes = [] - for i in range(reader.NbShapes()): - occ_shapes.append(reader.Shape(i + 1)) + rv = [Shape.cast(reader.Shape(i + 1)) for i in range(reader.NbShapes())] + + return rv + - # Make sure that we extract all the solids - solids = [] - for shape in occ_shapes: - solids.append(Shape.cast(shape)) +# Loads a STEP file into a CQ.Workplane object +def importStep(fileName: str, unit: UnitLiterals = "MM") -> "cq.Workplane": + """ + Accepts a file name and loads the STEP file into a cadquery Workplane + + :param fileName: The path and name of the STEP file to be imported + :param unit: Sets the target OpenCASCADE unit - OCCT scales from the file's + declared unit to this unit. Default "MM". + :type unit: UnitLiterals + """ - return cq.Workplane("XY").newObject(solids) + return cq.Workplane("XY").newObject(_importStep(fileName, unit)) def importDXF( diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 86af4afe9..1e1d62139 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -605,6 +605,26 @@ def importBin(cls, f: str | BytesIO) -> Shape: return cls.cast(s) + @classmethod + def importStep(cls, fileName: str, unit: UnitLiterals = "MM") -> Shape: + """ + Import shape from a STEP file. + + :param fileName: Path to the STEP file to import. + :param unit: Sets the target OpenCASCADE unit - OCCT scales from the file's + declared unit to this unit. Default "MM". + :type unit: UnitLiterals + """ + + from .importers import _importStep + + shapes = _importStep(fileName, unit) + + if not shapes: + raise ValueError(f"No shape found in {fileName}") + + return shapes[0] if len(shapes) == 1 else Compound.makeCompound(shapes) + def geomType(self) -> Geoms: """ Gets the underlying geometry type. diff --git a/tests/test_shapes.py b/tests/test_shapes.py index 3309093d6..f0509bb9d 100644 --- a/tests/test_shapes.py +++ b/tests/test_shapes.py @@ -259,6 +259,63 @@ def test_bin_import_export(): Shape.importBin(BytesIO()) +def test_step_import_export(tmp_path): + + b = box(1, 1, 1) + + fileName = str(tmp_path / "box.step") + + b.exportStep(fileName) + + r = Shape.importStep(fileName) + + assert r.isValid() + assert r.ShapeType() == "Solid" + assert r.Volume() == approx(1) + + with raises(Exception): + Shape.importStep(str(tmp_path / "does_not_exist.step")) + + +def test_step_import_no_shape(tmp_path): + + # a syntactically valid STEP file that has no shape data in it + fileName = str(tmp_path / "no_shape.step") + + with open(fileName, "w") as f: + f.write( + "ISO-10303-21;\n" + "HEADER;\n" + "FILE_DESCRIPTION((''),'2;1');\n" + "FILE_NAME('empty','2024-01-01T00:00:00',(''),(''),'','','');\n" + "FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 3 1 1 }'));\n" + "ENDSEC;\n" + "DATA;\n" + "ENDSEC;\n" + "END-ISO-10303-21;\n" + ) + + with raises(ValueError): + Shape.importStep(fileName) + + +def test_step_import_multiple_roots(tmp_path): + + b1 = box(1, 1, 1) + b2 = box(1, 1, 1).moved(3, 0, 0) + + fileName = str(tmp_path / "boxes.step") + + compound(b1, b2).exportStep(fileName) + + r = Shape.importStep(fileName) + + assert r.isValid() + assert r.ShapeType() == "Compound" + assert r.Volume() == approx(2) + assert len(list(r)) == 2 + + def test_sample(): e = ellipse(10, 1)