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

Added support for export to STEP, STL, 3MF and ThreeJS #45

Merged
merged 1 commit into from
Jan 7, 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
68 changes: 31 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ all of the consumers.
- [Publishing](#publishing)
- [Export](#export)
- [Images](#images)
- [Other modelling formats](#other-modelling-formats)
- [Purchasing / Bill of materials](#purchasing--bill-of-materials)
- [Security](#security)
- [Tools for mechanical engineering](#tools-for-mechanical-engineering)
Expand Down Expand Up @@ -155,6 +154,32 @@ PartCAD allows to define parts using any of the following methods:
<th>Result</td>
</tr>
<tr>
<td><a href="https://github.com/CadQuery/cadquery">CadQuery</a></td>
<td>
<code># partcad.yaml
parts:
cube:
type: cadquery</code>
<br/>
<br/>
Place the CadQuery script in "cube.py"
</td>
<td><img src="https://github.com/openvmp/partcad/blob/main/examples/part_cadquery_primitive/cube.png?raw=true"></td>
</tr>
<tr>
<td><a href="https://github.com/gumyr/build123d">build123d</a></td>
<td>
<code># partcad.yaml
parts:
cube:
type: build123d</code>

<br/>
Place the build123d script in "cube.py"
</td>
<td><img src="https://github.com/openvmp/partcad/blob/main/examples/part_cadquery_primitive/cube.png?raw=true"></td>
</tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/ISO_10303">STEP</a></td>
<td>
<code># partcad.yaml
Expand Down Expand Up @@ -206,32 +231,6 @@ Store the model in "cube.scad"
</td>
<td><img src="https://github.com/openvmp/partcad/blob/main/examples/part_scad/cube.png?raw=true"></td>
</tr>
<tr>
<td><a href="https://github.com/CadQuery/cadquery">CadQuery</a></td>
<td>
<code># partcad.yaml
parts:
cube:
type: cadquery</code>
<br/>
<br/>
Place the CadQuery script in "cube.py"
</td>
<td><img src="https://github.com/openvmp/partcad/blob/main/examples/part_cadquery_primitive/cube.png?raw=true"></td>
</tr>
<tr>
<td><a href="https://github.com/gumyr/build123d">build123d</a></td>
<td>
<code># partcad.yaml
parts:
cube:
type: build123d</code>

<br/>
Place the build123d script in "cube.py"
</td>
<td><img src="https://github.com/openvmp/partcad/blob/main/examples/part_cadquery_primitive/cube.png?raw=true"></td>
</tr>
</table>

Other methods to define parts are coming in soon (e.g. SCAD).
Expand Down Expand Up @@ -350,17 +349,12 @@ Individual parts, assemblies and scenes can be rendered and exported into the
following formats:

- PNG
- [STL](https://en.wikipedia.org/wiki/STL_(file_format)) (not yet)
- [STEP] (not yet)
- ...

### Other modelling formats

Additionally, assemblies and scenes can be exported into the following formats:
- [STEP]
- [STL](https://en.wikipedia.org/wiki/STL_(file_format))
- [3MF](https://en.wikipedia.org/wiki/3D_Manufacturing_Format)
- [ThreeJS](https://en.wikipedia.org/wiki/Three.js)

- [SDF](http://sdformat.org/) (not yet / in progress)
- [FreeCAD](https://www.freecad.org/) project (not yet / in progress)
- ...
Expect more image formats to be added to the list of supported export formats in the future.

### Purchasing / Bill of materials

Expand Down
5 changes: 5 additions & 0 deletions examples/export/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.png
*.step
*.stl
*.3mf
*.json
15 changes: 15 additions & 0 deletions examples/export/partcad.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
desc: PartCAD example project to demonstrate various export mechanisms
parts:
bolt:
type: step
desc: M8x30-screw
render:
png:
prefix: ./
width: 128
height: 128
step: ./
stl: ./
3mf: ./
threejs: ./
markdown: README.md
44 changes: 36 additions & 8 deletions src/partcad/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,42 @@ def render(self, parts=None, assemblies=None):
assemblies = self.config_obj["assemblies"].keys()

# See whether PNG is configured to be auto-rendered or not
if "png" in render:
logging.info("Rendering PNG...")

for part_name in parts:
part = self.get_part(part_name)
if not part is None:
for part_name in parts:
part = self.get_part(part_name)
if not part is None:
if "png" in render:
logging.info("Rendering PNG...")
part.render_png(project=self)
for assembly_name in assemblies:
assembly = self.get_assembly(assembly_name)
if not assembly is None:
if "step" in render:
logging.info("Rendering STEP...")
part.render_step(project=self)
if "stl" in render:
logging.info("Rendering STL...")
part.render_stl(project=self)
if "3mf" in render:
logging.info("Rendering 3MF...")
part.render_3mf(project=self)
if "threejs" in render:
logging.info("Rendering ThreeJS...")
part.render_threejs(project=self)
for assembly_name in assemblies:
assembly = self.get_assembly(assembly_name)
if not assembly is None:
if "png" in render:
logging.info("Rendering PNG...")
assembly.render_png(project=self)
if "step" in render:
logging.info("Rendering STEP...")
assembly.render_step(project=self)
if "stl" in render:
logging.info("Rendering STL...")
assembly.render_stl(project=self)
if "3mf" in render:
logging.info("Rendering 3MF...")
assembly.render_3mf(project=self)
if "threejs" in render:
logging.info("Rendering ThreeJS...")
assembly.render_threejs(project=self)

# See whether STEP is configured to be auto-rendered or not
127 changes: 115 additions & 12 deletions src/partcad/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ def _get_svg_url(self):
self.svg_url = "./part.svg"
return self.svg_url

def render_png(
def render_getopts(
self,
kind,
extension,
project=None,
filepath=None,
width=None,
height=None,
):
if (
not project is None
Expand All @@ -162,20 +162,33 @@ def render_png(
else:
render_opts = {}

if "png" in render_opts and not render_opts["png"] is None:
if isinstance(render_opts["png"], str):
png_opts = {"prefix": render_opts["png"]}
if kind in render_opts and not render_opts[kind] is None:
if isinstance(render_opts[kind], str):
opts = {"prefix": render_opts[kind]}
else:
png_opts = render_opts["png"]
opts = render_opts[kind]
else:
png_opts = {}
opts = {}

# Using the project's config defaults if any
if filepath is None:
if "prefix" in png_opts and not png_opts["prefix"] is None:
filepath = os.path.join(png_opts["prefix"], self.name + ".png")
if "prefix" in opts and not opts["prefix"] is None:
filepath = os.path.join(opts["prefix"], self.name + extension)
else:
filepath = self.name + ".png"
filepath = self.name + extension

logging.info("Rendering: %s" % filepath)

return opts, filepath

def render_png(
self,
project=None,
filepath=None,
width=None,
height=None,
):
png_opts, filepath = self.render_getopts("png", ".png", project, filepath)

if width is None:
if "width" in png_opts and not png_opts["width"] is None:
Expand All @@ -189,7 +202,6 @@ def render_png(
height = DEFAULT_RENDER_HEIGHT

# Render the vector image
logging.info("Rendering: %s" % filepath)
svg_path = self._get_svg_path()

# Render the raster image
Expand All @@ -202,6 +214,97 @@ def render_png(
drawing.height *= scale
renderPM.drawToFile(drawing, filepath, fmt="PNG")

def render_step(
self,
project=None,
filepath=None,
):
step_opts, filepath = self.render_getopts("step", ".step", project, filepath)

cq_obj = self.get_cadquery()
cq.exporters.export(cq_obj, filepath)

def render_stl(
self,
project=None,
filepath=None,
tolerance=None,
):
stl_opts, filepath = self.render_getopts("stl", ".stl", project, filepath)

cq_obj = self.get_cadquery()
cq.exporters.export(
cq_obj,
filepath,
)

def render_3mf(
self,
project=None,
filepath=None,
tolerance=None,
angularTolerance=None,
):
threemf_opts, filepath = self.render_getopts("3mf", ".3mf", project, filepath)

if tolerance is None:
if "tolerance" in threemf_opts and not threemf_opts["tolerance"] is None:
tolerance = threemf_opts["tolerance"]
else:
tolerance = 0.1

if angularTolerance is None:
if (
"angularTolerance" in threemf_opts
and not threemf_opts["angularTolerance"] is None
):
angularTolerance = threemf_opts["angularTolerance"]
else:
angularTolerance = 0.1

cq_obj = self.get_cadquery()
cq.exporters.export(
cq_obj,
filepath,
tolerance=tolerance,
angularTolerance=angularTolerance,
)

def render_threejs(
self,
project=None,
filepath=None,
tolerance=None,
angularTolerance=None,
):
threejs_opts, filepath = self.render_getopts(
"threejs", ".json", project, filepath
)

if tolerance is None:
if "tolerance" in threejs_opts and not threejs_opts["tolerance"] is None:
tolerance = threejs_opts["tolerance"]
else:
tolerance = 0.1

if angularTolerance is None:
if (
"angularTolerance" in threejs_opts
and not threejs_opts["angularTolerance"] is None
):
angularTolerance = threejs_opts["angularTolerance"]
else:
angularTolerance = 0.1

cq_obj = self.get_cadquery()
cq.exporters.export(
cq_obj,
filepath,
tolerance=tolerance,
angularTolerance=angularTolerance,
exportType=cq.exporters.ExportTypes.TJS,
)

def render_txt(self, filepath=None):
if filepath is None:
filepath = self.path + "/bom.txt"
Expand Down