mirror of
https://github.com/csirmaz/openscad-py.git
synced 2025-06-20 18:05:41 +02:00
Convenience methods for cylinders
This commit is contained in:
parent
1981dcf420
commit
787a3689e0
3 changed files with 96 additions and 22 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*~
|
||||
*.bak
|
||||
__pycache__
|
||||
.idea
|
107
openscad_py.py
107
openscad_py.py
|
@ -1,5 +1,6 @@
|
|||
|
||||
from typing import Union
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
EPSILON = 1e-7
|
||||
|
@ -7,31 +8,32 @@ NP_TYPE = np.float_
|
|||
|
||||
|
||||
class Point:
|
||||
"""Represents a 3D point of vector"""
|
||||
"""Represents a point of vector in arbitrary dimensions"""
|
||||
|
||||
def __init__(self, coords):
|
||||
self.c = np.array(coords, dtype=NP_TYPE)
|
||||
|
||||
@classmethod
|
||||
def c(cls, coords: Union[list, Point]) -> Point:
|
||||
def c(cls, coords: Union[list, 'Point']) -> 'Point':
|
||||
"""Ensure coords is an instance of Point"""
|
||||
if isinstance(coords, Point):
|
||||
return coords
|
||||
return Point(coords)
|
||||
|
||||
def render(self) -> str:
|
||||
"""Render the point into a SCAD script"""
|
||||
return ",".join([str(c) for c in self.c])
|
||||
|
||||
def scale(self, x: float) -> Point:
|
||||
def scale(self, x: float) -> 'Point':
|
||||
"""Scale the current vector/point by a scalar"""
|
||||
return self.__class__(self.c * x)
|
||||
|
||||
def add(self, p: Point) -> Point:
|
||||
def add(self, p: 'Point') -> 'Point':
|
||||
assert isinstance(p, Point)
|
||||
assert self.dim() == p.dim()
|
||||
return self.__class__(self.c + p.c)
|
||||
|
||||
def sub(self, p: Point) -> Point:
|
||||
def sub(self, p: 'Point') -> 'Point':
|
||||
assert isinstance(p, Point)
|
||||
assert self.dim() == p.dim()
|
||||
return self.__class__(self.c - p.c)
|
||||
|
@ -48,34 +50,53 @@ class Point:
|
|||
"""Return the length of the vector"""
|
||||
return np.sqrt(np.square(self.c).sum())
|
||||
|
||||
def norm(self) -> Point:
|
||||
def norm(self) -> 'Point':
|
||||
l = self.length()
|
||||
if l == 0:
|
||||
raise Exception("normalising 0 vector")
|
||||
return self.__class__(self.c / self.length())
|
||||
|
||||
def dot(self, p: Point) -> float:
|
||||
def dot(self, p: 'Point') -> float:
|
||||
"""Return the dot product"""
|
||||
return np.dot(self.c, p.c)
|
||||
|
||||
def cross(self, p: 'Point') -> 'Point':
|
||||
"""Return the cross product"""
|
||||
assert self.dim() == 3
|
||||
assert p.dim() == 3
|
||||
return Point([
|
||||
self.c[1]*p.c[2] - self.c[2]*p.c[1],
|
||||
self.c[2]*p.c[0] - self.c[0]*p.c[2],
|
||||
self.c[0]*p.c[1] - self.c[1]*p.c[0]
|
||||
|
||||
])
|
||||
|
||||
def eq(self, p: Point) -> bool:
|
||||
def eq(self, p: 'Point') -> bool:
|
||||
return (self.c == p.c).all()
|
||||
|
||||
def lt(self, p: Point) -> bool:
|
||||
def lt(self, p: 'Point') -> bool:
|
||||
return (self.c < p.c).all()
|
||||
|
||||
def le(self, p: Point) -> bool:
|
||||
def le(self, p: 'Point') -> bool:
|
||||
return (self.c <= p.c).all()
|
||||
|
||||
def gt(self, p: Point) -> bool:
|
||||
def gt(self, p: 'Point') -> bool:
|
||||
return (self.c > p.c).all()
|
||||
|
||||
def ge(self, p: Point) -> bool:
|
||||
def ge(self, p: 'Point') -> bool:
|
||||
return (self.c >= p.c).all()
|
||||
|
||||
def allclose(self, p: Point) -> bool:
|
||||
def allclose(self, p: 'Point') -> bool:
|
||||
return self.c.shape == p.c.shape and np.allclose(self.c, p.c)
|
||||
|
||||
def angle(self, p: 'Point') -> float:
|
||||
"""Return the angle between two vectors, in degrees"""
|
||||
r = self.dot(p)
|
||||
r = r / self.length() / p.length()
|
||||
r = math.acos(r)
|
||||
return r / math.pi * 180.
|
||||
|
||||
def rotate(self, coords, angle: float) -> Point:
|
||||
def rotate(self, coords, angle: float) -> 'Point':
|
||||
"""Rotate. coords is a list of 2 coordinate indices that we rotate"""
|
||||
assert len(coords) == 2
|
||||
ca, cb = coords
|
||||
|
@ -112,33 +133,39 @@ class Point:
|
|||
class Object:
|
||||
"""Abstract class for an SCAD object"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _center(self) -> str:
|
||||
return ('true' if self.center else 'false')
|
||||
|
||||
def add(self, obj):
|
||||
return Collection([self, action])
|
||||
return Collection([self, obj])
|
||||
|
||||
def render(self) -> str:
|
||||
raise Exception("abstract method")
|
||||
|
||||
def translate(self, v: Union[list, Point]):
|
||||
"""Apply a translation"""
|
||||
return Translate(v=v, child=self)
|
||||
|
||||
def move(self, v: Union[list, Point]):
|
||||
return Translate(v, self)
|
||||
"""Apply a translation"""
|
||||
return Translate(v=v, child=self)
|
||||
|
||||
def rotate(self, a, v: Union[list, Point]):
|
||||
"""Apply a rotation"""
|
||||
return Rotate(a=a, v=v, child=self)
|
||||
|
||||
|
||||
class Cube(Object):
|
||||
|
||||
def __init__(self, size: Union[list, Point], center: bool = False):
|
||||
self.size = Point.c(position)
|
||||
self.size = Point.c(size)
|
||||
self.center = center
|
||||
|
||||
def render(self):
|
||||
return f"cube(size=[{self.size.render()}], center={self._center()});"
|
||||
|
||||
|
||||
def Sphere(Object):
|
||||
class Sphere(Object):
|
||||
|
||||
def __init__(self, r):
|
||||
self.r = r
|
||||
|
@ -148,16 +175,37 @@ def Sphere(Object):
|
|||
return f"sphere(r={self.r});"
|
||||
|
||||
|
||||
def Cylinder(Object):
|
||||
class Cylinder(Object):
|
||||
|
||||
def __init__(self, h, r=None, r1=None, r2=None, center: bool = False):
|
||||
self.height = h
|
||||
self.r1 = r if r1 is None else r1
|
||||
self.r2 = r if r2 is None else r2
|
||||
self.center = center
|
||||
# $fa, $fs, $fn
|
||||
|
||||
def render(self):
|
||||
return f"cylinder(h={self.height}, r1={self.r1}, r2={self.r2}, center={self._center()});"
|
||||
|
||||
@classmethod
|
||||
def from_ends(cls, radius: float, p1: Union[list, Point], p2: Union[list, Point]) -> Object:
|
||||
"""Construct a cylinder between two points"""
|
||||
p1 = Point.c(p1)
|
||||
p2 = Point.c(p2)
|
||||
v = p2.sub(p1)
|
||||
length = v.length()
|
||||
assert length != 0
|
||||
z = Point([0, 0, 1])
|
||||
r = v.cross(z).norm()
|
||||
rangle = v.angle(z)
|
||||
if r.length() == 0:
|
||||
# The cylinder is in the Z direction
|
||||
if abs(abs(rangle) - 180.) < .1:
|
||||
p1 = p2
|
||||
rangle = 0
|
||||
r = z
|
||||
return cls(h=length, r=radius, center=False).rotate(a=rangle, v=r).move(p1)
|
||||
|
||||
|
||||
|
||||
# TODO polyhedron(points=[[],], faces[[p,],], convexity=)
|
||||
|
@ -177,11 +225,26 @@ class Collection(Object):
|
|||
|
||||
|
||||
class Translate(Object):
|
||||
"""Represents a translation transformation applied to an object"""
|
||||
|
||||
def __init__(self, v: Union[list, Point], child: Object):
|
||||
self.v = Point.c(v)
|
||||
self.child = child
|
||||
|
||||
def render(self):
|
||||
return f"translate(v=[{self.v.render()}]){{\n{self.child.render()}\n}}"
|
||||
|
||||
|
||||
class Rotate(Object):
|
||||
"""Represents a rotation transformation applied to an object"""
|
||||
|
||||
def __init__(self, a, v: Union[list, Point], child: Object):
|
||||
self.a = a
|
||||
self.v = Point.c(v)
|
||||
self.child = child
|
||||
|
||||
def render(self):
|
||||
return f"rotate(a={self.a}, v=[{self.v.render()}]){{\n{self.child.render()}\n}}"
|
||||
|
||||
|
||||
|
||||
|
|
7
test.py
Normal file
7
test.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
from openscad_py import Cube, Cylinder
|
||||
|
||||
|
||||
print(Cube([1,1,1]).render())
|
||||
print(Cylinder(h=5, r=2).render())
|
||||
print(Cylinder.from_ends(2, [0,0,0], [1,0,0]).render())
|
Loading…
Add table
Add a link
Reference in a new issue