From 6f2503038fe88e6997cc89e9ec19d4db76fcd5a5 Mon Sep 17 00:00:00 2001 From: Elod Csirmaz Date: Mon, 2 Dec 2024 19:39:18 +0000 Subject: [PATCH] Improve documentation --- openscad_py/cube.py | 10 +++++-- openscad_py/point.py | 63 +++++++++++++++++++++++++++++++-------- openscad_py/polyhedron.py | 9 ++++-- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/openscad_py/cube.py b/openscad_py/cube.py index 91a49d9..da8d2ab 100644 --- a/openscad_py/cube.py +++ b/openscad_py/cube.py @@ -10,12 +10,18 @@ from openscad_py.object_ import Object class Cube(Object): """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 """ 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.center = center diff --git a/openscad_py/point.py b/openscad_py/point.py index 79e5452..ee6786b 100644 --- a/openscad_py/point.py +++ b/openscad_py/point.py @@ -9,35 +9,38 @@ NP_TYPE = np.float_ 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) @classmethod - def c(cls, coords: TUnion[list, 'Point']) -> 'Point': - """Ensure coords is an instance of Point (idempotent)""" + def c(cls, coords: TUnion[list[float], 'Point']) -> 'Point': + """Ensure `coords` is an instance of Point (idempotent)""" if isinstance(coords, Point): return coords return Point(coords) 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])) + "]" def render_stl(self) -> str: + """Render the point / vector into STL""" return " ".join([str(c) for c in self.c]) 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) def add(self, p: 'Point') -> 'Point': + """Add another point / vector `p` to the current one""" assert isinstance(p, Point) assert self.dim() == p.dim() return self.__class__(self.c + p.c) def sub(self, p: 'Point') -> 'Point': + """Subtract another point / vector `p` from the current one""" assert isinstance(p, Point) assert self.dim() == p.dim() return self.__class__(self.c - p.c) @@ -55,17 +58,18 @@ class Point: return np.sqrt(np.square(self.c).sum()) def norm(self) -> 'Point': + """Return a normalized version of the vector (scaled to length 1)""" l = self.length() if l == 0: - raise Exception("normalising 0 vector") + raise Exception("Attempted to normalise 0 vector") return self.__class__(self.c / self.length()) 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) 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 p.dim() == 3 return Point([ @@ -76,25 +80,36 @@ class Point: ]) def eq(self, p: 'Point') -> bool: + """Return whether the current point / vector and `p` are equal""" return (self.c == p.c).all() def lt(self, p: 'Point') -> bool: + """Return whether the current vector is smaller than `p` in each dimension""" return (self.c < p.c).all() 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() def gt(self, p: 'Point') -> bool: + """Return whether the current vector is greater than `p` in each dimension""" return (self.c > p.c).all() 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() 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) 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 = r / self.length() / p.length() r = math.acos(r) @@ -105,7 +120,11 @@ class Point: raise ValueError("Unknown mode") 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 = math.asin(r) if mode == "rad": @@ -115,7 +134,12 @@ class Point: raise ValueError("Unknown mode") 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 ca, cb = coords s = np.sin(angle / 180. * np.pi) @@ -128,25 +152,40 @@ class Point: # Operator overloading def __add__(self, other): + """Use `p1 + p2` to add two vectors""" return self.add(other) def __radd__(self, other): + """Use `p1 + p2` to add two vectors""" assert isinstance(other, Point) return other.add(self) def __sub__(self, other): + """Use `p1 - p2` to subtract two vectors""" return self.sub(other) def __rsub__(self, other): + """Use `p1 - p2` to subtract two vectors""" assert isinstance(other, Point) return other.sub(self) def __mul__(self, other): + """Use `p * x` to scale a vector""" return self.scale(other) def __rmul__(self, other): + """Use `x * p` to scale a vector""" return self.scale(other) def __neg__(self): + """Use `-p` to negate a vector""" return self.scale(-1.) + +__pdoc__ = { + 'Point.__add__': True, + 'Point.__sub__': True, + 'Point.__mul__': True, + 'Point.__rmul__': True, + 'Point.__neg__': True, +} \ No newline at end of file diff --git a/openscad_py/polyhedron.py b/openscad_py/polyhedron.py index 70bf6d3..1dfac51 100644 --- a/openscad_py/polyhedron.py +++ b/openscad_py/polyhedron.py @@ -9,14 +9,17 @@ from openscad_py.object_ import Object class Polyhedron(Object): """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 - looking at the face from the outside inward. - Nonplanar faces should be triangulated by OpenSCAD. + Nonplanar faces will be triangulated by OpenSCAD. 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): + """ + 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.faces = faces self.convexity = convexity