Improve documentation

This commit is contained in:
Elod Csirmaz 2024-12-02 19:39:18 +00:00
parent 6bc3474d9e
commit 6f2503038f
3 changed files with 65 additions and 17 deletions

View file

@ -10,12 +10,18 @@ from openscad_py.object_ import Object
class Cube(Object): class Cube(Object):
"""A 3D primitive, cube. """A 3D primitive, cube.
Creates a cube in the first octant. When center is true, the cube is centered on the origin.
See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#cube See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#cube
""" """
def __init__(self, size: TUnion[list, Point], center: bool = False): def __init__(self, size: TUnion[list, Point], center: bool = False):
"""
Creates a cube in the first octant. When `center` is True, the cube is centered on the origin.
Arguments:
- size: a Point object or a list of `x, y, z` sizes
- center: if True, the cube is centered on the origin
"""
self.size = Point.c(size) self.size = Point.c(size)
self.center = center self.center = center

View file

@ -9,35 +9,38 @@ NP_TYPE = np.float_
class Point: class Point:
"""Represents a point or vector in arbitrary dimensions""" """Represents a point or vector in arbitrary number of dimensions"""
def __init__(self, coords): def __init__(self, coords: List[float]):
self.c = np.array(coords, dtype=NP_TYPE) self.c = np.array(coords, dtype=NP_TYPE)
@classmethod @classmethod
def c(cls, coords: TUnion[list, 'Point']) -> 'Point': def c(cls, coords: TUnion[list[float], 'Point']) -> 'Point':
"""Ensure coords is an instance of Point (idempotent)""" """Ensure `coords` is an instance of Point (idempotent)"""
if isinstance(coords, Point): if isinstance(coords, Point):
return coords return coords
return Point(coords) return Point(coords)
def render(self) -> str: def render(self) -> str:
"""Render the object into OpenSCAD code""" """Render the point / vector 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:
"""Render the point / vector into STL"""
return " ".join([str(c) for c in self.c]) 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""" """Scale the current point / vector by the scalar `x`"""
return self.__class__(self.c * x) return self.__class__(self.c * x)
def add(self, p: 'Point') -> 'Point': def add(self, p: 'Point') -> 'Point':
"""Add another point / vector `p` to the current one"""
assert isinstance(p, Point) assert isinstance(p, Point)
assert self.dim() == p.dim() assert self.dim() == p.dim()
return self.__class__(self.c + p.c) return self.__class__(self.c + p.c)
def sub(self, p: 'Point') -> 'Point': def sub(self, p: 'Point') -> 'Point':
"""Subtract another point / vector `p` from the current one"""
assert isinstance(p, Point) assert isinstance(p, Point)
assert self.dim() == p.dim() assert self.dim() == p.dim()
return self.__class__(self.c - p.c) return self.__class__(self.c - p.c)
@ -55,17 +58,18 @@ class Point:
return np.sqrt(np.square(self.c).sum()) return np.sqrt(np.square(self.c).sum())
def norm(self) -> 'Point': def norm(self) -> 'Point':
"""Return a normalized version of the vector (scaled to length 1)"""
l = self.length() l = self.length()
if l == 0: if l == 0:
raise Exception("normalising 0 vector") raise Exception("Attempted to normalise 0 vector")
return self.__class__(self.c / self.length()) return self.__class__(self.c / self.length())
def dot(self, p: 'Point') -> float: def dot(self, p: 'Point') -> float:
"""Return the dot product""" """Return the dot product of the current vector and `p`"""
return np.dot(self.c, p.c) return np.dot(self.c, p.c)
def cross(self, p: 'Point') -> 'Point': def cross(self, p: 'Point') -> 'Point':
"""Return the cross product""" """Return the cross product of the current vector and `p`"""
assert self.dim() == 3 assert self.dim() == 3
assert p.dim() == 3 assert p.dim() == 3
return Point([ return Point([
@ -76,25 +80,36 @@ class Point:
]) ])
def eq(self, p: 'Point') -> bool: def eq(self, p: 'Point') -> bool:
"""Return whether the current point / vector and `p` are equal"""
return (self.c == p.c).all() return (self.c == p.c).all()
def lt(self, p: 'Point') -> bool: def lt(self, p: 'Point') -> bool:
"""Return whether the current vector is smaller than `p` in each dimension"""
return (self.c < p.c).all() return (self.c < p.c).all()
def le(self, p: 'Point') -> bool: def le(self, p: 'Point') -> bool:
"""Return whether the current vector is smaller or equal to `p` in each dimension"""
return (self.c <= p.c).all() return (self.c <= p.c).all()
def gt(self, p: 'Point') -> bool: def gt(self, p: 'Point') -> bool:
"""Return whether the current vector is greater than `p` in each dimension"""
return (self.c > p.c).all() return (self.c > p.c).all()
def ge(self, p: 'Point') -> bool: def ge(self, p: 'Point') -> bool:
"""Return whether the current vector is greater or equal to `p` in each dimension"""
return (self.c >= p.c).all() return (self.c >= p.c).all()
def allclose(self, p: 'Point') -> bool: def allclose(self, p: 'Point') -> bool:
"""Return whether the current point / vector and `p` are close to each other"""
return self.c.shape == p.c.shape and np.allclose(self.c, p.c) return self.c.shape == p.c.shape and np.allclose(self.c, p.c)
def angle(self, p: 'Point', mode: str = "deg") -> float: def angle(self, p: 'Point', mode: str = "deg") -> float:
"""Return the angle between two vectors in degrees or radians""" """Return the angle between two vectors in degrees or radians
Arguments:
- p: a Point object
- mode: "deg" | "rad"
"""
r = self.dot(p) r = self.dot(p)
r = r / self.length() / p.length() r = r / self.length() / p.length()
r = math.acos(r) r = math.acos(r)
@ -105,7 +120,11 @@ class Point:
raise ValueError("Unknown mode") raise ValueError("Unknown mode")
def z_slope(self, mode: str = "deg") -> float: def z_slope(self, mode: str = "deg") -> float:
"""Return the slope of a vector in degrees or radians""" """Return the slope of a vector in degrees or radians
Arguments:
- mode: "deg" | "rad"
"""
r = self.c[2] / self.length() r = self.c[2] / self.length()
r = math.asin(r) r = math.asin(r)
if mode == "rad": if mode == "rad":
@ -115,7 +134,12 @@ class Point:
raise ValueError("Unknown mode") raise ValueError("Unknown mode")
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""" """Rotate the current vector
Arguments:
- coords: A list of 2 coordinate indices to rotate
- angle: the angle to rotate by, in degrees
"""
assert len(coords) == 2 assert len(coords) == 2
ca, cb = coords ca, cb = coords
s = np.sin(angle / 180. * np.pi) s = np.sin(angle / 180. * np.pi)
@ -128,25 +152,40 @@ class Point:
# Operator overloading # Operator overloading
def __add__(self, other): def __add__(self, other):
"""Use `p1 + p2` to add two vectors"""
return self.add(other) return self.add(other)
def __radd__(self, other): def __radd__(self, other):
"""Use `p1 + p2` to add two vectors"""
assert isinstance(other, Point) assert isinstance(other, Point)
return other.add(self) return other.add(self)
def __sub__(self, other): def __sub__(self, other):
"""Use `p1 - p2` to subtract two vectors"""
return self.sub(other) return self.sub(other)
def __rsub__(self, other): def __rsub__(self, other):
"""Use `p1 - p2` to subtract two vectors"""
assert isinstance(other, Point) assert isinstance(other, Point)
return other.sub(self) return other.sub(self)
def __mul__(self, other): def __mul__(self, other):
"""Use `p * x` to scale a vector"""
return self.scale(other) return self.scale(other)
def __rmul__(self, other): def __rmul__(self, other):
"""Use `x * p` to scale a vector"""
return self.scale(other) return self.scale(other)
def __neg__(self): def __neg__(self):
"""Use `-p` to negate a vector"""
return self.scale(-1.) return self.scale(-1.)
__pdoc__ = {
'Point.__add__': True,
'Point.__sub__': True,
'Point.__mul__': True,
'Point.__rmul__': True,
'Point.__neg__': True,
}

View file

@ -9,14 +9,17 @@ from openscad_py.object_ import Object
class Polyhedron(Object): class Polyhedron(Object):
"""A 3D primitive, a polyhedron defined by a list of points and faces. """A 3D primitive, a polyhedron defined by a list of points and faces.
Faces are defined by lists of point indices. The points of a face must be listed clockwise when Nonplanar faces will be triangulated by OpenSCAD.
looking at the face from the outside inward.
Nonplanar faces should be triangulated by OpenSCAD.
See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#polyhedron See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#polyhedron
""" """
def __init__(self, points: List[TUnion[list, Point]], faces: List[list], convexity: int = 10): def __init__(self, points: List[TUnion[list, Point]], faces: List[list], convexity: int = 10):
"""
Arguments:
- points: a list of Point objects or coordinate tuples defining the vertices
- faces: defines the faces as a list of lists of vertex indices. The points of a face must be listed clockwise when looking at the face from the outside inward.
"""
self.points = [Point.c(p) for p in points] self.points = [Point.c(p) for p in points]
self.faces = faces self.faces = faces
self.convexity = convexity self.convexity = convexity