From 4552668e6684b10f32ed51ba8dea611a4322ae43 Mon Sep 17 00:00:00 2001 From: Antony Chan Date: Sun, 22 Feb 2026 15:03:07 -0800 Subject: [PATCH 01/11] Project 3D part to plane Simulate the API from Build123d `project_to_viewpoint`: Given a 3D vector representing the camera position pointing at the origin `(0,0,0)`, render all visible edges/arcs/splines representing the outline of the 3D part projected to the camera. Also render all hidden edges/arcs/splines. Provide and example script `tests/test_projection.py` where a generic part (A bracket with rounded corners and a countersunk hole) is projected to top view, side view, front view, and orthogonal view, and then exported to DXF 2D drawings. Reference: https://build123d.readthedocs.io/en/latest/tech_drawing_tutorial.html --- cadquery/occ_impl/exporters/dxf.py | 1 + cadquery/occ_impl/exporters/svg.py | 81 ++++++------------------------ cadquery/occ_impl/shapes.py | 63 +++++++++++++++++++++++ tests/test_projection.py | 45 +++++++++++++++++ 4 files changed, 124 insertions(+), 66 deletions(-) create mode 100644 tests/test_projection.py diff --git a/cadquery/occ_impl/exporters/dxf.py b/cadquery/occ_impl/exporters/dxf.py index 41ae15224..8180f274f 100644 --- a/cadquery/occ_impl/exporters/dxf.py +++ b/cadquery/occ_impl/exporters/dxf.py @@ -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 = {} diff --git a/cadquery/occ_impl/exporters/svg.py b/cadquery/occ_impl/exporters/svg.py index b41dee7b0..2a04290e0 100644 --- a/cadquery/occ_impl/exporters/svg.py +++ b/cadquery/occ_impl/exporters/svg.py @@ -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 projectToViewpoint -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 @@ -106,7 +103,9 @@ 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. """ @@ -114,18 +113,16 @@ def getPaths(visibleShapes, hiddenShapes): 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) -def getSVG(shape, opts=None): +def getSVG(shape: Shape, opts=None): """ Export a shape to SVG text. @@ -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 = projectToViewpoint(shape, 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 diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 49f2ab667..bb1ceba81 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -158,6 +158,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, @@ -6572,3 +6575,63 @@ def closest(s1: Shape, s2: Shape) -> Tuple[Vector, Vector]: assert ext.Perform() return Vector(ext.PointOnShape1(1)), Vector(ext.PointOnShape2(1)) + + +def projectToViewpoint( + shape, projectionDir: tuple[float, float, float], focus: Optional[float] = None, +) -> tuple[list[Edge], list[Edge]]: + 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 = [Shape.cast(s) for s in visible] # s is a TopoDS_Shape (Compound) + hidden = [Shape.cast(s) for s in hidden] + + # Extract edges + visible_edges = [e for c in visible for e in c.Edges()] + hidden_edges = [e for c in hidden for e in c.Edges()] + + return visible_edges, hidden_edges diff --git a/tests/test_projection.py b/tests/test_projection.py new file mode 100644 index 000000000..b6298c27e --- /dev/null +++ b/tests/test_projection.py @@ -0,0 +1,45 @@ +import cadquery as cq +from cadquery.occ_impl.exporters.svg import exportSVG +from cadquery.occ_impl.shapes import Compound, projectToViewpoint +from cadquery import Workplane + +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(): + visible_edges, hidden_edges = projectToViewpoint(my_part.val(), direction) + cq.exporters.exportDXF( + Compound.makeCompound(visible_edges), f"{prefix}{name}.dxf", doc_units=6, + ) + + +def exportSVG3rdAngleProjection(my_part, prefix: str) -> None: + for name, direction in viewpoint.items(): + exportSVG( + my_part, f"{prefix}{name}.svg", opts={"projectionDir": direction,}, + ) + + +if __name__ == "__main__": + # Build the part + width = 10 + depth = 10 + height = 10 + + # !!! Test projection of fillets to arc segments in DXF. !!! + baseplate = cq.Workplane("XY").box(width, depth, height).edges("|Z").fillet(2.0) # + + hole_dia = 3.0 + + # !!! Test projection of countersunk to arc segments in DXF. !!! + 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, "") From 7e0a42f4b27f6093e6e5381bce5e6231d9d6b817 Mon Sep 17 00:00:00 2001 From: AU Date: Tue, 7 Apr 2026 16:49:36 +0200 Subject: [PATCH 02/11] do not type check getSVG --- cadquery/occ_impl/exporters/svg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/occ_impl/exporters/svg.py b/cadquery/occ_impl/exporters/svg.py index 2a04290e0..cf0661414 100644 --- a/cadquery/occ_impl/exporters/svg.py +++ b/cadquery/occ_impl/exporters/svg.py @@ -122,7 +122,7 @@ def getPaths( return (hiddenPaths, visiblePaths) -def getSVG(shape: Shape, opts=None): +def getSVG(shape, opts=None): """ Export a shape to SVG text. From d8fb5493f4368831d10cafde9ff7180401775ee1 Mon Sep 17 00:00:00 2001 From: AU Date: Wed, 8 Apr 2026 07:40:08 +0200 Subject: [PATCH 03/11] Another quick mypy fix --- cadquery/occ_impl/shapes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index bb1ceba81..4eae7fc48 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -6627,11 +6627,11 @@ def projectToViewpoint( BRepLib.BuildCurves3d_s(el, TOLERANCE) # convert to native CQ objects - visible = [Shape.cast(s) for s in visible] # s is a TopoDS_Shape (Compound) - hidden = [Shape.cast(s) for s in hidden] + visible_ = [Shape.cast(s) for s in visible] # s is a TopoDS_Shape (Compound) + hidden_ = [Shape.cast(s) for s in hidden] # Extract edges - visible_edges = [e for c in visible for e in c.Edges()] - hidden_edges = [e for c in hidden for e in c.Edges()] + visible_edges = [e for c in visible_ for e in c.Edges()] + hidden_edges = [e for c in hidden_ for e in c.Edges()] return visible_edges, hidden_edges From e78011959277e8554821490d79fceb26e127b1e0 Mon Sep 17 00:00:00 2001 From: AU Date: Wed, 8 Apr 2026 18:10:57 +0200 Subject: [PATCH 04/11] Fix unrelated upstream mypy issue --- cadquery/occ_impl/shapes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 4eae7fc48..d2edfa294 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -619,7 +619,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()] From 98d8f1afbeb58cd81b57e6572100b0135afb4eed Mon Sep 17 00:00:00 2001 From: AU Date: Tue, 30 Jun 2026 18:05:55 +0200 Subject: [PATCH 05/11] Remove Optional --- cadquery/occ_impl/shapes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index d2edfa294..49c83f596 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -6578,7 +6578,7 @@ def closest(s1: Shape, s2: Shape) -> Tuple[Vector, Vector]: def projectToViewpoint( - shape, projectionDir: tuple[float, float, float], focus: Optional[float] = None, + shape, projectionDir: tuple[float, float, float], focus: float|None = None, ) -> tuple[list[Edge], list[Edge]]: hlr = HLRBRep_Algo() hlr.Add(shape.wrapped) From 403b694494c26976bb57517f339ea3372c47e3d8 Mon Sep 17 00:00:00 2001 From: AU Date: Tue, 30 Jun 2026 19:23:52 +0200 Subject: [PATCH 06/11] Black fix --- cadquery/occ_impl/shapes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 49c83f596..5d6124fe1 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -6578,7 +6578,7 @@ def closest(s1: Shape, s2: Shape) -> Tuple[Vector, Vector]: def projectToViewpoint( - shape, projectionDir: tuple[float, float, float], focus: float|None = None, + shape, projectionDir: tuple[float, float, float], focus: float | None = None, ) -> tuple[list[Edge], list[Edge]]: hlr = HLRBRep_Algo() hlr.Add(shape.wrapped) From 6f597fe1f6fb0c2851e9b0b6af2245adcf11105e Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:19:08 +0200 Subject: [PATCH 07/11] Refactored the hlr projection function --- cadquery/occ_impl/exporters/svg.py | 4 ++-- cadquery/occ_impl/shapes.py | 20 ++++++++++---------- tests/test_projection.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cadquery/occ_impl/exporters/svg.py b/cadquery/occ_impl/exporters/svg.py index cf0661414..b742a6b87 100644 --- a/cadquery/occ_impl/exporters/svg.py +++ b/cadquery/occ_impl/exporters/svg.py @@ -2,7 +2,7 @@ from ..shapes import Shape, Compound, Edge from ..geom import BoundBox -from ..shapes import projectToViewpoint +from ..shapes import hlr from OCP.GCPnts import GCPnts_QuasiUniformDeflection @@ -183,7 +183,7 @@ def getSVG(shape, opts=None): showHidden = bool(d["showHidden"]) focus = float(d["focus"]) if d.get("focus") else None - visibleEdges, hiddenEdges = projectToViewpoint(shape, projectionDir, focus) + visibleEdges, hiddenEdges = hlr(shape, (0, 0, 0), projectionDir, focus) hiddenPaths, visiblePaths = getPaths(visibleEdges, hiddenEdges) # get bounding box -- these are all in 2D space diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index e0db52985..0e3a396bf 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -7976,13 +7976,17 @@ def closest(s1: Shape, s2: Shape) -> tuple[Vector, Vector]: return Vector(ext.PointOnShape1(1)), Vector(ext.PointOnShape2(1)) -def projectToViewpoint( - shape, projectionDir: tuple[float, float, float], focus: float | None = None, -) -> tuple[list[Edge], list[Edge]]: +def hlr( + shape, pnt: VectorLike, dir: VectorLike, focus: float | None = None, +) -> 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(gp_Pnt(), gp_Dir(*projectionDir)) + coordinate_system = gp_Ax2(Vector(pnt).toPnt(), Vector(dir).toDir()) if focus is not None: projector = HLRAlgo_Projector(coordinate_system, focus) @@ -8025,12 +8029,8 @@ def projectToViewpoint( for el in hidden: BRepLib.BuildCurves3d_s(el, TOLERANCE) - # convert to native CQ objects - visible_ = [Shape.cast(s) for s in visible] # s is a TopoDS_Shape (Compound) - hidden_ = [Shape.cast(s) for s in hidden] - # Extract edges - visible_edges = [e for c in visible_ for e in c.Edges()] - hidden_edges = [e for c in hidden_ for e in c.Edges()] + visible_edges = compound(_compound_or_shape(visible).Edges()) + hidden_edges = compound(_compound_or_shape(hidden).Edges()) return visible_edges, hidden_edges diff --git a/tests/test_projection.py b/tests/test_projection.py index b6298c27e..bc563d191 100644 --- a/tests/test_projection.py +++ b/tests/test_projection.py @@ -1,6 +1,6 @@ import cadquery as cq from cadquery.occ_impl.exporters.svg import exportSVG -from cadquery.occ_impl.shapes import Compound, projectToViewpoint +from cadquery.occ_impl.shapes import Compound, hlr from cadquery import Workplane viewpoint = { @@ -13,7 +13,7 @@ def exportDXF3rdAngleProjection(my_part: Workplane, prefix: str) -> None: for name, direction in viewpoint.items(): - visible_edges, hidden_edges = projectToViewpoint(my_part.val(), direction) + visible_edges, hidden_edges = hlr(my_part.val(), direction) cq.exporters.exportDXF( Compound.makeCompound(visible_edges), f"{prefix}{name}.dxf", doc_units=6, ) From af418fe1f4db6b38f2b3f845b9a3acd62264c09c Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:42:45 +0200 Subject: [PATCH 08/11] Move to examples --- tests/test_projection.py => examples/Ex102_HLR.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/test_projection.py => examples/Ex102_HLR.py (100%) diff --git a/tests/test_projection.py b/examples/Ex102_HLR.py similarity index 100% rename from tests/test_projection.py rename to examples/Ex102_HLR.py From d824877a5c4c471935eef52a1ab97412defd5d80 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:19:39 +0200 Subject: [PATCH 09/11] Add DXF export with projection and HLR --- cadquery/occ_impl/exporters/__init__.py | 9 ++--- cadquery/occ_impl/exporters/dxf.py | 41 ++++++++++++++++++++-- examples/Ex102_DXF_with_HLR.py | 36 ++++++++++++++++++++ examples/Ex102_HLR.py | 45 ------------------------- 4 files changed, 76 insertions(+), 55 deletions(-) create mode 100644 examples/Ex102_DXF_with_HLR.py delete mode 100644 examples/Ex102_HLR.py diff --git a/cadquery/occ_impl/exporters/__init__.py b/cadquery/occ_impl/exporters/__init__.py index d27bc7a57..266dd4ac9 100644 --- a/cadquery/occ_impl/exporters/__init__.py +++ b/cadquery/occ_impl/exporters/__init__.py @@ -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 diff --git a/cadquery/occ_impl/exporters/dxf.py b/cadquery/occ_impl/exporters/dxf.py index 8180f274f..0cc69f78b 100644 --- a/cadquery/occ_impl/exporters/dxf.py +++ b/cadquery/occ_impl/exporters/dxf.py @@ -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"] @@ -366,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, @@ -395,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 ` (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) diff --git a/examples/Ex102_DXF_with_HLR.py b/examples/Ex102_DXF_with_HLR.py new file mode 100644 index 000000000..1fc21f5bd --- /dev/null +++ b/examples/Ex102_DXF_with_HLR.py @@ -0,0 +1,36 @@ +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, "") diff --git a/examples/Ex102_HLR.py b/examples/Ex102_HLR.py deleted file mode 100644 index bc563d191..000000000 --- a/examples/Ex102_HLR.py +++ /dev/null @@ -1,45 +0,0 @@ -import cadquery as cq -from cadquery.occ_impl.exporters.svg import exportSVG -from cadquery.occ_impl.shapes import Compound, hlr -from cadquery import Workplane - -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(): - visible_edges, hidden_edges = hlr(my_part.val(), direction) - cq.exporters.exportDXF( - Compound.makeCompound(visible_edges), f"{prefix}{name}.dxf", doc_units=6, - ) - - -def exportSVG3rdAngleProjection(my_part, prefix: str) -> None: - for name, direction in viewpoint.items(): - exportSVG( - my_part, f"{prefix}{name}.svg", opts={"projectionDir": direction,}, - ) - - -if __name__ == "__main__": - # Build the part - width = 10 - depth = 10 - height = 10 - - # !!! Test projection of fillets to arc segments in DXF. !!! - baseplate = cq.Workplane("XY").box(width, depth, height).edges("|Z").fillet(2.0) # - - hole_dia = 3.0 - - # !!! Test projection of countersunk to arc segments in DXF. !!! - 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, "") From 6e798eb5329517e233b16092c0a4e460d0b59ae4 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:55:50 +0200 Subject: [PATCH 10/11] Export Shape too --- examples/Ex102_DXF_with_HLR.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/Ex102_DXF_with_HLR.py b/examples/Ex102_DXF_with_HLR.py index 1fc21f5bd..ee893f213 100644 --- a/examples/Ex102_DXF_with_HLR.py +++ b/examples/Ex102_DXF_with_HLR.py @@ -34,3 +34,4 @@ def exportSVG3rdAngleProjection(my_part, prefix: str) -> None: # Expected DXF output to be identical to SVG output exportSVG3rdAngleProjection(drilled, "") exportDXF3rdAngleProjection(drilled, "") +exportDXF3rdAngleProjection(drilled.val(), "shape_") From 1263b15b9c412a5f4d43f1d8ea4afd3641d3940c Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Wed, 8 Jul 2026 08:21:59 +0200 Subject: [PATCH 11/11] Make hlr public --- cadquery/func.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cadquery/func.py b/cadquery/func.py index 949bc809b..b2c86bfe0 100644 --- a/cadquery/func.py +++ b/cadquery/func.py @@ -59,6 +59,7 @@ chamfer2D, draft, History, + hlr, ) __all__ = [ @@ -124,4 +125,5 @@ "fillet2D", "draft", "History", + "hlr", ]