Skip to content

Commit c1813f3

Browse files
authored
Add Brep to supported importShape functions (#1467)
* Add Brep to supported importShape functions - Add a test for the BREP importer - Use os.path.join to ensure that the filepath is OS independent * Make BREP consistent with STEP and test Compounds Adding a case to test `type == "Compound"` revealed that that case was inconsistent with the STEP importer, so that case was removed. The compound test was obviously revealing, so it is retained for both STEP and BREP. * Add a test to verify that a RuntimeError is raised (should probably be a ValueError)
1 parent 688c4ac commit c1813f3

File tree

2 files changed

+132
-27
lines changed

2 files changed

+132
-27
lines changed

cadquery/occ_impl/importers/__init__.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
class ImportTypes:
1515
STEP = "STEP"
1616
DXF = "DXF"
17+
BREP = "BREP"
1718

1819

1920
class UNITS:
@@ -22,7 +23,7 @@ class UNITS:
2223

2324

2425
def importShape(
25-
importType: Literal["STEP", "DXF"], fileName: str, *args, **kwargs
26+
importType: Literal["STEP", "DXF", "BREP"], fileName: str, *args, **kwargs
2627
) -> "cq.Workplane":
2728
"""
2829
Imports a file based on the type (STEP, STL, etc)
@@ -36,10 +37,29 @@ def importShape(
3637
return importStep(fileName)
3738
elif importType == ImportTypes.DXF:
3839
return importDXF(fileName, *args, **kwargs)
40+
elif importType == ImportTypes.BREP:
41+
return importBrep(fileName)
3942
else:
4043
raise RuntimeError("Unsupported import type: {!r}".format(importType))
4144

4245

46+
def importBrep(fileName: str) -> "cq.Workplane":
47+
"""
48+
Loads the BREP file as a single shape into a cadquery Workplane.
49+
50+
:param fileName: The path and name of the BREP file to be imported
51+
52+
"""
53+
shape = Shape.importBrep(fileName)
54+
55+
# We know a single shape is returned. Sending it as a list prevents
56+
# newObject from decomposing the part into its constituent parts. If the
57+
# shape is a compound, it will be stored as a compound on the workplane. In
58+
# some cases it may be desirable for the compound to be broken into its
59+
# constituent solids. To do this, use list(shape) or shape.Solids().
60+
return cq.Workplane("XY").newObject([shape])
61+
62+
4363
# Loads a STEP file into a CQ.Workplane object
4464
def importStep(fileName: str) -> "cq.Workplane":
4565
"""

tests/test_importers.py

Lines changed: 111 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import tempfile
66
import os
77

8-
from cadquery import importers, Workplane
8+
from cadquery import importers, Workplane, Compound
99
from tests import BaseTest
1010
from pytest import approx, raises
1111

@@ -19,50 +19,135 @@
1919
class TestImporters(BaseTest):
2020
def importBox(self, importType, fileName):
2121
"""
22-
Exports a simple box to a STEP file and then imports it again
22+
Exports a simple box and then imports it again
2323
:param importType: The type of file we're importing (STEP, STL, etc)
2424
:param fileName: The path and name of the file to write to
2525
"""
26-
# We're importing a STEP file
26+
# We first need to build a simple shape to export
27+
shape = Workplane("XY").box(1, 2, 3).val()
28+
2729
if importType == importers.ImportTypes.STEP:
28-
# We first need to build a simple shape to export
29-
shape = Workplane("XY").box(1, 2, 3).val()
30+
# Export the shape to a temporary file
31+
shape.exportStep(fileName)
32+
elif importType == importers.ImportTypes.BREP:
33+
shape.exportBrep(fileName)
34+
35+
# Reimport the shape from the new file
36+
importedShape = importers.importShape(importType, fileName)
37+
38+
# Check to make sure we got a single solid back.
39+
self.assertTrue(importedShape.val().isValid())
40+
self.assertEqual(importedShape.val().ShapeType(), "Solid")
41+
self.assertEqual(len(importedShape.objects), 1)
3042

43+
# Check the number of faces and vertices per face to make sure we have a
44+
# box shape.
45+
self.assertNFacesAndNVertices(importedShape, (1, 1, 1), (4, 4, 4))
46+
# Check that the volume is correct.
47+
self.assertAlmostEqual(importedShape.findSolid().Volume(), 6)
48+
49+
def importCompound(self, importType, fileName):
50+
"""
51+
Exports a "+" shaped compound box and then imports it again.
52+
:param importType: The type of file we're importing (STEP, STL, etc)
53+
:param fileName: The path and name of the file to write to
54+
"""
55+
# We first need to build a simple shape to export
56+
b1 = Workplane("XY").box(1, 2, 3).val()
57+
b2 = Workplane("XZ").box(1, 2, 3).val()
58+
shape = Compound.makeCompound([b1, b2])
59+
60+
if importType == importers.ImportTypes.STEP:
3161
# Export the shape to a temporary file
3262
shape.exportStep(fileName)
63+
elif importType == importers.ImportTypes.BREP:
64+
shape.exportBrep(fileName)
65+
66+
# Reimport the shape from the new file
67+
importedShape = importers.importShape(importType, fileName)
68+
69+
# Check to make sure we got the shapes we expected.
70+
self.assertTrue(importedShape.val().isValid())
71+
self.assertEqual(importedShape.val().ShapeType(), "Compound")
72+
self.assertEqual(len(importedShape.objects), 1)
73+
74+
# Check the number of faces and vertices per face to make sure we have
75+
# two boxes.
76+
self.assertNFacesAndNVertices(importedShape, (2, 2, 2), (8, 8, 8))
77+
78+
# Check that the volume is the independent sum of the two boxes' 6mm^2
79+
# volumes.
80+
self.assertAlmostEqual(importedShape.findSolid().Volume(), 12)
81+
82+
# Join the boxes together and ensure that they are geometrically where
83+
# we expected them to be. This should be a workplane containing a
84+
# compound composed of a single Solid.
85+
fusedShape = Workplane("XY").newObject(importedShape.val().fuse())
3386

34-
# Reimport the shape from the new STEP file
35-
importedShape = importers.importShape(importType, fileName)
36-
37-
# Check to make sure we got a solid back
38-
self.assertTrue(importedShape.val().ShapeType() == "Solid")
39-
40-
# Check the number of faces and vertices per face to make sure we have a box shape
41-
self.assertTrue(
42-
importedShape.faces("+X").size() == 1
43-
and importedShape.faces("+X").vertices().size() == 4
44-
)
45-
self.assertTrue(
46-
importedShape.faces("+Y").size() == 1
47-
and importedShape.faces("+Y").vertices().size() == 4
48-
)
49-
self.assertTrue(
50-
importedShape.faces("+Z").size() == 1
51-
and importedShape.faces("+Z").vertices().size() == 4
52-
)
87+
# Check to make sure we got a valid shape
88+
self.assertTrue(fusedShape.val().isValid())
89+
90+
# Check the number of faces and vertices per face to make sure we have
91+
# two boxes.
92+
self.assertNFacesAndNVertices(fusedShape, (5, 3, 3), (12, 12, 12))
93+
94+
# Check that the volume accounts for the overlap of the two shapes.
95+
self.assertAlmostEqual(fusedShape.findSolid().Volume(), 8)
96+
97+
def assertNFacesAndNVertices(self, workplane, nFacesXYZ, nVerticesXYZ):
98+
"""
99+
Checks that the workplane has the number of faces and vertices expected
100+
in X, Y, and Z.
101+
:param workplane: The workplane to assess.
102+
:param nFacesXYZ: The number of faces expected in +X, +Y, and +Z planes.
103+
:param nVerticesXYZ: The number of vertices expected in +X, +Y, and +Z planes.
104+
"""
105+
nFacesX, nFacesY, nFacesZ = nFacesXYZ
106+
nVerticesX, nVerticesY, nVerticesZ = nVerticesXYZ
107+
108+
self.assertEqual(workplane.faces("+X").size(), nFacesX)
109+
self.assertEqual(workplane.faces("+X").vertices().size(), nVerticesX)
110+
111+
self.assertEqual(workplane.faces("+Y").size(), nFacesY)
112+
self.assertEqual(workplane.faces("+Y").vertices().size(), nVerticesY)
113+
114+
self.assertEqual(workplane.faces("+Z").size(), nFacesZ)
115+
self.assertEqual(workplane.faces("+Z").vertices().size(), nVerticesZ)
116+
117+
def testInvalidImportTypeRaisesRuntimeError(self):
118+
fileName = os.path.join(OUTDIR, "tempSTEP.step")
119+
shape = Workplane("XY").box(1, 2, 3).val()
120+
shape.exportStep(fileName)
121+
self.assertRaises(RuntimeError, importers.importShape, "INVALID", fileName)
122+
123+
def testBREP(self):
124+
"""
125+
Test BREP file import.
126+
"""
127+
self.importBox(
128+
importers.ImportTypes.BREP, os.path.join(OUTDIR, "tempBREP.brep")
129+
)
130+
self.importCompound(
131+
importers.ImportTypes.BREP, os.path.join(OUTDIR, "tempBREP.brep")
132+
)
53133

54134
def testSTEP(self):
55135
"""
56136
Tests STEP file import
57137
"""
58-
self.importBox(importers.ImportTypes.STEP, OUTDIR + "/tempSTEP.step")
138+
self.importBox(
139+
importers.ImportTypes.STEP, os.path.join(OUTDIR, "tempSTEP.step")
140+
)
141+
self.importCompound(
142+
importers.ImportTypes.STEP, os.path.join(OUTDIR, "tempSTEP.step")
143+
)
59144

60145
def testInvalidSTEP(self):
61146
"""
62147
Attempting to load an invalid STEP file should throw an exception, but
63148
not segfault.
64149
"""
65-
tmpfile = OUTDIR + "/badSTEP.step"
150+
tmpfile = os.path.join(OUTDIR, "badSTEP.step")
66151
with open(tmpfile, "w") as f:
67152
f.write("invalid STEP file")
68153
with self.assertRaises(ValueError):

0 commit comments

Comments
 (0)