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 cadquery/func.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
chamfer2D,
draft,
History,
hlr,
)

__all__ = [
Expand Down Expand Up @@ -124,4 +125,5 @@
"fillet2D",
"draft",
"History",
"hlr",
]
9 changes: 2 additions & 7 deletions cadquery/occ_impl/exporters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import tempfile
import os
import io as StringIO

from typing import IO, Optional, Union, cast, Dict, Any, Iterable
from typing_extensions import Literal

from OCP.VrmlAPI import VrmlAPI

from ...utils import deprecate
from ..shapes import Shape, compound
from ...types import UnitLiterals

from .svg import getSVG
from .svg import getSVG, exportSVG
from .json import JsonMesh
from .amf import AmfWriter
from .threemf import ThreeMFWriter
from .dxf import exportDXF, DxfDocument
from .dxf import exportDXF, exportDXFProjection, DxfDocument
from .vtk import exportVTP


Expand Down
42 changes: 39 additions & 3 deletions cadquery/occ_impl/exporters/dxf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
from typing_extensions import Self

from ...units import RAD2DEG
from ..shapes import Face, Edge, Shape, Compound, compound
from ..geom import Plane
from ..shapes import Face, Edge, Shape, Compound, compound, hlr
from ..geom import Plane, VectorLike


ApproxOptions = Literal["spline", "arc"]
Expand Down Expand Up @@ -157,6 +157,7 @@ def add_shape(self, shape: Union[WorkplaneLike, Shape], layer: str = "") -> Self
plane = shape.plane
shape_ = compound(*shape.__iter__()).transformShape(plane.fG)
else:
plane = Plane((0, 0, 0))
shape_ = shape

general_attributes = {}
Expand Down Expand Up @@ -365,7 +366,7 @@ def _dxf_spline(cls, edge: Edge, plane: Plane) -> DxfEntityAttributes:


def exportDXF(
w: Union[WorkplaneLike, Shape, Iterable[Shape]],
w: WorkplaneLike | Shape | Iterable[Shape],
fname: str,
approx: Optional[ApproxOptions] = None,
tolerance: float = 1e-3,
Expand Down Expand Up @@ -394,3 +395,38 @@ def exportDXF(

zoom.extents(dxf.msp)
dxf.document.saveas(fname)


def exportDXFProjection(
w: Union[WorkplaneLike, Shape],
pnt: VectorLike,
dir: VectorLike,
fname: str,
approx: Optional[ApproxOptions] = None,
tolerance: float = 1e-3,
*,
doc_units: int = units.MM,
) -> None:
"""
Export Workplane or shape content to DXF using projections. Works with 3D objects.

:param w: Workplane to be exported.
:param pnt: Center of projection.
:param dir: Direction of projection.
:param fname: Output filename.
:param approx: Approximation strategy. None means no approximation is applied.
"spline" results in all splines being approximated as cubic splines. "arc" results
in all curves being approximated as arcs and straight segments.
:param tolerance: Approximation tolerance.
:param doc_units: ezdxf document/modelspace :doc:`units <ezdxf-stable:concepts/units>` (in. = ``1``, mm = ``4``).
"""

shapes = []

if isinstance(w, WorkplaneLike):
for s in w.__iter__():
shapes.append(hlr(s, pnt, dir)[0])
else:
shapes.append(hlr(w, pnt, dir)[0])

exportDXF(shapes, fname, approx, tolerance, doc_units=doc_units)
79 changes: 14 additions & 65 deletions cadquery/occ_impl/exporters/svg.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import io as StringIO

from ..shapes import Shape, Compound, TOLERANCE
from ..shapes import Shape, Compound, Edge
from ..geom import BoundBox
from ..shapes import hlr


from OCP.gp import gp_Ax2, gp_Pnt, gp_Dir
from OCP.BRepLib import BRepLib
from OCP.HLRBRep import HLRBRep_Algo, HLRBRep_HLRToShape
from OCP.HLRAlgo import HLRAlgo_Projector
from OCP.GCPnts import GCPnts_QuasiUniformDeflection

DISCRETIZATION_TOLERANCE = 1e-3
Expand Down Expand Up @@ -106,21 +103,21 @@ def makeSVGedge(e):
return cs.getvalue()


def getPaths(visibleShapes, hiddenShapes):
def getPaths(
visibleEdges: list[Edge], hiddenEdges: list[Edge]
) -> tuple[list[str], list[str]]:
"""
Collects the visible and hidden edges from the CadQuery object.
"""

hiddenPaths = []
visiblePaths = []

for s in visibleShapes:
for e in s.Edges():
visiblePaths.append(makeSVGedge(e))
for e in visibleEdges:
visiblePaths.append(makeSVGedge(e))

for s in hiddenShapes:
for e in s.Edges():
hiddenPaths.append(makeSVGedge(e))
for e in hiddenEdges:
hiddenPaths.append(makeSVGedge(e))

return (hiddenPaths, visiblePaths)

Expand Down Expand Up @@ -186,64 +183,16 @@ def getSVG(shape, opts=None):
showHidden = bool(d["showHidden"])
focus = float(d["focus"]) if d.get("focus") else None

hlr = HLRBRep_Algo()
hlr.Add(shape.wrapped)

coordinate_system = gp_Ax2(gp_Pnt(), gp_Dir(*projectionDir))

if focus is not None:
projector = HLRAlgo_Projector(coordinate_system, focus)
else:
projector = HLRAlgo_Projector(coordinate_system)

hlr.Projector(projector)
hlr.Update()
hlr.Hide()

hlr_shapes = HLRBRep_HLRToShape(hlr)

visible = []

visible_sharp_edges = hlr_shapes.VCompound()
if not visible_sharp_edges.IsNull():
visible.append(visible_sharp_edges)

visible_smooth_edges = hlr_shapes.Rg1LineVCompound()
if not visible_smooth_edges.IsNull():
visible.append(visible_smooth_edges)

visible_contour_edges = hlr_shapes.OutLineVCompound()
if not visible_contour_edges.IsNull():
visible.append(visible_contour_edges)

hidden = []

hidden_sharp_edges = hlr_shapes.HCompound()
if not hidden_sharp_edges.IsNull():
hidden.append(hidden_sharp_edges)

hidden_contour_edges = hlr_shapes.OutLineHCompound()
if not hidden_contour_edges.IsNull():
hidden.append(hidden_contour_edges)

# Fix the underlying geometry - otherwise we will get segfaults
for el in visible:
BRepLib.BuildCurves3d_s(el, TOLERANCE)
for el in hidden:
BRepLib.BuildCurves3d_s(el, TOLERANCE)

# convert to native CQ objects
visible = list(map(Shape, visible))
hidden = list(map(Shape, hidden))
(hiddenPaths, visiblePaths) = getPaths(visible, hidden)
visibleEdges, hiddenEdges = hlr(shape, (0, 0, 0), projectionDir, focus)
hiddenPaths, visiblePaths = getPaths(visibleEdges, hiddenEdges)

# get bounding box -- these are all in 2D space
bb = Compound.makeCompound(hidden + visible).BoundingBox()
bb = Compound.makeCompound(hiddenEdges + visibleEdges).BoundingBox()

# Determine whether the user wants to fit the drawing to the bounding box
if width == None or height == None:
if width is None or height is None:
# Fit image to specified width (or height)
if width == None:
if width is None:
width = (height - (2.0 * marginTop)) * (
bb.xlen / bb.ylen
) + 2.0 * marginLeft
Comment thread
adam-urbanczyk marked this conversation as resolved.
Expand Down
63 changes: 63 additions & 0 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
BRepAlgoAPI_Check,
)

from OCP.HLRAlgo import HLRAlgo_Projector
from OCP.HLRBRep import HLRBRep_Algo, HLRBRep_HLRToShape

from OCP.Geom import (
Geom_BezierCurve,
Geom_ConicalSurface,
Expand Down Expand Up @@ -7971,3 +7974,63 @@ def closest(s1: Shape, s2: Shape) -> tuple[Vector, Vector]:
assert ext.Perform()

return Vector(ext.PointOnShape1(1)), Vector(ext.PointOnShape2(1))


def hlr(
Comment thread
adam-urbanczyk marked this conversation as resolved.
shape, pnt: VectorLike, dir: VectorLike, focus: float | None = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to provide an "up" direction with default (0, 0, 1) either in this PR or follow up. Otherwise we continue to have the problem of uncontrolled orientation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

) -> tuple[Compound, Compound]:
"""
Project shape onto a plane defined by pnt and dir and apply hidden line removal. Useful for
generating views for technical drawings.
"""
hlr = HLRBRep_Algo()
hlr.Add(shape.wrapped)

coordinate_system = gp_Ax2(Vector(pnt).toPnt(), Vector(dir).toDir())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could take a look at adding the "up" direction. I had started on that in #1277. We can either define a fallback as in #1277 or return a ValueError when up and dir are parallel.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, go for it.


if focus is not None:
projector = HLRAlgo_Projector(coordinate_system, focus)
else:
projector = HLRAlgo_Projector(coordinate_system)

hlr.Projector(projector)
hlr.Update()
hlr.Hide()

hlr_shapes = HLRBRep_HLRToShape(hlr)

visible = []

visible_sharp_edges = hlr_shapes.VCompound()
if not visible_sharp_edges.IsNull():
visible.append(visible_sharp_edges)

visible_smooth_edges = hlr_shapes.Rg1LineVCompound()
if not visible_smooth_edges.IsNull():
visible.append(visible_smooth_edges)

visible_contour_edges = hlr_shapes.OutLineVCompound()
if not visible_contour_edges.IsNull():
visible.append(visible_contour_edges)

hidden = []

hidden_sharp_edges = hlr_shapes.HCompound()
if not hidden_sharp_edges.IsNull():
hidden.append(hidden_sharp_edges)

hidden_contour_edges = hlr_shapes.OutLineHCompound()
if not hidden_contour_edges.IsNull():
hidden.append(hidden_contour_edges)

# Fix the underlying geometry - otherwise we will get segfaults
for el in visible:
BRepLib.BuildCurves3d_s(el, TOLERANCE)
for el in hidden:
BRepLib.BuildCurves3d_s(el, TOLERANCE)

# Extract edges
visible_edges = compound(_compound_or_shape(visible).Edges())
hidden_edges = compound(_compound_or_shape(hidden).Edges())

return visible_edges, hidden_edges
37 changes: 37 additions & 0 deletions examples/Ex102_DXF_with_HLR.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from cadquery import Workplane, exporters

viewpoint = {
"top": (0, 0, 1),
"left": (1, 0, 0),
"front": (0, 1, 0),
"ortho": (1, 1, 1),
}


def exportDXF3rdAngleProjection(my_part: Workplane, prefix: str) -> None:
for name, direction in viewpoint.items():
exporters.exportDXFProjection(
my_part, (0, 0, 0), direction, f"{prefix}{name}.dxf", doc_units=6,
)


def exportSVG3rdAngleProjection(my_part, prefix: str) -> None:
for name, direction in viewpoint.items():
exporters.exportSVG(
my_part, f"{prefix}{name}.svg", opts={"projectionDir": direction,},
)


# Build the part
width = 10
depth = 10
height = 10
hole_dia = 3.0

baseplate = Workplane("XY").box(width, depth, height).edges("|Z").fillet(2.0) #
drilled = baseplate.faces(">Z").workplane().cskHole(hole_dia, hole_dia * 2, 82.0) #

# Expected DXF output to be identical to SVG output
exportSVG3rdAngleProjection(drilled, "")
exportDXF3rdAngleProjection(drilled, "")
exportDXF3rdAngleProjection(drilled.val(), "shape_")