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
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ omit =
exclude_lines =
# Ignore stub body
\.\.\.
# Ignore imports for type checking only
if TYPE_CHECKING:
18 changes: 9 additions & 9 deletions cadquery/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,9 @@
UnaryConstraintKind,
BinaryConstraintKind,
)
from .occ_impl.exporters.assembly import (
exportAssembly,
exportCAF,
exportVTKJS,
exportVRML,
exportGLTF,
STEPExportModeLiterals,
)
from .occ_impl.importers.assembly import importStep as _importStep, importXbf, importXml

from .types import UnitLiterals
from .types import UnitLiterals, STEPExportModeLiterals

from .selectors import _expression_grammar as _selector_grammar
from .utils import deprecate, BiDict, instance_of
Expand Down Expand Up @@ -596,6 +588,14 @@ def export(
if mode not in get_args(STEPExportModeLiterals):
raise ValueError(f"Unknown assembly export mode {mode} for STEP")

from .occ_impl.exporters.assembly import (
exportAssembly,
exportCAF,
exportVTKJS,
exportVRML,
exportGLTF,
)

if exportType is None:
t = path.split(".")[-1].upper()
if t in ("STEP", "XML", "XBF", "VRML", "VTKJS", "GLTF", "GLB", "STL"):
Expand Down
28 changes: 18 additions & 10 deletions cadquery/occ_impl/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Any,
List,
cast,
TYPE_CHECKING,
)
from typing_extensions import Protocol, Self
from math import degrees, radians
Expand Down Expand Up @@ -41,19 +42,14 @@
from OCP.TopoDS import TopoDS_Shape
from OCP.gp import gp_EulerSequence

from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkPolyDataMapper as vtkMapper,
vtkRenderer,
vtkProp3D,
)

from .geom import Location
from .shapes import Shape, Solid, Compound, GlueLiteral, _set_glue, _set_builder_options
from .exporters.vtk import toString, extractEdgesFaces
from ..cq import Workplane
from ..utils import BiDict

if TYPE_CHECKING:
from vtkmodules.vtkRenderingCore import vtkRenderer, vtkProp3D

# type definitions
AssemblyObjects = Union[Shape, Workplane, None]

Expand Down Expand Up @@ -628,7 +624,10 @@ def toVTKAssy(
linewidth: float = 2,
tolerance: float = 1e-3,
angularTolerance: float = 0.1,
) -> List[vtkProp3D]:
) -> "List[vtkProp3D]":

from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper as vtkMapper
from .exporters.vtk import extractEdgesFaces

rv: List[vtkProp3D] = []

Expand Down Expand Up @@ -675,7 +674,14 @@ def toVTK(
color: Tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0),
tolerance: float = 1e-3,
angularTolerance: float = 0.1,
) -> vtkRenderer:
) -> "vtkRenderer":

from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkPolyDataMapper as vtkMapper,
vtkRenderer,
)
from .exporters.vtk import extractEdgesFaces

renderer = vtkRenderer()

Expand Down Expand Up @@ -725,6 +731,8 @@ def toJSON(
Export an object to a structure suitable for converting to VTK.js JSON.
"""

from .exporters.vtk import toString

rv = []

for shape, _, loc, col_ in assy:
Expand Down
24 changes: 21 additions & 3 deletions cadquery/occ_impl/exporters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import IO, Optional, Union, cast, Dict, Any, Iterable
from importlib import import_module
from typing import IO, Optional, Union, cast, Dict, Any, Iterable, TYPE_CHECKING
from typing_extensions import Literal

from OCP.VrmlAPI import VrmlAPI
Expand All @@ -10,8 +11,10 @@
from .json import JsonMesh
from .amf import AmfWriter
from .threemf import ThreeMFWriter
from .dxf import exportDXF, exportDXFProjection, DxfDocument
from .vtk import exportVTP

if TYPE_CHECKING:
from .dxf import exportDXF, exportDXFProjection, DxfDocument
from .vtk import exportVTP


class ExportTypes:
Expand Down Expand Up @@ -110,6 +113,8 @@ def export(
tmfw.write3mf(f)

elif exportType == ExportTypes.DXF:
from .dxf import exportDXF

exportDXF(w, fname, **opt)

elif exportType == ExportTypes.STEP:
Expand All @@ -128,6 +133,8 @@ def export(
VrmlAPI.Write_s(shape.wrapped, fname)

elif exportType == ExportTypes.VTP:
from .vtk import exportVTP

exportVTP(shape, fname, tolerance, angularTolerance)

elif exportType == ExportTypes.BREP:
Expand All @@ -138,3 +145,14 @@ def export(

else:
raise ValueError("Unknown export type")


# lazy: importing these here would load ezdxf and vtk with cadquery
def __getattr__(name):
if name in ("dxf", "vtk", "assembly"):
return import_module(f".{name}", __name__)
if name in ("exportDXF", "exportDXFProjection", "DxfDocument"):
return getattr(import_module(".dxf", __name__), name)
if name == "exportVTP":
return import_module(".vtk", __name__).exportVTP
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
6 changes: 1 addition & 5 deletions cadquery/occ_impl/exporters/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from tempfile import TemporaryDirectory
from shutil import make_archive
from typing import Optional
from typing_extensions import Literal

from vtkmodules.vtkIOExport import vtkJSONSceneExporter, vtkVRMLExporter
from vtkmodules.vtkRenderingCore import vtkRenderWindow
Expand Down Expand Up @@ -50,17 +49,14 @@
from ..assembly import AssemblyProtocol, toCAF, toVTK, toFusedCAF
from ..geom import Location
from ..shapes import Shape, Compound
from ...types import UnitLiterals
from ...types import UnitLiterals, STEPExportModeLiterals


class ExportModes:
DEFAULT = "default"
FUSED = "fused"


STEPExportModeLiterals = Literal["default", "fused"]


def exportAssembly(
assy: AssemblyProtocol,
path: str,
Expand Down
3 changes: 2 additions & 1 deletion cadquery/occ_impl/importers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

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

RAD2DEG = 360.0 / (2 * pi)
Expand Down Expand Up @@ -133,6 +132,8 @@ def importDXF(
:param include: a list of layer names to import
"""

from .dxf import _importDXF

faces = _importDXF(filename, tol, exclude, include)

return cq.Workplane("XY").newObject(faces)
10 changes: 7 additions & 3 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Self,
TypeAlias,
TypeVar,
TYPE_CHECKING,
cast as tcast,
overload,
Type,
Expand All @@ -20,9 +21,6 @@

from warnings import warn

from vtkmodules.vtkCommonDataModel import vtkPolyData
from vtkmodules.vtkFiltersCore import vtkTriangleFilter, vtkPolyDataNormals

from OCP.ShapeBuild import ShapeBuild_ReShape

from .geom import Vector, VectorLike, BoundBox, Plane, Location, Matrix
Expand All @@ -36,6 +34,9 @@
from ..utils import multimethod, multidispatch, mypyclassmethod
from ..types import UnitLiterals

if TYPE_CHECKING:
from vtkmodules.vtkCommonDataModel import vtkPolyData

# change default OCCT logging level
from OCP.Message import Message, Message_Gravity

Expand Down Expand Up @@ -1711,6 +1712,9 @@ def toVtkPolyData(
Convert shape to vtkPolyData
"""

# vtk must be loaded before getVtkPolyData(), which returns None otherwise
from vtkmodules.vtkFiltersCore import vtkTriangleFilter, vtkPolyDataNormals

vtk_shape = IVtkOCC_Shape(self.wrapped)
shape_data = IVtkVTK_ShapeData()
shape_mesher = IVtkOCC_ShapeMesher()
Expand Down
3 changes: 2 additions & 1 deletion cadquery/sketch.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
)
from .occ_impl.geom import Location, Vector
from .occ_impl.exporters import export
from .occ_impl.importers.dxf import _importDXF
from .occ_impl.sketch_solver import (
SketchConstraintSolver,
ConstraintKind,
Expand Down Expand Up @@ -232,6 +231,8 @@ def importDXF(
Import a DXF file and construct face(s)
"""

from .occ_impl.importers.dxf import _importDXF

res = Compound.makeCompound(_importDXF(filename, tol, exclude, include))

return self.face(res, angle, mode, tag)
Expand Down
1 change: 1 addition & 0 deletions cadquery/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Union, Literal

UnitLiterals = Literal["MM", "CM", "M", "KM", "INCH", "FT", "MI", "UM", "NM"]
STEPExportModeLiterals = Literal["default", "fused"]
Real = Union[int, float]
45 changes: 45 additions & 0 deletions tests/test_lazy_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os
import subprocess
import sys

import pytest

import cadquery
from cadquery.occ_impl import exporters
from cadquery.occ_impl.exporters import assembly, dxf, vtk


def test_import_does_not_load_ezdxf_or_vtk():
# a fresh interpreter: this process has ezdxf and vtk loaded by other tests
code = (
"import sys, cadquery as cq;"
"print({m.split('.')[0] for m in sys.modules} & {'ezdxf', 'vtkmodules'});"
# the submodules stay reachable through the packages
"cq.exporters.assembly.ExportModes, cq.exporters.dxf, cq.exporters.vtk;"
"print('ok')"
)
# run next to the package under test; only stdout is checked, since the
# interpreter may crash on exit on Windows (#1911)
out = subprocess.run(
[sys.executable, "-u", "-c", code],
cwd=os.path.dirname(os.path.dirname(cadquery.__file__)),
capture_output=True,
text=True,
)

assert out.stdout.split() == ["set()", "ok"], out.stdout + out.stderr


def test_exporters_lazy_names():
# the submodules are attributes here already, so ask __getattr__ directly
assert exporters.__getattr__("dxf") is dxf
assert exporters.__getattr__("vtk") is vtk
assert exporters.__getattr__("assembly") is assembly

assert exporters.exportDXF is dxf.exportDXF
assert exporters.exportDXFProjection is dxf.exportDXFProjection
assert exporters.DxfDocument is dxf.DxfDocument
assert exporters.exportVTP is vtk.exportVTP

with pytest.raises(AttributeError):
exporters.nonexistent
Loading