Skip to content

Commit

Permalink
Merge pull request #1418 from CadQuery/ascii_exports
Browse files Browse the repository at this point in the history
Added argument handling for ASCII export of assemblies to GLTF and STL
  • Loading branch information
jmwright authored Oct 23, 2023
2 parents 4974df1 + 36bffa8 commit cce60ce
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ nested.wrl
nested.xml
nested.zip
nested.stl
nested.bin
nested_*.bin
nested_*.gltf
nested_*.glb
nested_*.stl
out1.3mf
out2.3mf
out3.3mf
Expand Down
21 changes: 14 additions & 7 deletions cadquery/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,12 @@ def save(
:param path: Path and filename for writing.
:param exportType: export format (default: None, results in format being inferred form the path)
:param tolerance: the deflection tolerance, in model units. Only used for GLTF, VRML. Default 0.1.
:param angularTolerance: the angular tolerance, in radians. Only used for GLTF, VRML. Default 0.1.
:param \**kwargs: Additional keyword arguments. Only used for STEP.
:param tolerance: the deflection tolerance, in model units. Only used for glTF, VRML. Default 0.1.
:param angularTolerance: the angular tolerance, in radians. Only used for glTF, VRML. Default 0.1.
:param \**kwargs: Additional keyword arguments. Only used for STEP, glTF and STL.
See :meth:`~cadquery.occ_impl.exporters.assembly.exportAssembly`.
:param ascii: STL only - Sets whether or not STL export should be text or binary
:type ascii: bool
"""

# Make sure the export mode setting is correct
Expand All @@ -476,7 +478,7 @@ def save(

if exportType is None:
t = path.split(".")[-1].upper()
if t in ("STEP", "XML", "VRML", "VTKJS", "GLTF", "STL"):
if t in ("STEP", "XML", "VRML", "VTKJS", "GLTF", "GLB", "STL"):
exportType = cast(ExportLiterals, t)
else:
raise ValueError("Unknown extension, specify export type explicitly")
Expand All @@ -487,12 +489,17 @@ def save(
exportCAF(self, path)
elif exportType == "VRML":
exportVRML(self, path, tolerance, angularTolerance)
elif exportType == "GLTF":
exportGLTF(self, path, True, tolerance, angularTolerance)
elif exportType == "GLTF" or exportType == "GLB":
exportGLTF(self, path, None, tolerance, angularTolerance)
elif exportType == "VTKJS":
exportVTKJS(self, path)
elif exportType == "STL":
self.toCompound().exportStl(path, tolerance, angularTolerance)
# Handle the ascii setting for STL export
export_ascii = False
if "ascii" in kwargs:
export_ascii = bool(kwargs.get("ascii"))

self.toCompound().exportStl(path, tolerance, angularTolerance, export_ascii)
else:
raise ValueError(f"Unknown format: {exportType}")

Expand Down
13 changes: 12 additions & 1 deletion cadquery/occ_impl/exporters/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from tempfile import TemporaryDirectory
from shutil import make_archive
from itertools import chain
from typing import Optional
from typing_extensions import Literal

from vtkmodules.vtkIOExport import vtkJSONSceneExporter, vtkVRMLExporter
Expand Down Expand Up @@ -185,14 +186,24 @@ def exportVRML(
def exportGLTF(
assy: AssemblyProtocol,
path: str,
binary: bool = True,
binary: Optional[bool] = None,
tolerance: float = 1e-3,
angularTolerance: float = 0.1,
):
"""
Export an assembly to a gltf file.
"""

# If the caller specified the binary option, respect it
if binary is None:
# Handle the binary option for GLTF export based on file extension
binary = True
path_parts = path.split(".")

# Binary will be the default if the user specified a non-standard file extension
if len(path_parts) > 0 and path_parts[-1] == "gltf":
binary = False

# map from CadQuery's right-handed +Z up coordinate system to glTF's right-handed +Y up coordinate system
# https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units
orig_loc = assy.loc
Expand Down
2 changes: 2 additions & 0 deletions doc/importexport.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Export Formats
* VRML
* VTP
* 3MF
* glTF

Notes on the Formats
---------------------
Expand All @@ -42,6 +43,7 @@ Notes on the Formats
* TJS is short for ThreeJS, and is a JSON mesh format that is useful for displaying 3D models in web browsers. The TJS format is used to display embedded 3D examples within the CadQuery documentation.
* VRML is a mesh-based format for representing interactive 3D objects in a web browser.
* VTP is a mesh-based format used by the VTK library.
* glTF is a mesh-based format useful for viewing models on the web. Whether the resulting glTF file is binary (.glb) or text (.gltf) is set by the file extension.

Importing DXF
##############
Expand Down
57 changes: 55 additions & 2 deletions tests/test_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,11 +647,64 @@ def test_save(extension, args, nested_assy, nested_assy_sphere):
assert os.path.exists(filename)


def test_save_stl_formats(nested_assy_sphere):

# Binary export
nested_assy_sphere.save("nested.stl", "STL", ascii=False)
assert os.path.exists("nested.stl")

# Trying to read a binary file as UTF-8/ASCII should throw an error
with pytest.raises(UnicodeDecodeError) as info:
with open("nested.stl", "r") as file:
file.read()

# ASCII export
nested_assy_sphere.save("nested_ascii.stl", ascii=True)
assert os.path.exists("nested_ascii.stl")
assert os.path.getsize("nested_ascii.stl") > 3960 * 1024


def test_save_gltf(nested_assy_sphere):

nested_assy_sphere.save("nested.glb", "GLTF")
# Binary export
nested_assy_sphere.save("nested.glb")
assert os.path.exists("nested.glb")
assert os.path.getsize("nested.glb") > 50 * 1024

# Trying to read a binary file as UTF-8/ASCII should throw an error
with pytest.raises(UnicodeDecodeError) as info:
with open("nested.glb", "r") as file:
file.read()

# ASCII export
nested_assy_sphere.save("nested_ascii.gltf")
assert os.path.exists("nested_ascii.gltf")
assert os.path.getsize("nested_ascii.gltf") > 5 * 1024


def test_exportGLTF(nested_assy_sphere):
"""Tests the exportGLTF function directly for binary vs ascii export."""

# Test binary export inferred from file extension
cq.exporters.assembly.exportGLTF(nested_assy_sphere, "nested_export_gltf.glb")
with pytest.raises(UnicodeDecodeError) as info:
with open("nested_export_gltf.glb", "r") as file:
file.read()

# Test explicit binary export
cq.exporters.assembly.exportGLTF(
nested_assy_sphere, "nested_export_gltf_2.glb", binary=True
)
with pytest.raises(UnicodeDecodeError) as info:
with open("nested_export_gltf_2.glb", "r") as file:
file.read()

# Test explicit ascii export
cq.exporters.assembly.exportGLTF(
nested_assy_sphere, "nested_export_gltf_3.gltf", binary=False
)
with open("nested_export_gltf_3.gltf", "r") as file:
lines = file.readlines()
assert lines[0].startswith('{"accessors"')


def test_save_gltf_boxes2(boxes2_assy, tmpdir, capfd):
Expand Down

0 comments on commit cce60ce

Please sign in to comment.