diff --git a/docs/assets/examples/platonic_solids.png b/docs/assets/examples/platonic_solids.png new file mode 100644 index 00000000..91edf93e Binary files /dev/null and b/docs/assets/examples/platonic_solids.png differ diff --git a/docs/assets/examples/stud_wall.png b/docs/assets/examples/stud_wall.png new file mode 100644 index 00000000..000ed1fb Binary files /dev/null and b/docs/assets/examples/stud_wall.png differ diff --git a/docs/examples_1.rst b/docs/examples_1.rst index dccb122a..1607490e 100644 --- a/docs/examples_1.rst +++ b/docs/examples_1.rst @@ -8,7 +8,7 @@ The build123d Examples Overview -------------------------------- -In the GitHub repository you will find an `examples folder `_ +In the GitHub repository you will find an `examples folder `_. Most of the examples show the builder and algebra modes. @@ -39,18 +39,26 @@ Most of the examples show the builder and algebra modes. :link: examples-build123d_logo :link-type: ref - .. grid-item-card:: Circuit Board With Holes |Builder| |Algebra| - :img-top: assets/examples/thumbnail_canadian_flag_01.png + :img-top: assets/examples/thumbnail_circuit_board_01.png :link: examples-canadian_flag :link-type: ref - .. grid-item-card:: Canadian Flag Blowing in The Wind |Builder| |Algebra| - :img-top: assets/examples/thumbnail_circuit_board_01.png + :img-top: assets/examples/thumbnail_canadian_flag_01.png :link: examples-circuit_board :link-type: ref - + + .. grid-item-card:: Stud Wall |Algebra| + :img-top: assets/examples/stud_wall.png + :link: stud_wall + :link-type: ref + + .. grid-item-card:: Platonic Solids |Algebra| + :img-top: assets/examples/platonic_solids.png + :link: platonic_solids + :link-type: ref + .. NOTE 01: insert new example thumbnails above this line .. TODO: Copy this block to add the example thumbnails here @@ -71,7 +79,8 @@ Low Poly Benchy :align: center -The Benchy examples shows hot to import a STL model as a `Solid` object with the class `Mesher` and change it to low poly. +The Benchy examples shows how to import a STL model as a `Solid` object with the class `Mesher` and +modify it by replacing chimney with a BREP version. .. note @@ -240,6 +249,49 @@ This example demonstrates placing holes around a part. :start-after: [Code] :end-before: [End] +.. _stud_wall: + +Stud Wall +--------- +.. image:: assets/examples/stud_wall.png + :align: center + +This example demonstrates creatings custom `Part` objects and putting them into +assemblies. The custom object is a `Stud` used in the building industry while +the assembly is a `StudWall` created from copies of `Stud` objects for efficiency. +Both the `Stud` and `StudWall` objects use `RigidJoints` to define snap points which +are used to position all of objects. + +.. dropdown:: |Algebra| Reference Implementation (Algebra Mode) + + .. literalinclude:: ../examples/stud_wall.py + :start-after: [Code] + :end-before: [End] + +.. _platonic_solids: + +Platonic Solids +--------------- +.. image:: assets/examples/platonic_solids.png + :align: center + +This example creates a custom Part object PlatonicSolid. + +Platonic solids are five three-dimensional shapes that are highly symmetrical, +known since antiquity and named after the ancient Greek philosopher Plato. +These solids are unique because their faces are congruent regular polygons, +with the same number of faces meeting at each vertex. The five Platonic solids +are the tetrahedron (4 triangular faces), cube (6 square faces), octahedron +(8 triangular faces), dodecahedron (12 pentagonal faces), and icosahedron +(20 triangular faces). Each solid represents a unique way in which identical +polygons can be arranged in three dimensions to form a convex polyhedron, +embodying ideals of symmetry and balance. + +.. dropdown:: |Algebra| Reference Implementation (Algebra Mode) + + .. literalinclude:: ../examples/platonic_solids.py + :start-after: [Code] + :end-before: [End] .. NOTE 02: insert new example thumbnails above this line diff --git a/examples/platonic_solids.py b/examples/platonic_solids.py new file mode 100644 index 00000000..792bdefe --- /dev/null +++ b/examples/platonic_solids.py @@ -0,0 +1,140 @@ +""" +The Platonic solids as custom Part objects. + +name: platonic_solids.py +by: Gumyr +date: February 17, 2024 + +desc: + This example creates a custom Part object PlatonicSolid. + + Platonic solids are five three-dimensional shapes that are highly symmetrical, + known since antiquity and named after the ancient Greek philosopher Plato. + These solids are unique because their faces are congruent regular polygons, + with the same number of faces meeting at each vertex. The five Platonic solids + are the tetrahedron (4 triangular faces), cube (6 square faces), octahedron + (8 triangular faces), dodecahedron (12 pentagonal faces), and icosahedron + (20 triangular faces). Each solid represents a unique way in which identical + polygons can be arranged in three dimensions to form a convex polyhedron, + embodying ideals of symmetry and balance. + +license: + + Copyright 2024 Gumyr + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +""" + +# [Code] +from build123d import * +from math import sqrt +from typing import Union, Literal +from scipy.spatial import ConvexHull + +from ocp_vscode import show + +PHI = (1 + sqrt(5)) / 2 # The Golden Ratio + + +class PlatonicSolid(BasePartObject): + """Part Object: Platonic Solid + + Create one of the five convex Platonic solids. + + Args: + face_count (Literal[4,6,8,12,20]): number of faces + diameter (float): double distance to vertices, i.e. maximum size + rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0). + align (Union[None, Align, tuple[Align, Align, Align]], optional): align min, center, + or max of object. Defaults to None. + mode (Mode, optional): combine mode. Defaults to Mode.ADD. + """ + + tetrahedron_vertices = [(1, 1, 1), (1, -1, -1), (-1, 1, -1), (-1, -1, 1)] + + cube_vertices = [(i, j, k) for i in [-1, 1] for j in [-1, 1] for k in [-1, 1]] + + octahedron_vertices = ( + [(i, 0, 0) for i in [-1, 1]] + + [(0, i, 0) for i in [-1, 1]] + + [(0, 0, i) for i in [-1, 1]] + ) + + dodecahedron_vertices = ( + [(i, j, k) for i in [-1, 1] for j in [-1, 1] for k in [-1, 1]] + + [(0, i / PHI, j * PHI) for i in [-1, 1] for j in [-1, 1]] + + [(i / PHI, j * PHI, 0) for i in [-1, 1] for j in [-1, 1]] + + [(i * PHI, 0, j / PHI) for i in [-1, 1] for j in [-1, 1]] + ) + + icosahedron_vertices = ( + [(0, i, j * PHI) for i in [-1, 1] for j in [-1, 1]] + + [(i, j * PHI, 0) for i in [-1, 1] for j in [-1, 1]] + + [(i * PHI, 0, j) for i in [-1, 1] for j in [-1, 1]] + ) + + vertices_lookup = { + 4: tetrahedron_vertices, + 6: cube_vertices, + 8: octahedron_vertices, + 12: dodecahedron_vertices, + 20: icosahedron_vertices, + } + _applies_to = [BuildPart._tag] + + def __init__( + self, + face_count: Literal[4, 6, 8, 12, 20], + diameter: float = 1.0, + rotation: RotationLike = (0, 0, 0), + align: Union[None, Align, tuple[Align, Align, Align]] = None, + mode: Mode = Mode.ADD, + ): + try: + platonic_vertices = PlatonicSolid.vertices_lookup[face_count] + except KeyError: + raise ValueError( + f"face_count must be one of 4, 6, 8, 12, or 20 not {face_count}" + ) + + # Create a convex hull from the vertices + hull = ConvexHull(platonic_vertices).simplices.tolist() + + # Create faces from the vertex indices + platonic_faces = [] + for face_vertex_indices in hull: + corner_vertices = [platonic_vertices[i] for i in face_vertex_indices] + platonic_faces.append( + Face.make_from_wires(Wire.make_polygon(corner_vertices)) + ) + + # Create the solid from the Faces + platonic_solid = Solid.make_solid(Shell.make_shell(platonic_faces)).clean() + + # By definition, all vertices are the same distance from the origin so + # scale proportionally to this distance + platonic_solid = platonic_solid.scale( + (diameter / 2) / Vector(platonic_solid.vertices()[0]).length + ) + + super().__init__(part=platonic_solid, rotation=rotation, align=align, mode=mode) + + +solids = [ + Rot(0, 0, 72 * i) * Pos(1, 0, 0) * PlatonicSolid(faces) + for i, faces in enumerate([4, 6, 8, 12, 20]) +] +show(solids) + +# [End] diff --git a/examples/stud_wall.py b/examples/stud_wall.py new file mode 100644 index 00000000..c38725cc --- /dev/null +++ b/examples/stud_wall.py @@ -0,0 +1,148 @@ +""" +Stud Wall creation using RigidJoints to position components. + +name: stud_wall.py +by: Gumyr +date: February 17, 2024 + +desc: + This example builds stud walls from dimensional lumber as an assembly + with the parts positioned with RigidJoints. + +license: + + Copyright 2024 Gumyr + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +""" + +from build123d import * +from ocp_vscode import show +from typing import Union +import copy + + +# [Code] +class Stud(BasePartObject): + """Part Object: Stud + + Create a dimensional framing stud. + + Args: + length (float): stud size + width (float): stud size + thickness (float): stud size + rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0). + align (Union[Align, tuple[Align, Align, Align]], optional): align min, center, + or max of object. Defaults to (Align.CENTER, Align.CENTER, Align.MIN). + mode (Mode, optional): combine mode. Defaults to Mode.ADD. + """ + + _applies_to = [BuildPart._tag] + + def __init__( + self, + length: float = 8 * FT, + width: float = 3.5 * IN, + thickness: float = 1.5 * IN, + rotation: RotationLike = (0, 0, 0), + align: Union[None, Align, tuple[Align, Align, Align]] = ( + Align.CENTER, + Align.CENTER, + Align.MIN, + ), + mode: Mode = Mode.ADD, + ): + self.length = length + self.width = width + self.thickness = thickness + + # Create the basic shape + with BuildPart() as stud: + with BuildSketch(): + RectangleRounded(thickness, width, 0.25 * IN) + extrude(amount=length) + + # Create a Part object with appropriate alignment and rotation + super().__init__(part=stud.part, rotation=rotation, align=align, mode=mode) + + # Add joints to the ends of the stud + RigidJoint("end0", self, Location()) + RigidJoint("end1", self, Location((0, 0, length), (1, 0, 0), 180)) + + +class StudWall(Compound): + """StudWall + + A simple stud wall assembly with top and sole plates. + + Args: + length (float): wall length + depth (float, optional): stud width. Defaults to 3.5*IN. + height (float, optional): wall height. Defaults to 8*FT. + stud_spacing (float, optional): center-to-center. Defaults to 16*IN. + stud_thickness (float, optional): Defaults to 1.5*IN. + """ + + def __init__( + self, + length: float, + depth: float = 3.5 * IN, + height: float = 8 * FT, + stud_spacing: float = 16 * IN, + stud_thickness: float = 1.5 * IN, + ): + # Create the object that will be used for top and sole plates + plate = Stud( + length, + depth, + rotation=(0, -90, 0), + align=(Align.MIN, Align.CENTER, Align.MAX), + ) + # Define where studs will go on the plates + stud_locations = Pos(stud_thickness / 2, 0, stud_thickness) * GridLocations( + stud_spacing, 0, int(length / stud_spacing) + 1, 1, align=Align.MIN + ) + stud_locations.append(Pos(length - stud_thickness / 2, 0, stud_thickness)) + + # Create a single stud that will be copied for efficiency + stud = Stud(height - 2 * stud_thickness, depth, stud_thickness) + + # For efficiency studs in the walls are copies with their own position + studs = [] + for i, loc in enumerate(stud_locations): + stud_joint = RigidJoint(f"stud{i}", plate, loc) + stud_copy = copy.copy(stud) + stud_joint.connect_to(stud_copy.joints["end0"]) + studs.append(stud_copy) + top_plate = copy.copy(plate) + sole_plate = copy.copy(plate) + + # Position the top plate relative to the top of the first stud + studs[0].joints["end1"].connect_to(top_plate.joints["stud0"]) + + # Build the assembly of parts + super().__init__(children=[top_plate, sole_plate] + studs) + + # Add joints to the wall + RigidJoint("inside0", self, Location((depth / 2, depth / 2, 0), (0, 0, 1), 90)) + RigidJoint("end0", self, Location()) + + +x_wall = StudWall(13 * FT) +y_wall = StudWall(9 * FT) +x_wall.joints["inside0"].connect_to(y_wall.joints["end0"]) + +show(x_wall, y_wall, render_joints=False) +# [End]