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 .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ omit =
exclude_lines =
# Ignore stub body
\.\.\.
if TYPE_CHECKING:
23 changes: 14 additions & 9 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,15 @@
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 +625,9 @@ 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

rv: List[vtkProp3D] = []

Expand Down Expand Up @@ -675,7 +674,13 @@ 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,
)

renderer = vtkRenderer()

Expand Down
16 changes: 11 additions & 5 deletions cadquery/occ_impl/exporters/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@

from tempfile import TemporaryDirectory
from shutil import make_archive
from typing import Optional
from typing import Optional, TYPE_CHECKING
from typing_extensions import Literal

from vtkmodules.vtkIOExport import vtkJSONSceneExporter, vtkVRMLExporter
from vtkmodules.vtkRenderingCore import vtkRenderWindow

from OCP.XSControl import XSControl_WorkSession
from OCP.STEPCAFControl import STEPCAFControl_Writer
from OCP.STEPControl import STEPControl_StepModelType
Expand Down Expand Up @@ -52,6 +49,9 @@
from ..shapes import Shape, Compound
from ...types import UnitLiterals

if TYPE_CHECKING:
from vtkmodules.vtkRenderingCore import vtkRenderWindow


class ExportModes:
DEFAULT = "default"
Expand Down Expand Up @@ -392,11 +392,13 @@ def exportCAF(assy: AssemblyProtocol, path: str, binary: bool = False) -> bool:

def _vtkRenderWindow(
assy: AssemblyProtocol, tolerance: float = 1e-3, angularTolerance: float = 0.1
) -> vtkRenderWindow:
) -> "vtkRenderWindow":
"""
Convert an assembly to a vtkRenderWindow. Used by vtk based exporters.
"""

from vtkmodules.vtkRenderingCore import vtkRenderWindow

renderer = toVTK(assy, tolerance=tolerance, angularTolerance=angularTolerance)
renderWindow = vtkRenderWindow()
renderWindow.AddRenderer(renderer)
Expand All @@ -412,6 +414,8 @@ def exportVTKJS(assy: AssemblyProtocol, path: str):
Export an assembly to a zipped vtkjs. NB: .zip extensions is added to path.
"""

from vtkmodules.vtkIOExport import vtkJSONSceneExporter

renderWindow = _vtkRenderWindow(assy)

with TemporaryDirectory() as tmpdir:
Expand All @@ -433,6 +437,8 @@ def exportVRML(
Export an assembly to a vrml file using vtk.
"""

from vtkmodules.vtkIOExport import vtkVRMLExporter

exporter = vtkVRMLExporter()
exporter.SetFileName(path)
exporter.SetRenderWindow(_vtkRenderWindow(assy, tolerance, angularTolerance))
Expand Down
16 changes: 10 additions & 6 deletions cadquery/occ_impl/exporters/dxf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
runtime_checkable,
)

import ezdxf
from ezdxf import units, zoom
from ezdxf.entities import factory
from OCP.GeomConvert import GeomConvert
from OCP.gp import gp_Dir
from OCP.GC import GC_MakeArcOfEllipse
Expand All @@ -27,6 +24,8 @@
from ..geom import Plane, VectorLike


_MM = 4 # ezdxf.units.MM, without importing ezdxf at module level

ApproxOptions = Literal["spline", "arc"]
DxfEntityAttributes = Tuple[
Literal["ARC", "CIRCLE", "ELLIPSE", "LINE", "SPLINE",], Dict[str, Any]
Expand Down Expand Up @@ -85,7 +84,7 @@ def __init__(
self,
dxfversion: str = "AC1027",
setup: Union[bool, List[str]] = False,
doc_units: int = units.MM,
doc_units: int = _MM,
*,
metadata: Union[Dict[str, str], None] = None,
approx: Optional[ApproxOptions] = None,
Expand All @@ -111,6 +110,8 @@ def __init__(

:param tolerance: Approximation tolerance for converting :class:`cadquery.Workplane` objects to DXF entities.
"""
import ezdxf

if metadata is None:
metadata = {}

Expand Down Expand Up @@ -153,6 +154,8 @@ def add_shape(self, shape: Union[WorkplaneLike, Shape], layer: str = "") -> Self
:param s: CadQuery Workplane or Shape
:param layer: layer definition name
"""
import ezdxf
from ezdxf.entities import factory

if isinstance(shape, WorkplaneLike):
plane = shape.plane
Expand Down Expand Up @@ -372,7 +375,7 @@ def exportDXF(
approx: Optional[ApproxOptions] = None,
tolerance: float = 1e-3,
*,
doc_units: int = units.MM,
doc_units: int = _MM,
) -> None:
"""
Export Workplane content to DXF. Works with 2D sections.
Expand All @@ -385,6 +388,7 @@ def exportDXF(
:param tolerance: Approximation tolerance.
:param doc_units: ezdxf document/modelspace :doc:`units <ezdxf-stable:concepts/units>` (in. = ``1``, mm = ``4``).
"""
from ezdxf import zoom

dxf = DxfDocument(approx=approx, tolerance=tolerance, doc_units=doc_units)

Expand All @@ -407,7 +411,7 @@ def exportDXFProjection(
tolerance: float = 1e-3,
*,
up: Optional[VectorLike] = None,
doc_units: int = units.MM,
doc_units: int = _MM,
) -> None:
"""
Export to DXF using projections. Works with 3D objects.
Expand Down
29 changes: 17 additions & 12 deletions cadquery/occ_impl/exporters/vtk.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter
from vtkmodules.vtkFiltersCore import vtkAppendPolyData
from vtkmodules.vtkCommonDataModel import vtkPolyData
from vtkmodules.vtkFiltersExtraction import vtkExtractCellsByType

from vtkmodules.vtkCommonDataModel import (
VTK_TRIANGLE,
VTK_LINE,
VTK_VERTEX,
VTK_POLY_LINE,
)
from typing import TYPE_CHECKING

from ..shapes import Shape

if TYPE_CHECKING:
from vtkmodules.vtkCommonDataModel import vtkPolyData

def extractEdgesFaces(data: vtkPolyData) -> tuple[vtkPolyData, vtkPolyData]:

def extractEdgesFaces(data: "vtkPolyData") -> "tuple[vtkPolyData, vtkPolyData]":
"""Helper for edges and faces extraction"""

from vtkmodules.vtkFiltersExtraction import vtkExtractCellsByType
from vtkmodules.vtkCommonDataModel import (
VTK_TRIANGLE,
VTK_LINE,
VTK_VERTEX,
VTK_POLY_LINE,
)

# extract edges
extr = vtkExtractCellsByType()
extr.SetInputDataObject(data)
Expand Down Expand Up @@ -44,6 +45,8 @@ def exportVTP(
shape: Shape, fname: str, tolerance: float = 0.1, angularTolerance: float = 0.1
):

from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter

writer = vtkXMLPolyDataWriter()
writer.SetFileName(fname)
writer.SetInputData(shape.toVtkPolyData(tolerance, angularTolerance))
Expand All @@ -54,6 +57,8 @@ def toString(
shape: Shape, tolerance: float = 1e-3, angularTolerance: float = 0.1
) -> tuple[str, str]:

from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter

writer_edges = vtkXMLPolyDataWriter()
writer_edges.SetWriteToOutputString(True)

Expand Down
3 changes: 1 addition & 2 deletions cadquery/occ_impl/importers/dxf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from ..geom import Vector
from ..shapes import Shape, Edge, Face, sortWiresByBuildOrder

import ezdxf

from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds
from OCP.collections import HSequence_TopoDS_Shape as TopTools_HSequenceOfShape
from OCP.gp import gp_Pnt
Expand Down Expand Up @@ -172,6 +170,7 @@ def _importDXF(
:param exclude: a list of layer names not to import
:param include: a list of layer names to import
"""
import ezdxf

if exclude and include:
raise ValueError("you may specify either 'include' or 'exclude' but not both")
Expand Down
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
23 changes: 23 additions & 0 deletions tests/test_lazy_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os
import subprocess
import sys

import cadquery


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;"
"print({m.split('.')[0] for m in sys.modules} & {'ezdxf', 'vtkmodules'})"
)
# 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, "-c", code],
cwd=os.path.dirname(os.path.dirname(cadquery.__file__)),
capture_output=True,
text=True,
)

assert out.stdout.strip().endswith("set()"), out.stdout + out.stderr