mirror of
https://github.com/csirmaz/openscad-py.git
synced 2025-06-21 02:15:40 +02:00
Documentation
This commit is contained in:
parent
9d24a2c85f
commit
bb7155a5fd
25 changed files with 88 additions and 21 deletions
72
README.md
72
README.md
|
@ -2,57 +2,73 @@
|
||||||
|
|
||||||
A Python OOP precompiler for OpenSCAD's language
|
A Python OOP precompiler for OpenSCAD's language
|
||||||
|
|
||||||
OpenSCAD ( https://openscad.org/ ) uses a functional scripting language to define solid 3D CAD models.
|
[OpenSCAD](https://openscad.org/) uses a functional scripting language to define solid 3D CAD models.
|
||||||
As such, it is a prefix language (modifiers go before the things they modify).
|
As such, it is a prefix language (modifiers go before the things they modify).
|
||||||
|
|
||||||
OpenSCADPy allows one to write OpenSCAD scripts using an object representation, which uses method calls
|
OpenSCADPy allows one to write OpenSCAD scripts using an object representation, which uses method calls
|
||||||
to describe modifications. This way, modifications are written after the objects they modify in a postfix
|
to describe modifications. This way, modifications are written after the objects they modify in a postfix
|
||||||
fashion, more closely resembling a procedural ordering of steps in the creation of the models.
|
fashion, more closely resembling a procedural ordering of steps in the creation of the models.
|
||||||
It also contains convenience functions to define a wider range of primitives, as well as some vector operations.
|
|
||||||
|
It also contains convenience functions to define a wider range of primitives, vector operations, and a method
|
||||||
|
to export polyhedra directly to STL.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```
|
```python
|
||||||
Cube([1, 1, 2]).move([0, 0, 1]).render()
|
# example.py
|
||||||
|
from openscad_py import Cube
|
||||||
|
|
||||||
|
colored_moved_cube = Cube([1, 1, 1]).move([2, 0, 0]).color(r=1, g=0, b=0)
|
||||||
|
print(colored_moved_cube.render())
|
||||||
```
|
```
|
||||||
|
|
||||||
returns
|
prints the OpenSCAD code
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
translate(v=[0, 0, 1]) { cube(size=[1, 1, 2], center=false); }
|
# example.scad
|
||||||
|
color(c=[1,0,0,1.0]){ translate(v=[2.0,0.0,0.0]){
|
||||||
|
cube(size=[1.0,1.0,1.0], center=false);
|
||||||
|
} }
|
||||||
|
```
|
||||||
|
|
||||||
|
An easy way to write and render the OpenSCAD code would be
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3 example.py > example.scad
|
||||||
|
$ openscad example.scad
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notable convenience functions
|
## Notable convenience functions
|
||||||
|
|
||||||
### Computational geometry
|
### Computational geometry
|
||||||
|
|
||||||
Usual computational geometry functions on the `Point` class that work in an arbitrary number of dimensions. Overloads for algebraic operators.
|
Usual computational geometry functions in the `Point` class that work in an arbitrary number of dimensions.
|
||||||
|
Overloads for algebraic operators. Examples:
|
||||||
|
|
||||||
```
|
```python
|
||||||
distance = (Point((0, 0, 1)) - Point((1, 0, 1))).length()
|
distance = (Point((0, 0, 1)) - Point((1, 0, 1))).length()
|
||||||
angle_between = Point((0, 0, 1) * 2).angle(Point((1, 0, 1)))
|
angle_between = Point((0, 0, 1) * 2).angle(Point((1, 0, 1)))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cylinders
|
### Cylinders
|
||||||
|
|
||||||
`Cylinder.from_ends()` constructs a cylinder between two given points in space
|
`Cylinder.from_ends()` constructs a cylinder between two given points in space. Example:
|
||||||
|
|
||||||
```
|
```python
|
||||||
openscad_code = Cylinder.from_ends(radius=2, p1=(0, 0, 1), p2=(1, 0, 2)).render()
|
openscad_code = Cylinder.from_ends(radius=2, p1=(0, 0, 1), p2=(1, 0, 2)).render()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tubes and toroids from a point grid
|
### Tubes and toroids from a point grid
|
||||||
|
|
||||||
`Polyhedron.tube()` creates a tube-like polyhedron from a 2D array of points
|
`Polyhedron.tube()` creates a tube-like Polyhedron object from a 2D array of points.
|
||||||
|
|
||||||
`Polyhedron.torus()` creates a toroid polyhedron from a 2D array of points
|
`Polyhedron.torus()` creates a toroid Polyhedron object from a 2D array of points.
|
||||||
|
|
||||||
### Tubes from a path
|
### Tubes from a path
|
||||||
|
|
||||||
`PathTube` creates a tube-like or toroid polyhedron from an arbitrary path
|
`PathTube` creates a tube-like or toroid Polyhedron object from an arbitrary path. Example:
|
||||||
|
|
||||||
```
|
```python
|
||||||
PathTube(
|
PathTube(
|
||||||
points=[(0,0,0), (0,0,1), (1,0,2), (1,1,0), (0,.5,0)],
|
points=[(0,0,0), (0,0,1), (1,0,2), (1,1,0), (0,.5,0)],
|
||||||
radius=.2,
|
radius=.2,
|
||||||
|
@ -64,7 +80,9 @@ PathTube(
|
||||||
|
|
||||||
### Polyhedron from a height map
|
### Polyhedron from a height map
|
||||||
|
|
||||||
```
|
`Polyhedron.from_heightmap()` creates a Polyhedron object from a 2D matrix of heights. Example:
|
||||||
|
|
||||||
|
```python
|
||||||
Polyhedron.from_heightmap(
|
Polyhedron.from_heightmap(
|
||||||
heights=[
|
heights=[
|
||||||
[3, 3, 1, 1, 1],
|
[3, 3, 1, 1, 1],
|
||||||
|
@ -81,4 +99,24 @@ Polyhedron.from_heightmap(
|
||||||
|
|
||||||
### Direct STL export
|
### Direct STL export
|
||||||
|
|
||||||
`Polyhedron.render_stl()` exports any polyhedron into STL directly
|
`Polyhedron.render_stl()` exports a Polyhedron object into STL directly.
|
||||||
|
This works well with `tube()`, `torus()`, `from_heightmap()` and `PathTube` described above.
|
||||||
|
Note that the polyhedron object cannot be post-modified (e.g. by `union`, `difference`) - if so,
|
||||||
|
use OpenSCAD to render the object and export to STL.
|
||||||
|
|
||||||
|
## Overview and usage
|
||||||
|
|
||||||
|
In `openscad_py`, all objects (including derived ones) come with a large set of convenience methods
|
||||||
|
to apply transformations, implemented in the base [`Object` class](https://github.com/csirmaz/openscad-py/blob/main/openscad_py/object_.py).
|
||||||
|
This allows to freely specify transformations on any object:
|
||||||
|
|
||||||
|
```python
|
||||||
|
moved_cube = Cube([1, 1, 1]).move([2, 0, 0])
|
||||||
|
colored_moved_cube = Cube([1, 1, 1]).move([2, 0, 0]).color(r=1, g=0, b=0)
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the desired object has been created, call `render()` on the final object to obtain the
|
||||||
|
OpenSCAD code.
|
||||||
|
|
||||||
|
For the full list of convenience functions, see
|
||||||
|
https://github.com/csirmaz/openscad-py/blob/main/openscad_py/object_.py .
|
||||||
|
|
7
example.py
Normal file
7
example.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
from openscad_py import Cube
|
||||||
|
|
||||||
|
colored_moved_cube = Cube([1, 1, 1]).move([2, 0, 0]).color(r=1, g=0, b=0)
|
||||||
|
print(colored_moved_cube.render())
|
||||||
|
|
||||||
|
|
3
example.scad
Normal file
3
example.scad
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
color(c=[1,0,0,1.0]){ translate(v=[2.0,0.0,0.0]){
|
||||||
|
cube(size=[1.0,1.0,1.0], center=false);
|
||||||
|
} }
|
|
@ -29,6 +29,7 @@ class Circle(Object):
|
||||||
return cls(r=r, fn=sides)
|
return cls(r=r, fn=sides)
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
fnstr = '' if self.fn is None else f", $fn={self.fn}"
|
fnstr = '' if self.fn is None else f", $fn={self.fn}"
|
||||||
return f"circle(r={self.r}{fnstr});"
|
return f"circle(r={self.r}{fnstr});"
|
||||||
|
|
||||||
|
|
|
@ -22,5 +22,6 @@ class Collection(Object):
|
||||||
return self.__class__(self.collection + [obj])
|
return self.__class__(self.collection + [obj])
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return "\n".join([o.render() for o in self.collection])
|
return "\n".join([o.render() for o in self.collection])
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Color(Object):
|
||||||
self.child = child
|
self.child = child
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"color(c=[{','.join([str(c) for c in self.color])}]){{ {self.child.render()} }}"
|
return f"color(c=[{','.join([str(c) for c in self.color])}]){{ {self.child.render()} }}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,5 +20,6 @@ class Cube(Object):
|
||||||
self.center = center
|
self.center = center
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"cube(size={self.size.render()}, center={self._center()});"
|
return f"cube(size={self.size.render()}, center={self._center()});"
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ class Cylinder(Object):
|
||||||
# $fa, $fs, $fn
|
# $fa, $fs, $fn
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"cylinder(h={self.height}, r1={self.r1}, r2={self.r2}, center={self._center()});"
|
return f"cylinder(h={self.height}, r1={self.r1}, r2={self.r2}, center={self._center()});"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -19,5 +19,6 @@ class DeltaOffset(Object):
|
||||||
self.chamfer = chamfer
|
self.chamfer = chamfer
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"offset(delta={delta}, chamfer={'true' if self.chamfer else 'false'}){{\n{self.child.render()}\n}}"
|
return f"offset(delta={delta}, chamfer={'true' if self.chamfer else 'false'}){{\n{self.child.render()}\n}}"
|
||||||
|
|
||||||
|
|
|
@ -15,5 +15,6 @@ class Difference(Object):
|
||||||
self.tool = Collection.c(tool) # what to remove
|
self.tool = Collection.c(tool) # what to remove
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"difference(){{ {self.subject.render()}\n{self.tool.render()} }}"
|
return f"difference(){{ {self.subject.render()}\n{self.tool.render()} }}"
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,12 @@ class Header:
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, quality: str = 'draft'):
|
def __init__(self, quality: str = 'draft'):
|
||||||
|
# See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Other_Language_Features#Circle_resolution:_$fa,_$fs,_and_$fn
|
||||||
self.quality = quality
|
self.quality = quality
|
||||||
|
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
# See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Other_Language_Features#Circle_resolution:_$fa,_$fs,_and_$fn
|
"""Return OpenSCAD code"""
|
||||||
if self.quality == 'draft':
|
if self.quality == 'draft':
|
||||||
return ""
|
return ""
|
||||||
if self.quality == 'mid':
|
if self.quality == 'mid':
|
||||||
|
|
|
@ -14,6 +14,7 @@ class Intersection(Object):
|
||||||
self.child = Collection.c(child)
|
self.child = Collection.c(child)
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"intersection(){{ {self.child.render()} }}"
|
return f"intersection(){{ {self.child.render()} }}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ class LinearExtrude(Object):
|
||||||
# twist, slices, scale (float/vector), $fn
|
# twist, slices, scale (float/vector), $fn
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"linear_extrude(height={self.height}, center={self._center()}, convexity={self.convexity}){{\n{self.child.render()}\n}}"
|
return f"linear_extrude(height={self.height}, center={self._center()}, convexity={self.convexity}){{\n{self.child.render()}\n}}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ class Object:
|
||||||
return Collection([self, obj])
|
return Collection([self, obj])
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
raise Exception("abstract method")
|
raise Exception("abstract method")
|
||||||
|
|
||||||
def translate(self, v: TUnion[list, Point]) -> 'Object':
|
def translate(self, v: TUnion[list, Point]) -> 'Object':
|
||||||
|
|
|
@ -119,6 +119,7 @@ class PathTube(Object):
|
||||||
return Polyhedron.tube(points=points_rows, convexity=self.convexity, make_torus=self.make_torus)
|
return Polyhedron.tube(points=points_rows, convexity=self.convexity, make_torus=self.make_torus)
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return self.process().render()
|
return self.process().render()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Point:
|
||||||
return Point(coords)
|
return Point(coords)
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
"""Render the point into a SCAD script"""
|
"""Render the object into OpenSCAD code"""
|
||||||
return "[" + (",".join([str(c) for c in self.c])) + "]"
|
return "[" + (",".join([str(c) for c in self.c])) + "]"
|
||||||
|
|
||||||
def render_stl(self) -> str:
|
def render_stl(self) -> str:
|
||||||
|
|
|
@ -16,5 +16,6 @@ class Polygon(Object):
|
||||||
self.convexity = convexity
|
self.convexity = convexity
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"polygon(points=[{','.join([p.render() for p in self.points])}], convexity={self.convexity});"
|
return f"polygon(points=[{','.join([p.render() for p in self.points])}], convexity={self.convexity});"
|
||||||
|
|
||||||
|
|
|
@ -176,6 +176,7 @@ class Polyhedron(Object):
|
||||||
return cls(points=point_list, faces=faces, convexity=convexity)
|
return cls(points=point_list, faces=faces, convexity=convexity)
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
faces_list = [f"[{','.join([str(x) for x in face])}]" for face in self.faces]
|
faces_list = [f"[{','.join([str(x) for x in face])}]" for face in self.faces]
|
||||||
return f"polyhedron(points=[{','.join([p.render() for p in self.points])}], faces=[{','.join(faces_list)}], convexity={self.convexity});"
|
return f"polyhedron(points=[{','.join([p.render() for p in self.points])}], faces=[{','.join(faces_list)}], convexity={self.convexity});"
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ class RadialOffset(Object):
|
||||||
# $fa, $fs, and $fn
|
# $fa, $fs, and $fn
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"offset(r={self.r}){{\n{self.child.render()}\n}}"
|
return f"offset(r={self.r}){{\n{self.child.render()}\n}}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ class Rotate(Object):
|
||||||
self.child = child
|
self.child = child
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"rotate(a={self.a}, v={self.v.render()}){{\n{self.child.render()}\n}}"
|
return f"rotate(a={self.a}, v={self.v.render()}){{\n{self.child.render()}\n}}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ class RotateExtrude(Object):
|
||||||
# $fa, $fs, $fn
|
# $fa, $fs, $fn
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"rotate_extrude(angle={self.angle}, convexity={self.convexity}) {{\n{self.child.render()}\n}}"
|
return f"rotate_extrude(angle={self.angle}, convexity={self.convexity}) {{\n{self.child.render()}\n}}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ class Scale(Object):
|
||||||
self.child = child
|
self.child = child
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"scale(v={self.v.render()}){{\n{self.child.render()}\n}}"
|
return f"scale(v={self.v.render()}){{\n{self.child.render()}\n}}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,12 @@ class Sphere(Object):
|
||||||
See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#sphere
|
See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#sphere
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, r):
|
def __init__(self, r):
|
||||||
self.r = r
|
self.r = r
|
||||||
# $fa, $fs, $fn
|
# $fa, $fs, $fn
|
||||||
|
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"sphere(r={self.r});"
|
return f"sphere(r={self.r});"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,5 +15,6 @@ class Translate(Object):
|
||||||
self.child = child
|
self.child = child
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"translate(v={self.v.render()}){{\n{self.child.render()}\n}}"
|
return f"translate(v={self.v.render()}){{\n{self.child.render()}\n}}"
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ class Union(Object):
|
||||||
self.child = Collection.c(child)
|
self.child = Collection.c(child)
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
|
"""Render the object into OpenSCAD code"""
|
||||||
return f"union(){{ {self.child.render()} }}"
|
return f"union(){{ {self.child.render()} }}"
|
||||||
|
|
||||||
def union(self, objects: TUnion[list, Object]) -> Object:
|
def union(self, objects: TUnion[list, Object]) -> Object:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue