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 cadquery/occ_impl/exporters/dxf.py
Original file line number Diff line number Diff line change
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
85 changes: 16 additions & 69 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 ..projection 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
Expand Down Expand Up @@ -106,26 +103,24 @@ 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)


def getSVG(shape, opts=None):
def getSVG(shape: Shape, opts=None):
"""
Export a shape to SVG text.

Expand Down Expand Up @@ -171,10 +166,10 @@ def getSVG(shape, opts=None):

# Handle the case where the height or width are None
width = d["width"]
if width != None:
if width is not None:
width = float(d["width"])
height = d["height"]
if d["height"] != None:
if d["height"] is not None:
height = float(d["height"])
marginLeft = float(d["marginLeft"])
marginTop = float(d["marginTop"])
Expand All @@ -184,66 +179,18 @@ def getSVG(shape, opts=None):
strokeColor = tuple(d["strokeColor"])
hiddenColor = tuple(d["hiddenColor"])
showHidden = bool(d["showHidden"])
focus = float(d["focus"]) if d.get("focus") else None
focus = float(d["focus"]) if d.get("focus") is not None 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
Expand Down
72 changes: 72 additions & 0 deletions cadquery/occ_impl/projection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from typing import Optional

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

from .shapes import TOLERANCE, Edge, Shape

DISCRETIZATION_TOLERANCE = 1e-3


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)
Comment on lines 18 to 62
Copy link
Author

Choose a reason for hiding this comment

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

Help wanted to reduce duplicated code.. This part is 90% similar to the code in exporters/svg.py, so it would be better if we can consolidate it.

Also, should I create a new file occ_impl/projection.py? Or, should I move it to occ_impl/geom.py?


# 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
61 changes: 61 additions & 0 deletions tests/test_projection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import cadquery as cq
from cadquery.occ_impl.projection import projectToViewpoint
from cadquery.occ_impl.exporters.svg import exportSVG
from cadquery.occ_impl.shapes import Compound
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,
)
Comment on lines 18 to 22
Copy link
Author

Choose a reason for hiding this comment

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

Dimensioning in Librecad:

Image



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, "")
Comment on lines +59 to +61
Copy link
Author

Choose a reason for hiding this comment

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

Help wanted to move this example code to examples/ folder.