Skip to content

Commit 2629bf9

Browse files
Text fixes and some shape/free func additions (#1700)
* Fix regression [single letters] * Text, offset, solid and exportBin changes - text on path - text on path/surface - symmetric offset - import/export binary brep * Binary BREP in exporters/importers * Extra tests * Fix tests * Add bin imp/exp test * Blacken * Remove one overload * Fix dispatch * Fix test * Extra vis options * Different error * Fix/more tests * Better coverage * More tests * Docs text placeholder * Pseudo-infinite plane * Add an example for text * Black fix * Apply suggestions from code review Co-authored-by: Jeremy Wright <[email protected]> * Typo fixes --------- Co-authored-by: Jeremy Wright <[email protected]>
1 parent a175cb8 commit 2629bf9

File tree

10 files changed

+324
-37
lines changed

10 files changed

+324
-37
lines changed

cadquery/occ_impl/exporters/__init__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ class ExportTypes:
2929
VTP = "VTP"
3030
THREEMF = "3MF"
3131
BREP = "BREP"
32+
BIN = "BIN"
3233

3334

3435
ExportLiterals = Literal[
35-
"STL", "STEP", "AMF", "SVG", "TJS", "DXF", "VRML", "VTP", "3MF", "BREP"
36+
"STL", "STEP", "AMF", "SVG", "TJS", "DXF", "VRML", "VTP", "3MF", "BREP", "BIN"
3637
]
3738

3839

@@ -128,6 +129,9 @@ def export(
128129
elif exportType == ExportTypes.BREP:
129130
shape.exportBrep(fname)
130131

132+
elif exportType == ExportTypes.BIN:
133+
shape.exportBin(fname)
134+
131135
else:
132136
raise ValueError("Unknown export type")
133137

cadquery/occ_impl/importers/__init__.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class ImportTypes:
1515
STEP = "STEP"
1616
DXF = "DXF"
1717
BREP = "BREP"
18+
BIN = "BIN"
1819

1920

2021
class UNITS:
@@ -23,7 +24,7 @@ class UNITS:
2324

2425

2526
def importShape(
26-
importType: Literal["STEP", "DXF", "BREP"], fileName: str, *args, **kwargs
27+
importType: Literal["STEP", "DXF", "BREP", "BIN"], fileName: str, *args, **kwargs
2728
) -> "cq.Workplane":
2829
"""
2930
Imports a file based on the type (STEP, STL, etc)
@@ -39,6 +40,8 @@ def importShape(
3940
return importDXF(fileName, *args, **kwargs)
4041
elif importType == ImportTypes.BREP:
4142
return importBrep(fileName)
43+
elif importType == ImportTypes.BIN:
44+
return importBin(fileName)
4245
else:
4346
raise RuntimeError("Unsupported import type: {!r}".format(importType))
4447

@@ -60,6 +63,18 @@ def importBrep(fileName: str) -> "cq.Workplane":
6063
return cq.Workplane("XY").newObject([shape])
6164

6265

66+
def importBin(fileName: str) -> "cq.Workplane":
67+
"""
68+
Loads the binary BREP file as a single shape into a cadquery Workplane.
69+
70+
:param fileName: The path and name of the BREP file to be imported
71+
72+
"""
73+
shape = Shape.importBin(fileName)
74+
75+
return cq.Workplane("XY").newObject([shape])
76+
77+
6378
# Loads a STEP file into a CQ.Workplane object
6479
def importStep(fileName: str) -> "cq.Workplane":
6580
"""

cadquery/occ_impl/shapes.py

+161-26
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@
279279

280280
from OCP.LProp3d import LProp3d_CLProps
281281

282+
from OCP.BinTools import BinTools
283+
282284
from math import pi, sqrt, inf, radians, cos
283285

284286
import warnings
@@ -520,6 +522,26 @@ def importBrep(cls, f: Union[str, BytesIO]) -> "Shape":
520522

521523
return cls.cast(s)
522524

525+
def exportBin(self, f: Union[str, BytesIO]) -> bool:
526+
"""
527+
Export this shape to a binary BREP file.
528+
"""
529+
530+
rv = BinTools.Write_s(self.wrapped, f)
531+
532+
return True if rv is None else rv
533+
534+
@classmethod
535+
def importBin(cls, f: Union[str, BytesIO]) -> "Shape":
536+
"""
537+
Import shape from a binary BREP file.
538+
"""
539+
s = TopoDS_Shape()
540+
541+
BinTools.Read_s(s, f)
542+
543+
return cls.cast(s)
544+
523545
def geomType(self) -> Geoms:
524546
"""
525547
Gets the underlying geometry type.
@@ -4528,6 +4550,7 @@ def _normalize(s: Shape) -> Shape:
45284550
"""
45294551
Apply some normalizations:
45304552
- Shell with only one Face -> Face.
4553+
- Compound with only one element -> element.
45314554
"""
45324555

45334556
t = s.ShapeType()
@@ -4724,17 +4747,26 @@ def shell(s: Sequence[Shape], tol: float = 1e-6) -> Shape:
47244747

47254748

47264749
@multimethod
4727-
def solid(*s: Shape, tol: float = 1e-6) -> Shape:
4750+
def solid(s1: Shape, *sn: Shape, tol: float = 1e-6) -> Shape:
47284751
"""
4729-
Build solid from faces.
4752+
Build solid from faces or shells.
47304753
"""
47314754

47324755
builder = ShapeFix_Solid()
47334756

4734-
faces = [f for el in s for f in _get(el, "Face")]
4735-
rv = builder.SolidFromShell(shell(*faces, tol=tol).wrapped)
4757+
# get both Shells and Faces
4758+
s = [s1, *sn]
4759+
shells_faces = [f for el in s for f in _get(el, ("Shell", "Face"))]
47364760

4737-
return _compound_or_shape(rv)
4761+
# if no shells are present, use faces to construct them
4762+
shells = [el.wrapped for el in shells_faces if el.ShapeType() == "Shell"]
4763+
if not shells:
4764+
faces = [el for el in shells_faces]
4765+
shells = [shell(*faces, tol=tol).wrapped]
4766+
4767+
rvs = [builder.SolidFromShell(sh) for sh in shells]
4768+
4769+
return _compound_or_shape(rvs)
47384770

47394771

47404772
@solid.register
@@ -4922,9 +4954,10 @@ def ellipse(r1: float, r2: float) -> Shape:
49224954
)
49234955

49244956

4925-
def plane(w: float, l: float) -> Shape:
4957+
@multimethod
4958+
def plane(w: Real, l: Real) -> Shape:
49264959
"""
4927-
Construct a planar face.
4960+
Construct a finite planar face.
49284961
"""
49294962

49304963
pln_geom = gp_Pln(Vector(0, 0, 0).toPnt(), Vector(0, 0, 1).toDir())
@@ -4934,6 +4967,24 @@ def plane(w: float, l: float) -> Shape:
49344967
)
49354968

49364969

4970+
@plane.register
4971+
def plane() -> Shape:
4972+
"""
4973+
Construct an infinite planar face.
4974+
4975+
This is a crude approximation. Truly infinite faces in OCCT do not work as
4976+
expected in all contexts.
4977+
"""
4978+
4979+
INF = 1e60
4980+
4981+
pln_geom = gp_Pln(Vector(0, 0, 0).toPnt(), Vector(0, 0, 1).toDir())
4982+
4983+
return _compound_or_shape(
4984+
BRepBuilderAPI_MakeFace(pln_geom, -INF, INF, -INF, INF).Face()
4985+
)
4986+
4987+
49374988
def box(w: float, l: float, h: float) -> Shape:
49384989
"""
49394990
Construct a solid box.
@@ -5012,9 +5063,10 @@ def cone(d: Real, h: Real) -> Shape:
50125063
return cone(d, 0, h)
50135064

50145065

5066+
@multimethod
50155067
def text(
50165068
txt: str,
5017-
size: float,
5069+
size: Real,
50185070
font: str = "Arial",
50195071
path: Optional[str] = None,
50205072
kind: Literal["regular", "bold", "italic"] = "regular",
@@ -5065,7 +5117,67 @@ def text(
50655117
font_i, NCollection_Utf8String(txt), theHAlign=theHAlign, theVAlign=theVAlign
50665118
)
50675119

5068-
return clean(_compound_or_shape(rv).faces().fuse())
5120+
return clean(compound(_compound_or_shape(rv).faces()).fuse())
5121+
5122+
5123+
@text.register
5124+
def text(
5125+
txt: str,
5126+
size: Real,
5127+
spine: Shape,
5128+
planar: bool = False,
5129+
font: str = "Arial",
5130+
path: Optional[str] = None,
5131+
kind: Literal["regular", "bold", "italic"] = "regular",
5132+
halign: Literal["center", "left", "right"] = "center",
5133+
valign: Literal["center", "top", "bottom"] = "center",
5134+
) -> Shape:
5135+
"""
5136+
Create a text on a spine.
5137+
"""
5138+
5139+
spine = _get_one_wire(spine)
5140+
L = spine.Length()
5141+
5142+
rv = []
5143+
for el in text(txt, size, font, path, kind, halign, valign):
5144+
pos = el.BoundingBox().center.x
5145+
5146+
# position
5147+
rv.append(
5148+
el.moved(-pos)
5149+
.moved(rx=-90 if planar else 0, ry=-90)
5150+
.moved(spine.locationAt(pos / L))
5151+
)
5152+
5153+
return _normalize(compound(rv))
5154+
5155+
5156+
@text.register
5157+
def text(
5158+
txt: str,
5159+
size: Real,
5160+
spine: Shape,
5161+
base: Shape,
5162+
font: str = "Arial",
5163+
path: Optional[str] = None,
5164+
kind: Literal["regular", "bold", "italic"] = "regular",
5165+
halign: Literal["center", "left", "right"] = "center",
5166+
valign: Literal["center", "top", "bottom"] = "center",
5167+
) -> Shape:
5168+
"""
5169+
Create a text on a spine and a base surface.
5170+
"""
5171+
5172+
base = _get_one(base, "Face")
5173+
5174+
tmp = text(txt, size, spine, False, font, path, kind, halign, valign)
5175+
5176+
rv = []
5177+
for f in tmp.faces():
5178+
rv.append(f.project(base, f.normalAt()))
5179+
5180+
return _normalize(compound(rv))
50695181

50705182

50715183
#%% ops
@@ -5266,33 +5378,56 @@ def revolve(s: Shape, p: VectorLike, d: VectorLike, a: float = 360):
52665378
return _compound_or_shape(results)
52675379

52685380

5269-
def offset(s: Shape, t: float, cap=True, tol: float = 1e-6) -> Shape:
5381+
def offset(
5382+
s: Shape, t: float, cap=True, both: bool = False, tol: float = 1e-6
5383+
) -> Shape:
52705384
"""
52715385
Offset or thicken faces or shells.
52725386
"""
52735387

5274-
builder = BRepOffset_MakeOffset()
5388+
def _offset(t):
52755389

5276-
results = []
5390+
results = []
52775391

5278-
for el in _get(s, ("Face", "Shell")):
5392+
for el in _get(s, ("Face", "Shell")):
52795393

5280-
builder.Initialize(
5281-
el.wrapped,
5282-
t,
5283-
tol,
5284-
BRepOffset_Mode.BRepOffset_Skin,
5285-
False,
5286-
False,
5287-
GeomAbs_Intersection,
5288-
cap,
5289-
)
5394+
builder = BRepOffset_MakeOffset()
52905395

5291-
builder.MakeOffsetShape()
5396+
builder.Initialize(
5397+
el.wrapped,
5398+
t,
5399+
tol,
5400+
BRepOffset_Mode.BRepOffset_Skin,
5401+
False,
5402+
False,
5403+
GeomAbs_Intersection,
5404+
cap,
5405+
)
52925406

5293-
results.append(builder.Shape())
5407+
builder.MakeOffsetShape()
52945408

5295-
return _compound_or_shape(results)
5409+
results.append(builder.Shape())
5410+
5411+
return results
5412+
5413+
if both:
5414+
results_pos = _offset(t)
5415+
results_neg = _offset(-t)
5416+
5417+
results_both = [
5418+
Shape(el1) + Shape(el2) for el1, el2 in zip(results_pos, results_neg)
5419+
]
5420+
5421+
if len(results_both) == 1:
5422+
rv = results_both[0]
5423+
else:
5424+
rv = Compound.makeCompound(results_both)
5425+
5426+
else:
5427+
results = _offset(t)
5428+
rv = _compound_or_shape(results)
5429+
5430+
return rv
52965431

52975432

52985433
@multimethod

cadquery/vis.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,14 @@ def _to_vtk_axs(locs: List[Location], scale: float = 0.1) -> vtkActor:
129129
return rv
130130

131131

132-
def show(*objs: Showable, scale: float = 0.2, alpha: float = 1, **kwrags: Any):
132+
def show(
133+
*objs: Showable,
134+
scale: float = 0.2,
135+
alpha: float = 1,
136+
tolerance: float = 1e-3,
137+
edges: bool = False,
138+
**kwrags: Any,
139+
):
133140
"""
134141
Show CQ objects using VTK.
135142
"""
@@ -145,10 +152,16 @@ def show(*objs: Showable, scale: float = 0.2, alpha: float = 1, **kwrags: Any):
145152
axs = _to_vtk_axs(locs, scale=scale)
146153

147154
# create a VTK window
148-
win = _vtkRenderWindow(assy)
155+
win = _vtkRenderWindow(assy, tolerance=tolerance)
149156

150157
win.SetWindowName("CQ viewer")
151158

159+
# get renderer and actor
160+
if edges:
161+
ren = win.GetRenderers().GetFirstRenderer()
162+
for act in ren.GetActors():
163+
act.GetProperty().EdgeVisibilityOn()
164+
152165
# rendering related settings
153166
win.SetMultiSamples(16)
154167
vtkMapper.SetResolveCoincidentTopologyToPolygonOffset()

0 commit comments

Comments
 (0)