Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Brep to supported importShape functions #1467

Merged
merged 3 commits into from
Jan 18, 2024
Merged
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
22 changes: 21 additions & 1 deletion cadquery/occ_impl/importers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
class ImportTypes:
STEP = "STEP"
DXF = "DXF"
BREP = "BREP"


class UNITS:
Expand All @@ -22,7 +23,7 @@ class UNITS:


def importShape(
importType: Literal["STEP", "DXF"], fileName: str, *args, **kwargs
importType: Literal["STEP", "DXF", "BREP"], fileName: str, *args, **kwargs
) -> "cq.Workplane":
"""
Imports a file based on the type (STEP, STL, etc)
Expand All @@ -36,10 +37,29 @@ def importShape(
return importStep(fileName)
elif importType == ImportTypes.DXF:
return importDXF(fileName, *args, **kwargs)
elif importType == ImportTypes.BREP:
return importBrep(fileName)
else:
raise RuntimeError("Unsupported import type: {!r}".format(importType))


def importBrep(fileName: str) -> "cq.Workplane":
"""
Loads the BREP file as a single shape into a cadquery Workplane.

:param fileName: The path and name of the BREP file to be imported

"""
shape = Shape.importBrep(fileName)

# We know a single shape is returned. Sending it as a list prevents
# newObject from decomposing the part into its constituent parts. If the
# shape is a compound, it will be stored as a compound on the workplane. In
# some cases it may be desirable for the compound to be broken into its
# constituent solids. To do this, use list(shape) or shape.Solids().
return cq.Workplane("XY").newObject([shape])


# Loads a STEP file into a CQ.Workplane object
def importStep(fileName: str) -> "cq.Workplane":
"""
Expand Down
137 changes: 111 additions & 26 deletions tests/test_importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import tempfile
import os

from cadquery import importers, Workplane
from cadquery import importers, Workplane, Compound
from tests import BaseTest
from pytest import approx, raises

Expand All @@ -19,50 +19,135 @@
class TestImporters(BaseTest):
def importBox(self, importType, fileName):
"""
Exports a simple box to a STEP file and then imports it again
Exports a simple box and then imports it again
:param importType: The type of file we're importing (STEP, STL, etc)
:param fileName: The path and name of the file to write to
"""
# We're importing a STEP file
# We first need to build a simple shape to export
shape = Workplane("XY").box(1, 2, 3).val()

if importType == importers.ImportTypes.STEP:
# We first need to build a simple shape to export
shape = Workplane("XY").box(1, 2, 3).val()
# Export the shape to a temporary file
shape.exportStep(fileName)
elif importType == importers.ImportTypes.BREP:
shape.exportBrep(fileName)

# Reimport the shape from the new file
importedShape = importers.importShape(importType, fileName)

# Check to make sure we got a single solid back.
self.assertTrue(importedShape.val().isValid())
self.assertEqual(importedShape.val().ShapeType(), "Solid")
self.assertEqual(len(importedShape.objects), 1)

# Check the number of faces and vertices per face to make sure we have a
# box shape.
self.assertNFacesAndNVertices(importedShape, (1, 1, 1), (4, 4, 4))
# Check that the volume is correct.
self.assertAlmostEqual(importedShape.findSolid().Volume(), 6)

def importCompound(self, importType, fileName):
"""
Exports a "+" shaped compound box and then imports it again.
:param importType: The type of file we're importing (STEP, STL, etc)
:param fileName: The path and name of the file to write to
"""
# We first need to build a simple shape to export
b1 = Workplane("XY").box(1, 2, 3).val()
b2 = Workplane("XZ").box(1, 2, 3).val()
shape = Compound.makeCompound([b1, b2])

if importType == importers.ImportTypes.STEP:
# Export the shape to a temporary file
shape.exportStep(fileName)
elif importType == importers.ImportTypes.BREP:
shape.exportBrep(fileName)

# Reimport the shape from the new file
importedShape = importers.importShape(importType, fileName)

# Check to make sure we got the shapes we expected.
self.assertTrue(importedShape.val().isValid())
self.assertEqual(importedShape.val().ShapeType(), "Compound")
self.assertEqual(len(importedShape.objects), 1)

# Check the number of faces and vertices per face to make sure we have
# two boxes.
self.assertNFacesAndNVertices(importedShape, (2, 2, 2), (8, 8, 8))

# Check that the volume is the independent sum of the two boxes' 6mm^2
# volumes.
self.assertAlmostEqual(importedShape.findSolid().Volume(), 12)

# Join the boxes together and ensure that they are geometrically where
# we expected them to be. This should be a workplane containing a
# compound composed of a single Solid.
fusedShape = Workplane("XY").newObject(importedShape.val().fuse())

# Reimport the shape from the new STEP file
importedShape = importers.importShape(importType, fileName)

# Check to make sure we got a solid back
self.assertTrue(importedShape.val().ShapeType() == "Solid")

# Check the number of faces and vertices per face to make sure we have a box shape
self.assertTrue(
importedShape.faces("+X").size() == 1
and importedShape.faces("+X").vertices().size() == 4
)
self.assertTrue(
importedShape.faces("+Y").size() == 1
and importedShape.faces("+Y").vertices().size() == 4
)
self.assertTrue(
importedShape.faces("+Z").size() == 1
and importedShape.faces("+Z").vertices().size() == 4
)
# Check to make sure we got a valid shape
self.assertTrue(fusedShape.val().isValid())

# Check the number of faces and vertices per face to make sure we have
# two boxes.
self.assertNFacesAndNVertices(fusedShape, (5, 3, 3), (12, 12, 12))

# Check that the volume accounts for the overlap of the two shapes.
self.assertAlmostEqual(fusedShape.findSolid().Volume(), 8)

def assertNFacesAndNVertices(self, workplane, nFacesXYZ, nVerticesXYZ):
"""
Checks that the workplane has the number of faces and vertices expected
in X, Y, and Z.
:param workplane: The workplane to assess.
:param nFacesXYZ: The number of faces expected in +X, +Y, and +Z planes.
:param nVerticesXYZ: The number of vertices expected in +X, +Y, and +Z planes.
"""
nFacesX, nFacesY, nFacesZ = nFacesXYZ
nVerticesX, nVerticesY, nVerticesZ = nVerticesXYZ

self.assertEqual(workplane.faces("+X").size(), nFacesX)
self.assertEqual(workplane.faces("+X").vertices().size(), nVerticesX)

self.assertEqual(workplane.faces("+Y").size(), nFacesY)
self.assertEqual(workplane.faces("+Y").vertices().size(), nVerticesY)

self.assertEqual(workplane.faces("+Z").size(), nFacesZ)
self.assertEqual(workplane.faces("+Z").vertices().size(), nVerticesZ)

def testInvalidImportTypeRaisesRuntimeError(self):
fileName = os.path.join(OUTDIR, "tempSTEP.step")
shape = Workplane("XY").box(1, 2, 3).val()
shape.exportStep(fileName)
self.assertRaises(RuntimeError, importers.importShape, "INVALID", fileName)

def testBREP(self):
"""
Test BREP file import.
"""
self.importBox(
importers.ImportTypes.BREP, os.path.join(OUTDIR, "tempBREP.brep")
)
self.importCompound(
importers.ImportTypes.BREP, os.path.join(OUTDIR, "tempBREP.brep")
)

def testSTEP(self):
"""
Tests STEP file import
"""
self.importBox(importers.ImportTypes.STEP, OUTDIR + "/tempSTEP.step")
self.importBox(
importers.ImportTypes.STEP, os.path.join(OUTDIR, "tempSTEP.step")
)
self.importCompound(
importers.ImportTypes.STEP, os.path.join(OUTDIR, "tempSTEP.step")
)

def testInvalidSTEP(self):
"""
Attempting to load an invalid STEP file should throw an exception, but
not segfault.
"""
tmpfile = OUTDIR + "/badSTEP.step"
tmpfile = os.path.join(OUTDIR, "badSTEP.step")
with open(tmpfile, "w") as f:
f.write("invalid STEP file")
with self.assertRaises(ValueError):
Expand Down