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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
build/
__pycache__
*.pyc
doc/_build/*
dist/*
Expand Down
3 changes: 2 additions & 1 deletion cadquery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from .occ_impl import exporters
from .occ_impl import importers
from .occ_impl.types import UnitLiterals

# these items are the common implementation

Expand Down Expand Up @@ -75,6 +76,6 @@
"DirectionMinMaxSelector",
"StringSyntaxSelector",
"Selector",
"plugins",
"Sketch",
"UnitLiterals",
]
16 changes: 12 additions & 4 deletions cadquery/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
)
from .occ_impl.importers.assembly import importStep as _importStep, importXbf, importXml

from .occ_impl.types import UnitLiterals

from .selectors import _expression_grammar as _selector_grammar
from .utils import deprecate, BiDict, instance_of

Expand Down Expand Up @@ -618,18 +620,24 @@ def export(
return self

@classmethod
def importStep(cls, path: str) -> Self:
def importStep(cls, path: str, unit: UnitLiterals = "MM") -> Self:
"""
Reads an assembly from a STEP file.

:param path: Path and filename for reading.
:param unit: The unit of measurement for the STEP file. Default "MM".
:return: An Assembly object.
"""

return cls.load(path, importType="STEP")
return cls.load(path, importType="STEP", unit=unit)

@classmethod
def load(cls, path: str, importType: Optional[ImportLiterals] = None,) -> Self:
def load(
cls,
path: str,
importType: Optional[ImportLiterals] = None,
unit: UnitLiterals = "MM",
) -> Self:
"""
Load step, xbf or xml.
"""
Expand All @@ -644,7 +652,7 @@ def load(cls, path: str, importType: Optional[ImportLiterals] = None,) -> Self:
assy = cls()

if importType == "STEP":
_importStep(assy, path)
_importStep(assy, path, unit)
elif importType == "XML":
importXml(assy, path)
elif importType == "XBF":
Expand Down
25 changes: 25 additions & 0 deletions cadquery/occ_impl/exporters/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from ..assembly import AssemblyProtocol, toCAF, toVTK, toFusedCAF
from ..geom import Location
from ..shapes import Shape, Compound
from ..types import UnitLiterals


class ExportModes:
Expand All @@ -62,6 +63,8 @@ def exportAssembly(
assy: AssemblyProtocol,
path: str,
mode: STEPExportModeLiterals = "default",
unit: UnitLiterals = "MM",
outputUnit: Optional[UnitLiterals] = None,
**kwargs,
) -> bool:
"""
Expand All @@ -73,6 +76,12 @@ def exportAssembly(
:param path: Path and filename for writing
:param mode: STEP export mode. The options are "default", and "fused" (a single fused compound).
It is possible that fused mode may exhibit low performance.
:param unit: The internal unit of the model's geometry values. Default "MM".
:type unit: UnitLiterals
:param outputUnit: The unit to use in the STEP file header. If None, defaults to the value of ``unit``.
Use this when you want the output file to declare a different unit than the model's internal unit,
for example to export a MM model as a STEP file declaring meters.
:type outputUnit: UnitLiterals or None
:param fuzzy_tol: OCCT fuse operation tolerance setting used only for fused assembly export.
:type fuzzy_tol: float
:param glue: Enable gluing mode for improved performance during fused assembly export.
Expand Down Expand Up @@ -113,6 +122,10 @@ def exportAssembly(
Interface_Static.SetIVal_s("write.surfacecurve.mode", pcurves)
Interface_Static.SetIVal_s("write.precision.mode", precision_mode)
Interface_Static.SetIVal_s("write.stepcaf.subshapes.name", 1)
Interface_Static.SetCVal_s("xstep.cascade.unit", unit.upper())
Interface_Static.SetCVal_s(
"write.step.unit", outputUnit if outputUnit is not None else unit.upper()
)
writer.Transfer(doc, STEPControl_StepModelType.STEPControl_AsIs)

if name_geometries:
Expand Down Expand Up @@ -157,6 +170,8 @@ def exportStepMeta(
path: str,
write_pcurves: bool = True,
precision_mode: int = 0,
unit: UnitLiterals = "MM",
outputUnit: Optional[UnitLiterals] = None,
) -> bool:
"""
Export an assembly to a STEP file with faces tagged with names and colors. This is done as a
Expand All @@ -172,6 +187,12 @@ def exportStepMeta(
If False, writes STEP file without pcurves. This decreases the size of the resulting STEP file.
:param precision_mode: Controls the uncertainty value for STEP entities. Specify -1, 0, or 1. Default 0.
See OCCT documentation.
:param unit: The internal unit of the model's geometry values. Default "MM".
:type unit: UnitLiterals
:param outputUnit: The unit to use in the STEP file header. If None, defaults to the value of ``unit``.
Use this when you want the output file to declare a different unit than the model's internal unit,
for example to export a MM model as a STEP file declaring meters.
:type outputUnit: UnitLiterals or None
"""

pcurves = 1
Expand Down Expand Up @@ -311,6 +332,10 @@ def _process_assembly(
writer.SetNameMode(True)
Interface_Static.SetIVal_s("write.surfacecurve.mode", pcurves)
Interface_Static.SetIVal_s("write.precision.mode", precision_mode)
Interface_Static.SetCVal_s("xstep.cascade.unit", unit.upper())
Interface_Static.SetCVal_s(
"write.step.unit", outputUnit if outputUnit is not None else unit.upper()
)
writer.Transfer(doc, STEPControl_StepModelType.STEPControl_AsIs)

status = writer.Write(path)
Expand Down
20 changes: 17 additions & 3 deletions cadquery/occ_impl/importers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

import OCP.IFSelect
from OCP.STEPControl import STEPControl_Reader
from OCP.Interface import Interface_Static

from ... import cq
from ..shapes import Shape
from .dxf import _importDXF
from ..types import UnitLiterals

RAD2DEG = 360.0 / (2 * pi)

Expand All @@ -24,18 +26,24 @@ class UNITS:


def importShape(
importType: Literal["STEP", "DXF", "BREP", "BIN"], fileName: str, *args, **kwargs
importType: Literal["STEP", "DXF", "BREP", "BIN"],
fileName: str,
unit: UnitLiterals = "MM",
*args,
**kwargs,
) -> "cq.Workplane":
"""
Imports a file based on the type (STEP, STL, etc)

:param importType: The type of file that we're importing
:param fileName: The name of the file that we're importing
:param unit: The unit of measurement for the STEP file. Default "MM".
:type unit: UnitLiterals
"""

# Check to see what type of file we're working with
if importType == ImportTypes.STEP:
return importStep(fileName)
return importStep(fileName, unit)
elif importType == ImportTypes.DXF:
return importDXF(fileName, *args, **kwargs)
elif importType == ImportTypes.BREP:
Expand Down Expand Up @@ -76,13 +84,19 @@ def importBin(fileName: str) -> "cq.Workplane":


# Loads a STEP file into a CQ.Workplane object
def importStep(fileName: str) -> "cq.Workplane":
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
"""

# Set the target cascade unit - OCCT scales from the file's declared unit to this unit
Interface_Static.SetCVal_s("xstep.cascade.unit", unit.upper())

# Now read and return the shape
reader = STEPControl_Reader()
readStatus = reader.ReadFile(fileName)
Expand Down
7 changes: 6 additions & 1 deletion cadquery/occ_impl/importers/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from ..assembly import AssemblyProtocol, Color, Material
from ..geom import Location
from ..shapes import Shape
from ..types import UnitLiterals


def _get_name(label: TDF_Label) -> str:
Expand Down Expand Up @@ -129,12 +130,15 @@ def _get_shape_color(s: TopoDS_Shape, color_tool: XCAFDoc_ColorTool) -> Color |
return rv


def importStep(assy: AssemblyProtocol, path: str):
def importStep(assy: AssemblyProtocol, path: str, unit: UnitLiterals = "MM"):
"""
Import a step file into an assembly.

:param assy: An Assembly object that will be packed with the contents of the STEP file.
:param path: Path and filename to the STEP file to read.
:param unit: The assumed unit of measurement when the STEP file does not
declare one in its header. Has no effect when the file already contains
a unit declaration. Default "MM".

:return: None
"""
Expand All @@ -147,6 +151,7 @@ def importStep(assy: AssemblyProtocol, path: str):
step_reader.SetSHUOMode(True)

Interface_Static.SetIVal_s("read.stepcaf.subshapes.name", 1)
Interface_Static.SetCVal_s("xstep.cascade.unit", unit.upper())

# Read the STEP file
status = step_reader.ReadFile(path)
Expand Down
26 changes: 21 additions & 5 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
)

from ..utils import multimethod, multidispatch, mypyclassmethod
from .types import UnitLiterals

# change default OCCT logging level
from OCP.Message import Message, Message_Gravity
Expand Down Expand Up @@ -511,15 +512,26 @@ def exportStl(

return writer.Write(self.wrapped, fileName)

def exportStep(self, fileName: str, **kwargs) -> IFSelect_ReturnStatus:
def exportStep(
self,
fileName: str,
unit: UnitLiterals = "MM",
outputUnit: Optional[UnitLiterals] = None,
**kwargs,
) -> IFSelect_ReturnStatus:
"""
Export this shape to a STEP file.

kwargs is used to provide optional keyword arguments to configure the exporter.
kwargs is used to provide additional optional keyword arguments to configure the exporter.

:param fileName: Path and filename for writing.
:type fileName: str
:param unit: The internal unit of the model's geometry values. Default "MM".
:type unit: UnitLiterals
:param outputUnit: The unit to use in the STEP file header. If None, defaults to the value of ``unit``.
Use this when you want the output file to declare a different unit than the model's internal unit,
for example to export a MM model as a STEP file declaring meters.
:type outputUnit: UnitLiterals or None
:param write_pcurves: Enable or disable writing parametric curves to the STEP file. Default True.

If False, writes STEP file without pcurves. This decreases the size of the resulting STEP file.
:type write_pcurves: bool
:param precision_mode: Controls the uncertainty value for STEP entities. Specify -1, 0, or 1. Default 0.
Expand All @@ -536,6 +548,10 @@ def exportStep(self, fileName: str, **kwargs) -> IFSelect_ReturnStatus:
writer = STEPControl_Writer()
Interface_Static.SetIVal_s("write.surfacecurve.mode", pcurves)
Interface_Static.SetIVal_s("write.precision.mode", precision_mode)
Interface_Static.SetCVal_s("xstep.cascade.unit", unit.upper())
Interface_Static.SetCVal_s(
"write.step.unit", outputUnit if outputUnit is not None else unit.upper()
)
writer.Transfer(self.wrapped, STEPControl_AsIs)

return writer.Write(fileName)
Expand Down Expand Up @@ -617,7 +633,7 @@ def geomType(self) -> Geoms:
if isinstance(tr, str):
rv = tr
elif tr is BRepAdaptor_Curve:
rv = geom_LUT_EDGE[tr(self.wrapped).GetType()]
rv = geom_LUT_EDGE[tr(tcast(TopoDS_Edge, self.wrapped)).GetType()]
else:
rv = geom_LUT_FACE[tr(self.wrapped).GetType()]

Expand Down
3 changes: 3 additions & 0 deletions cadquery/occ_impl/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from typing_extensions import Literal

UnitLiterals = Literal["MM", "CM", "M", "KM", "INCH", "FT", "MI", "UM", "NM"]
71 changes: 70 additions & 1 deletion doc/importexport.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,24 @@ Importing STEP
###############

STEP files can be imported using the :meth:`importers.importStep` method (note the capitalization of "Step").
There are no parameters for this method other than the file path to import.

.. code-block:: python

import cadquery as cq

result = cq.importers.importStep("/path/to/step/block.stp")

By default, no unit conversion is applied.
The ``unit`` parameter sets the target unit - OCCT scales from the unit declared in the STEP file's header to the requested unit.
The valid values are defined by :class:`UnitLiterals`: ``"MM"``, ``"CM"``, ``"M"``, ``"KM"``, ``"INCH"``, ``"FT"``, ``"MI"``, ``"UM"``, and ``"NM"``.

.. code-block:: python

import cadquery as cq

# Import a STEP file converting its units to meters
result = cq.importers.importStep("/path/to/step/block.stp", unit="M")

Exporting STEP
###############

Expand Down Expand Up @@ -139,6 +149,35 @@ or the :meth:`Assembly.exportAssembly`` method.
# or equivalently when exporting a lower level Shape object
box.val().export("/path/to/step/box2.step", opt={"write_pcurves": False})

Setting Units
--------------

By default, CadQuery exports STEP files with millimeter units.
The ``unit`` parameter specifies the internal unit of the model's geometry values.
The ``outputUnit`` parameter controls the unit written to the STEP file header.
If ``outputUnit`` is not specified, it defaults to the value of ``unit``.
The valid values for both parameters are defined by :class:`UnitLiterals`: ``"MM"``, ``"CM"``, ``"M"``, ``"KM"``, ``"INCH"``, ``"FT"``, ``"MI"``, ``"UM"``, and ``"NM"``.

To export a millimeter model as a STEP file declaring meters, set ``outputUnit="M"`` while leaving ``unit`` at its default of ``"MM"``.
OCCT will scale the coordinate values accordingly.

.. code-block:: python

import cadquery as cq

# Create a simple object
box = cq.Workplane().box(10, 10, 10)

# Export with meter units
box.val().exportStep("/path/to/step/box.step", outputUnit="M")

For assemblies, see the ``outputUnit`` parameter example in the exporting assemblies section.

.. note::

CadQuery treats all geometry values as millimeters internally.
If you built a model using values that represent meters (e.g. a box with side length ``10`` meaning 10 meters), you must scale the geometry by 1000 before exporting, then export with the default ``unit="MM"``.
Exporting such a model directly with ``outputUnit="M"`` will produce a STEP file representing a 0.01-meter (10mm) object, not a 10-meter one.

Exporting Assemblies
####################
Expand Down Expand Up @@ -176,6 +215,36 @@ export with all defaults is shown below.
This will produce a STEP file that is nested with auto-generated object names. The colors of each assembly object will be
preserved, but the names that were set for each will not.

Setting Units
--------------

By default, CadQuery exports STEP files with millimeter units.
The ``unit`` parameter specifies the internal unit of the model's geometry values.
The ``outputUnit`` parameter controls the unit written to the STEP file header.
If ``outputUnit`` is not specified, it defaults to the value of ``unit``.
The valid values for both parameters are defined by :class:`UnitLiterals`: ``"MM"``, ``"CM"``, ``"M"``, ``"KM"``, ``"INCH"``, ``"FT"``, ``"MI"``, ``"UM"``, and ``"NM"``.
The default is ``"MM"``.

To export a millimeter model as a STEP file declaring meters, set ``outputUnit="M"`` while leaving ``unit`` at its default of ``"MM"``.
OCCT will scale the coordinate values accordingly.

.. code-block:: python

import cadquery as cq

assy = cq.Assembly()
assy.add(cq.Workplane().box(10, 10, 10), name="box")

# Export the assembly with meter units
assy.export("/path/to/step/assy.step", outputUnit="M")

.. note::

CadQuery treats all geometry values as millimeters internally.
If you built a model using values that represent meters (e.g. a box with side length ``10`` meaning 10 meters), you must scale the geometry by 1000 before exporting, then export with the default ``unit="MM"``.
Exporting such a model directly with ``outputUnit="M"`` will produce a STEP file representing a 0.01-meter
(10mm) object, not a 10-meter one.

Fused
------

Expand Down
Loading