diff --git a/docs/OpenSCAD.rst b/docs/OpenSCAD.rst new file mode 100644 index 0000000..5d2d0d2 --- /dev/null +++ b/docs/OpenSCAD.rst @@ -0,0 +1,149 @@ +Transitioning from OpenSCAD +=========================== + +Welcome to build123d! If you're familiar with OpenSCAD, you'll notice key differences in +how models are constructed. This guide is designed to help you adapt your design approach +and understand the fundamental differences in modeling philosophies. While OpenSCAD relies +heavily on Constructive Solid Geometry (CSG) to combine primitive 3D shapes like cubes and +spheres, build123d encourages a more flexible and efficient workflow based on building +lower-dimensional objects. + +Why Transition to build123d? +---------------------------- + +Transitioning to build123d allows you to harness a modern and efficient approach to 3D modeling. +By starting with lower-dimensional objects and leveraging powerful transformation tools, you can +create precise, complex designs with ease. This workflow emphasizes modularity and maintainability, +enabling quick modifications and reducing computational complexity. + +Moving Beyond Constructive Solid Geometry (CSG) +----------------------------------------------- + +OpenSCAD's modeling paradigm heavily relies on Constructive Solid Geometry (CSG) to build +models by combining and subtracting 3D solids. While build123d supports similar operations, +its design philosophy encourages a fundamentally different, often more efficient approach: +starting with lower-dimensional entities like faces and edges and then transforming them +into solids. + +### Why Transition Away from CSG? + +CSG is a powerful method for creating 3D models, but it has limitations when dealing with +complex designs. build123d’s approach offers several advantages: + +- **Simplified Complexity Management**: + Working with 2D profiles and faces instead of directly manipulating 3D solids simplifies + your workflow. In large models, the number of operations on solids can grow exponentially, + making it difficult to manage and debug. Building with 2D profiles helps keep designs + modular and organized. + +- **Improved Robustness**: + Operations on 2D profiles are inherently less computationally intensive and + less error-prone than equivalent operations on 3D solids. This robustness ensures smoother + workflows and reduces the likelihood of failing operations in complex models. + +- **Enhanced Efficiency**: + Constructing models from 2D profiles using operations like **extruding**, **lofting**, + **sweeping**, or **revolving** is computationally faster. These methods also provide + greater design flexibility, enabling you to create intricate forms with ease. + +- **Better Precision and Control**: + Starting with 2D profiles allows for more precise geometric control. Constraints, dimensions, + and relationships between entities can be established more effectively in 2D, ensuring a solid + foundation for your 3D design. + +Using a More Traditional CAD Design Workflow +-------------------------------------------- + +Most industry-standard CAD packages recommend starting with a sketch (a 2D object) and +transforming it into a 3D model—a design philosophy that is central to build123d. + +In build123d, the design process typically begins with defining the outline of an object. +This might involve creating a complex 1D object using **BuildLine**, which provides tools +for constructing intricate wireframe geometries. The next step involves converting these +1D objects into 2D sketches using **BuildSketch**, which offers a wide range of 2D primitives +and advanced capabilities, such as: + +- **make_face**: Converts a 1D **BuildLine** object into a planar 2D face. +- **make_hull**: Generates a convex hull from a 1D **BuildLine** object. + +Once a 2D profile is created, it can be transformed into 3D objects in a **BuildPart** context +using operations such as: + +- **Extrusion**: Extends a 2D profile along a straight path to create a 3D shape. +- **Revolution**: Rotates a 2D profile around an axis to form a symmetrical 3D object. +- **Lofting**: Connects multiple 2D profiles along a path to create smooth transitions + between shapes. +- **Sweeping**: Moves a 2D profile along a defined path to create a 3D form. + +### Refining the Model + +After creating the initial 3D shape, you can refine the model by adding details or making +modifications using build123d's advanced features, such as: + +- **Fillets and Chamfers**: Smooth or bevel edges to enhance the design. +- **Boolean Operations**: Combine, subtract, or intersect 3D shapes to achieve the desired + geometry. + +### Example Comparison + +To illustrate the advantages of this approach, compare a simple model in OpenSCAD and +build123d: + +**OpenSCAD Approach** + +.. code-block:: openscad + + // A basic cylinder with a hole + difference() { + cylinder(r=10, h=20); + translate([0, 0, 5]) cylinder(r=5, h=20); + } + +**build123d Approach** + +.. code-block:: python + + from build123d import * + + # In Builder mode + with BuildPart() as cylinder_with_hole: + with BuildSketch(): + Circle(10) + extrude(amount=20) + with BuildSketch(cylinder_with_hole.faces().sort_by(Axis.Z).last): + Circle(5) + extrude(amount=-15, mode=Mode.SUBTRACT) + + # In Algebra mode + cyl = extrude(Circle(10), 20) + cyl -= extrude(Plane(cyl.faces().sort_by(Axis.Z)[-1]) * Circle + + +This approach emphasizes creating a 2D profile (such as the **Circle**) and then applying a +3D operation (like **extrude**) to achieve the desired result. Topological features of the +part under construction are extracted and used as references for adding further details. + +Tips for Transitioning +---------------------- + +- **Think in Lower Dimensions**: Begin with 1D curves or 2D sketches as the foundation + and progressively build upwards into 3D shapes. + +- **Leverage Topological References**: Use build123d's powerful selector system to + reference features of existing objects for creating new ones. For example, apply + inside or outside fillets and chamfers to vertices and edges of an existing part + with precision. + +- **Explore the Documentation**: Dive into build123d’s comprehensive API documentation + to unlock its full potential and discover advanced features. + +By shifting your design mindset from solid-based CSG to a profile-driven approach, you +can fully harness build123d's capabilities to create precise, efficient, and complex models. +Welcome aboard, and happy designing! + +Conclusion +---------- +While OpenSCAD and build123d share the goal of empowering users to create parametric 3D +models, their approaches differ significantly. Embracing build123d’s workflow of building +with lower-dimensional objects and applying extrusion, lofting, sweeping, or revolution +will unlock its full potential and lead to better design outcomes. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index f5bf239..7a76273 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -106,7 +106,9 @@ Table Of Contents introduction.rst installation.rst key_concepts.rst + key_concepts_builder.rst key_concepts_algebra.rst + OpenSCAD.rst introductory_examples.rst tutorials.rst objects.rst diff --git a/docs/key_concepts.rst b/docs/key_concepts.rst index e8a7756..1b2f11b 100644 --- a/docs/key_concepts.rst +++ b/docs/key_concepts.rst @@ -1,14 +1,6 @@ -########################### -Key Concepts (builder mode) -########################### - -There are two primary APIs provided by build123d: builder and algebra. The builder -API may be easier for new users as it provides some assistance and shortcuts; however, -if you know what a Quaternion is you might prefer the algebra API which allows -CAD objects to be created in the style of mathematical equations. Both API can -be mixed in the same model with the exception that the algebra API can't be used -from within a builder context. As with music, there is no "best" genre or API, -use the one you prefer or both if you like. +############ +Key Concepts +############ The following key concepts will help new users understand build123d quickly. @@ -120,118 +112,6 @@ topology of a shape as shown here for a unit cube: Users of build123d will often reference topological objects as part of the process of creating the object as described below. -Builders -======== - -The three builders, ``BuildLine``, ``BuildSketch``, and ``BuildPart`` are tools to create -new objects - not the objects themselves. Each of the objects and operations applicable -to these builders create objects of the standard CadQuery Direct API, most commonly -``Compound`` objects. This is opposed to CadQuery's Fluent API which creates objects -of the ``Workplane`` class which frequently needed to be converted back to base -class for further processing. - -One can access the objects created by these builders by referencing the appropriate -instance variable. For example: - -.. code-block:: python - - with BuildPart() as my_part: - ... - - show_object(my_part.part) - -.. code-block:: python - - with BuildSketch() as my_sketch: - ... - - show_object(my_sketch.sketch) - -.. code-block:: python - - with BuildLine() as my_line: - ... - - show_object(my_line.line) - -Implicit Builder Instance Variables -=================================== - -One might expect to have to reference a builder's instance variable when using -objects or operations that impact that builder like this: - -.. code-block:: python - - with BuildPart() as part_builder: - Box(part_builder, 10,10,10) - -Instead, build123d determines from the scope of the object or operation which -builder it applies to thus eliminating the need for the user to provide this -information - as follows: - -.. code-block:: python - - with BuildPart() as part_builder: - Box(10,10,10) - with BuildSketch() as sketch_builder: - Circle(2) - -In this example, ``Box`` is in the scope of ``part_builder`` while ``Circle`` -is in the scope of ``sketch_builder``. - -Workplanes -========== - -As build123d is a 3D CAD package one must be able to position objects anywhere. As one -frequently will work in the same plane for a sequence of operations, the first parameter(s) -of the builders is a (sequence of) workplane(s) which is (are) used -to aid in the location of features. The default workplane in most cases is the ``Plane.XY`` -where a tuple of numbers represent positions on the x and y axes. However workplanes can -be generated on any plane which allows users to put a workplane where they are working -and then work in local 2D coordinate space. - - -.. code-block:: python - - with BuildPart(Plane.XY) as example: - ... # a 3D-part - with BuildSketch(example.faces().sort_by(sort_by=Axis.Z)[0]) as bottom: - ... - with BuildSketch(Plane.XZ) as vertical: - ... - with BuildSketch(example.faces().sort_by(sort_by=Axis.Z)[-1]) as top: - ... - -When ``BuildPart`` is invoked it creates the workplane provided as a parameter (which has a -default of the ``Plane.XY``). The ``bottom`` sketch is therefore created on the ``Plane.XY`` but with the -normal reversed to point down. Subsequently the user has created the ``vertical`` (``Plane.XZ``) sketch. -All objects or operations within the scope of a workplane will automatically be orientated with -respect to this plane so the user only has to work with local coordinates. - -As shown above, workplanes can be created from faces as well. The ``top`` sketch is -positioned on top of ``example`` by selecting its faces and finding the one with the greatest z value. - -One is not limited to a single workplane at a time. In the following example all six -faces of the first box are used to define workplanes which are then used to position -rotated boxes. - -.. code-block:: python - - import build123d as bd - - with bd.BuildPart() as bp: - bd.Box(3, 3, 3) - with bd.BuildSketch(*bp.faces()): - bd.Rectangle(1, 2, rotation=45) - bd.extrude(amount=0.1) - -This is the result: - -.. image:: boxes_on_faces.svg - :align: center - -.. _location_context_link: - Location ======== @@ -286,182 +166,7 @@ There are also four methods that are used to change the location of objects: Locations can be combined with the ``*`` operator and have their direction flipped with the ``-`` operator. -Locations Context -================= - -When positioning objects or operations within a builder Location Contexts are used. These -function in a very similar was to the builders in that they create a context where one or -more locations are active within a scope. For example: - -.. code-block:: python - - with BuildPart(): - with Locations((0,10),(0,-10)): - Box(1,1,1) - with GridLocations(x_spacing=5, y_spacing=5, x_count=2, y_count=2): - Sphere(1) - Cylinder(1,1) - -In this example ``Locations`` creates two positions on the current workplane at (0,10) and (0,-10). -Since ``Box`` is within the scope of ``Locations``, two boxes are created at these locations. The -``GridLocations`` context creates four positions which apply to the ``Sphere``. The ``Cylinder`` is -out of the scope of ``GridLocations`` but in the scope of ``Locations`` so two cylinders are created. - -Note that these contexts are creating Location objects not just simple points. The difference -isn't obvious until the ``PolarLocations`` context is used which can also rotate objects within -its scope - much as the hour and minute indicator on an analogue clock. - -Also note that the locations are local to the current location(s) - i.e. ``Locations`` can be -nested. It's easy for a user to retrieve the global locations: - -.. code-block:: python - - with Locations(Plane.XY, Plane.XZ): - locs = GridLocations(1, 1, 2, 2) - for l in locs: - print(l) - -.. code-block:: - - Location(p=(-0.50,-0.50,0.00), o=(0.00,-0.00,0.00)) - Location(p=(-0.50,0.50,0.00), o=(0.00,-0.00,0.00)) - Location(p=(0.50,-0.50,0.00), o=(0.00,-0.00,0.00)) - Location(p=(0.50,0.50,0.00), o=(0.00,-0.00,0.00)) - Location(p=(-0.50,-0.00,-0.50), o=(90.00,-0.00,0.00)) - Location(p=(-0.50,0.00,0.50), o=(90.00,-0.00,0.00)) - Location(p=(0.50,0.00,-0.50), o=(90.00,-0.00,0.00)) - Location(p=(0.50,0.00,0.50), o=(90.00,-0.00,0.00)) - - -Operation Inputs -================ - -When one is operating on an existing object, e.g. adding a fillet to a part, -an iterable of objects is often required (often a ShapeList). - -Here is the definition of :meth:`~operations_generic.fillet` to help illustrate: - -.. code-block:: python - - def fillet( - objects: Union[Union[Edge, Vertex], Iterable[Union[Edge, Vertex]]], - radius: float, - ): - -To use this fillet operation, an edge or vertex or iterable of edges or -vertices must be provided followed by a fillet radius with or without the keyword as follows: - -.. code-block:: python - - with BuildPart() as pipes: - Box(10, 10, 10, rotation=(10, 20, 30)) - ... - fillet(pipes.edges(Select.LAST), radius=0.2) - -Here the fillet accepts the iterable ShapeList of edges from the last operation of -the ``pipes`` builder and a radius is provided as a keyword argument. - -Combination Modes -================= - -Almost all objects or operations have a ``mode`` parameter which is defined by the -``Mode`` Enum class as follows: - -.. code-block:: python - - class Mode(Enum): - ADD = auto() - SUBTRACT = auto() - INTERSECT = auto() - REPLACE = auto() - PRIVATE = auto() - -The ``mode`` parameter describes how the user would like the object or operation to -interact with the object within the builder. For example, ``Mode.ADD`` will -integrate a new object(s) in with an existing ``part``. Note that a part doesn't -necessarily have to be a single object so multiple distinct objects could be added -resulting is multiple objects stored as a ``Compound`` object. As one might expect -``Mode.SUBTRACT``, ``Mode.INTERSECT``, and ``Mode.REPLACE`` subtract, intersect, or replace -(from) the builder's object. ``Mode.PRIVATE`` instructs the builder that this object -should not be combined with the builder's object in any way. - -Most commonly, the default ``mode`` is ``Mode.ADD`` but this isn't always true. -For example, the ``Hole`` classes use a default ``Mode.SUBTRACT`` as they remove -a volume from the part under normal circumstances. However, the ``mode`` used in -the ``Hole`` classes can be specified as ``Mode.ADD`` or ``Mode.INTERSECT`` to -help in inspection or debugging. - Selectors ========= .. include:: selectors.rst - -Using Locations & Rotating Objects -================================== - -build123d stores points (to be specific ``Location`` (s)) internally to be used as -positions for the placement of new objects. By default, a single location -will be created at the origin of the given workplane such that: - -.. code-block:: python - - with BuildPart() as pipes: - Box(10, 10, 10, rotation=(10, 20, 30)) - -will create a single 10x10x10 box centered at (0,0,0) - by default objects are -centered. One can create multiple objects by pushing points prior to creating -objects as follows: - -.. code-block:: python - - with BuildPart() as pipes: - with Locations((-10, -10, -10), (10, 10, 10)): - Box(10, 10, 10, rotation=(10, 20, 30)) - -which will create two boxes. - -To orient a part, a ``rotation`` parameter is available on ``BuildSketch``` and -``BuildPart`` APIs. When working in a sketch, the rotation is a single angle in -degrees so the parameter is a float. When working on a part, the rotation is -a three dimensional ``Rotation`` object of the form -``Rotation(, , )`` although a simple three tuple of -floats can be used as input. As 3D rotations are not cumulative, one can -combine rotations with the `*` operator like this: -``Rotation(10, 20, 30) * Rotation(0, 90, 0)`` to generate any desired rotation. - -.. hint:: - Experts Only - - ``Locations`` will accept ``Location`` objects for input which allows one - to specify both the position and orientation. However, the orientation - is often determined by the ``Plane`` that an object was created on. - ``Rotation`` is a subclass of ``Location`` and therefore will also accept - a position component. - -Builder's Pending Objects -========================= - -When a builder exits, it will push the object created back to its parent if -there was one. Here is an example: - -.. code-block:: python - - height, width, thickness, f_rad = 60, 80, 20, 10 - - with BuildPart() as pillow_block: - with BuildSketch() as plan: - Rectangle(width, height) - fillet(plan.vertices(), radius=f_rad) - extrude(amount=thickness) - -``BuildSketch`` exits after the ``fillet`` operation and when doing so it transfers -the sketch to the ``pillow_block`` instance of ``BuildPart`` as the internal instance variable -``pending_faces``. This allows the ``extrude`` operation to be immediately invoked as it -extrudes these pending faces into ``Solid`` objects. Likewise, ``loft`` would take all of the -``pending_faces`` and attempt to create a single ``Solid`` object from them. - -Normally the user will not need to interact directly with pending objects; however, -one can see pending Edges and Faces with ``.pending_edges`` and -``.pending_faces`` attributes. In the above example, by adding a -``print(pillow_block.pending_faces)`` prior to the ``extrude(amount=thickness)`` the -pending ``Face`` from the ``BuildSketch`` will be displayed. diff --git a/docs/key_concepts_builder.rst b/docs/key_concepts_builder.rst new file mode 100644 index 0000000..20370f3 --- /dev/null +++ b/docs/key_concepts_builder.rst @@ -0,0 +1,393 @@ +########################### +Key Concepts (builder mode) +########################### + +There are two primary APIs provided by build123d: builder and algebra. The builder +API may be easier for new users as it provides some assistance and shortcuts; however, +if you know what a Quaternion is you might prefer the algebra API which allows +CAD objects to be created in the style of mathematical equations. Both API can +be mixed in the same model with the exception that the algebra API can't be used +from within a builder context. As with music, there is no "best" genre or API, +use the one you prefer or both if you like. + +The following key concepts will help new users understand build123d quickly. + +Understanding the Builder Paradigm +================================== + +The **Builder** paradigm in build123d provides a powerful and intuitive way to construct +complex geometric models. At its core, the Builder works like adding a column of numbers +on a piece of paper: a running "total" is maintained internally as each new object is +added or modified. This approach simplifies the process of constructing models by breaking +it into smaller, incremental steps. + +How the Builder Works +---------------------- + +When using a Builder (such as **BuildLine**, **BuildSketch**, or **BuildPart**), the +following principles apply: + +1. **Running Total**: + - The Builder maintains an internal "total," which represents the current state of + the object being built. + - Each operation updates this total by combining the new object with the existing one. + +2. **Combination Modes**: + - Just as numbers in a column may have a `+` or `-` sign to indicate addition or + subtraction, Builders use **modes** to control how each object is combined with + the current total. + - Common modes include: + + - **ADD**: Adds the new object to the current total. + - **SUBTRACT**: Removes the new object from the current total. + - **INTERSECT**: Keeps only the overlapping regions of the new object and the current total. + - **REPLACE**: Entirely replace the running total. + - **PRIVATE**: Don't change the running total at all. + + - The mode can be set dynamically for each operation, allowing for flexible and precise modeling. + +3. **Extracting the Result**: + - At the end of the building process, the final object is accessed through the + Builder's attributes, such as ``.line``, ``.sketch``, or ``.part``, depending on + the Builder type. + - For example: + + - **BuildLine**: Use ``.line`` to retrieve the final wireframe geometry. + - **BuildSketch**: Use ``.sketch`` to extract the completed 2D profile. + - **BuildPart**: Use ``.part`` to obtain the 3D solid. + +Example Workflow +----------------- + +Here is an example of using a Builder to create a simple part: + +.. code-block:: python + + from build123d import * + + # Using BuildPart to create a 3D model + with BuildPart() as example_part: + with BuildSketch() as base_sketch: + Rectangle(20, 20) + extrude(amount=10) # Create a base block + with BuildSketch(Plane(example_part.faces().sort_by(Axis.Z).last)) as cut_sketch: + Circle(5) + extrude(amount=-5, mode=Mode.SUBTRACT) # Subtract a cylinder + + # Access the final part + result_part = example_part.part + +Key Concepts +------------ + +- **Incremental Construction**: + Builders allow you to build objects step-by-step, maintaining clarity and modularity. + +- **Dynamic Mode Switching**: + The **mode** parameter gives you precise control over how each operation modifies + the current total. + +- **Seamless Extraction**: + The Builder paradigm simplifies the retrieval of the final object, ensuring that you + always have access to the most up-to-date result. + +Analogy: Adding Numbers on Paper +-------------------------------- + +Think of the Builder as a running tally when adding numbers on a piece of paper: + +- Each number represents an operation or object. +- The ``+`` or ``-`` sign corresponds to the **ADD** or **SUBTRACT** mode. +- At the end, the total is the sum of all operations, which you can retrieve by referencing + the Builder’s output. + +By adopting this approach, build123d ensures a natural, intuitive workflow for constructing +2D and 3D models. + +Builders +======== + +The three builders, ``BuildLine``, ``BuildSketch``, and ``BuildPart`` are tools to create +new objects - not the objects themselves. Each of the objects and operations applicable +to these builders create objects of the standard CadQuery Direct API, most commonly +``Compound`` objects. This is opposed to CadQuery's Fluent API which creates objects +of the ``Workplane`` class which frequently needed to be converted back to base +class for further processing. + +One can access the objects created by these builders by referencing the appropriate +instance variable. For example: + +.. code-block:: python + + with BuildPart() as my_part: + ... + + show_object(my_part.part) + +.. code-block:: python + + with BuildSketch() as my_sketch: + ... + + show_object(my_sketch.sketch) + +.. code-block:: python + + with BuildLine() as my_line: + ... + + show_object(my_line.line) + +Implicit Builder Instance Variables +=================================== + +One might expect to have to reference a builder's instance variable when using +objects or operations that impact that builder like this: + +.. code-block:: python + + with BuildPart() as part_builder: + Box(part_builder, 10,10,10) + +Instead, build123d determines from the scope of the object or operation which +builder it applies to thus eliminating the need for the user to provide this +information - as follows: + +.. code-block:: python + + with BuildPart() as part_builder: + Box(10,10,10) + with BuildSketch() as sketch_builder: + Circle(2) + +In this example, ``Box`` is in the scope of ``part_builder`` while ``Circle`` +is in the scope of ``sketch_builder``. + +Workplanes +========== + +As build123d is a 3D CAD package one must be able to position objects anywhere. As one +frequently will work in the same plane for a sequence of operations, the first parameter(s) +of the builders is a (sequence of) workplane(s) which is (are) used +to aid in the location of features. The default workplane in most cases is the ``Plane.XY`` +where a tuple of numbers represent positions on the x and y axes. However workplanes can +be generated on any plane which allows users to put a workplane where they are working +and then work in local 2D coordinate space. + + +.. code-block:: python + + with BuildPart(Plane.XY) as example: + ... # a 3D-part + with BuildSketch(example.faces().sort_by(sort_by=Axis.Z)[0]) as bottom: + ... + with BuildSketch(Plane.XZ) as vertical: + ... + with BuildSketch(example.faces().sort_by(sort_by=Axis.Z)[-1]) as top: + ... + +When ``BuildPart`` is invoked it creates the workplane provided as a parameter (which has a +default of the ``Plane.XY``). The ``bottom`` sketch is therefore created on the ``Plane.XY`` but with the +normal reversed to point down. Subsequently the user has created the ``vertical`` (``Plane.XZ``) sketch. +All objects or operations within the scope of a workplane will automatically be orientated with +respect to this plane so the user only has to work with local coordinates. + +As shown above, workplanes can be created from faces as well. The ``top`` sketch is +positioned on top of ``example`` by selecting its faces and finding the one with the greatest z value. + +One is not limited to a single workplane at a time. In the following example all six +faces of the first box are used to define workplanes which are then used to position +rotated boxes. + +.. code-block:: python + + import build123d as bd + + with bd.BuildPart() as bp: + bd.Box(3, 3, 3) + with bd.BuildSketch(*bp.faces()): + bd.Rectangle(1, 2, rotation=45) + bd.extrude(amount=0.1) + +This is the result: + +.. image:: boxes_on_faces.svg + :align: center + +.. _location_context_link: + +Locations Context +================= + +When positioning objects or operations within a builder Location Contexts are used. These +function in a very similar was to the builders in that they create a context where one or +more locations are active within a scope. For example: + +.. code-block:: python + + with BuildPart(): + with Locations((0,10),(0,-10)): + Box(1,1,1) + with GridLocations(x_spacing=5, y_spacing=5, x_count=2, y_count=2): + Sphere(1) + Cylinder(1,1) + +In this example ``Locations`` creates two positions on the current workplane at (0,10) and (0,-10). +Since ``Box`` is within the scope of ``Locations``, two boxes are created at these locations. The +``GridLocations`` context creates four positions which apply to the ``Sphere``. The ``Cylinder`` is +out of the scope of ``GridLocations`` but in the scope of ``Locations`` so two cylinders are created. + +Note that these contexts are creating Location objects not just simple points. The difference +isn't obvious until the ``PolarLocations`` context is used which can also rotate objects within +its scope - much as the hour and minute indicator on an analogue clock. + +Also note that the locations are local to the current location(s) - i.e. ``Locations`` can be +nested. It's easy for a user to retrieve the global locations: + +.. code-block:: python + + with Locations(Plane.XY, Plane.XZ): + locs = GridLocations(1, 1, 2, 2) + for l in locs: + print(l) + +.. code-block:: + + Location(p=(-0.50,-0.50,0.00), o=(0.00,-0.00,0.00)) + Location(p=(-0.50,0.50,0.00), o=(0.00,-0.00,0.00)) + Location(p=(0.50,-0.50,0.00), o=(0.00,-0.00,0.00)) + Location(p=(0.50,0.50,0.00), o=(0.00,-0.00,0.00)) + Location(p=(-0.50,-0.00,-0.50), o=(90.00,-0.00,0.00)) + Location(p=(-0.50,0.00,0.50), o=(90.00,-0.00,0.00)) + Location(p=(0.50,0.00,-0.50), o=(90.00,-0.00,0.00)) + Location(p=(0.50,0.00,0.50), o=(90.00,-0.00,0.00)) + + +Operation Inputs +================ + +When one is operating on an existing object, e.g. adding a fillet to a part, +an iterable of objects is often required (often a ShapeList). + +Here is the definition of :meth:`~operations_generic.fillet` to help illustrate: + +.. code-block:: python + + def fillet( + objects: Union[Union[Edge, Vertex], Iterable[Union[Edge, Vertex]]], + radius: float, + ): + +To use this fillet operation, an edge or vertex or iterable of edges or +vertices must be provided followed by a fillet radius with or without the keyword as follows: + +.. code-block:: python + + with BuildPart() as pipes: + Box(10, 10, 10, rotation=(10, 20, 30)) + ... + fillet(pipes.edges(Select.LAST), radius=0.2) + +Here the fillet accepts the iterable ShapeList of edges from the last operation of +the ``pipes`` builder and a radius is provided as a keyword argument. + +Combination Modes +================= + +Almost all objects or operations have a ``mode`` parameter which is defined by the +``Mode`` Enum class as follows: + +.. code-block:: python + + class Mode(Enum): + ADD = auto() + SUBTRACT = auto() + INTERSECT = auto() + REPLACE = auto() + PRIVATE = auto() + +The ``mode`` parameter describes how the user would like the object or operation to +interact with the object within the builder. For example, ``Mode.ADD`` will +integrate a new object(s) in with an existing ``part``. Note that a part doesn't +necessarily have to be a single object so multiple distinct objects could be added +resulting is multiple objects stored as a ``Compound`` object. As one might expect +``Mode.SUBTRACT``, ``Mode.INTERSECT``, and ``Mode.REPLACE`` subtract, intersect, or replace +(from) the builder's object. ``Mode.PRIVATE`` instructs the builder that this object +should not be combined with the builder's object in any way. + +Most commonly, the default ``mode`` is ``Mode.ADD`` but this isn't always true. +For example, the ``Hole`` classes use a default ``Mode.SUBTRACT`` as they remove +a volume from the part under normal circumstances. However, the ``mode`` used in +the ``Hole`` classes can be specified as ``Mode.ADD`` or ``Mode.INTERSECT`` to +help in inspection or debugging. + + +Using Locations & Rotating Objects +================================== + +build123d stores points (to be specific ``Location`` (s)) internally to be used as +positions for the placement of new objects. By default, a single location +will be created at the origin of the given workplane such that: + +.. code-block:: python + + with BuildPart() as pipes: + Box(10, 10, 10, rotation=(10, 20, 30)) + +will create a single 10x10x10 box centered at (0,0,0) - by default objects are +centered. One can create multiple objects by pushing points prior to creating +objects as follows: + +.. code-block:: python + + with BuildPart() as pipes: + with Locations((-10, -10, -10), (10, 10, 10)): + Box(10, 10, 10, rotation=(10, 20, 30)) + +which will create two boxes. + +To orient a part, a ``rotation`` parameter is available on ``BuildSketch``` and +``BuildPart`` APIs. When working in a sketch, the rotation is a single angle in +degrees so the parameter is a float. When working on a part, the rotation is +a three dimensional ``Rotation`` object of the form +``Rotation(, , )`` although a simple three tuple of +floats can be used as input. As 3D rotations are not cumulative, one can +combine rotations with the `*` operator like this: +``Rotation(10, 20, 30) * Rotation(0, 90, 0)`` to generate any desired rotation. + +.. hint:: + Experts Only + + ``Locations`` will accept ``Location`` objects for input which allows one + to specify both the position and orientation. However, the orientation + is often determined by the ``Plane`` that an object was created on. + ``Rotation`` is a subclass of ``Location`` and therefore will also accept + a position component. + +Builder's Pending Objects +========================= + +When a builder exits, it will push the object created back to its parent if +there was one. Here is an example: + +.. code-block:: python + + height, width, thickness, f_rad = 60, 80, 20, 10 + + with BuildPart() as pillow_block: + with BuildSketch() as plan: + Rectangle(width, height) + fillet(plan.vertices(), radius=f_rad) + extrude(amount=thickness) + +``BuildSketch`` exits after the ``fillet`` operation and when doing so it transfers +the sketch to the ``pillow_block`` instance of ``BuildPart`` as the internal instance variable +``pending_faces``. This allows the ``extrude`` operation to be immediately invoked as it +extrudes these pending faces into ``Solid`` objects. Likewise, ``loft`` would take all of the +``pending_faces`` and attempt to create a single ``Solid`` object from them. + +Normally the user will not need to interact directly with pending objects; however, +one can see pending Edges and Faces with ``.pending_edges`` and +``.pending_faces`` attributes. In the above example, by adding a +``print(pillow_block.pending_faces)`` prior to the ``extrude(amount=thickness)`` the +pending ``Face`` from the ``BuildSketch`` will be displayed.