[]{#index.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
{.align-center}
::: {#index.xhtml#about .section}
# About
Build123d is a Python-based, parametric (BREP) modeling framework for 2D and 3D CAD. Built on the Open Cascade geometric kernel, it provides a clean, fully Pythonic interface for creating precise models suitable for 3D printing, CNC machining, laser cutting, and other manufacturing processes. Models can be exported to popular CAD tools such as FreeCAD and SolidWorks.
Designed for modern, maintainable CAD-as-code, build123d combines clear architecture with expressive, algebraic modeling. It offers:
- Minimal or no internal state depending on mode
- Explicit 1D, 2D, and 3D geometry classes with well-defined operations
- Extensibility through subclassing and functional composition---no monkey patching
- Standards-compliant code (PEP 8, mypy, pylint) with rich pylance type hints
- Deep Python integration---selectors as lists, locations as iterables, and natural conversions (`Solid(shell)`{.docutils .literal .notranslate}, `tuple(Vector)`{.docutils .literal .notranslate})
- Operator-driven modeling (`obj += sub_obj`{.docutils .literal .notranslate}, `Plane.XZ * Pos(X=5) * Rectangle(1, 1)`{.docutils .literal .notranslate}) for algebraic, readable, and composable design logic
::: {.highlight-build123d .notranslate}
::: highlight
:::
:::
With build123d, intricate parametric models can be created in just a few lines of readable Python code---as demonstrated by the tea cup example below.
```{=html}
```
```{=html}
```
[Teacup Example]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
wall_thickness = 3 * MM
fillet_radius = wall_thickness * 0.49
with BuildPart() as tea_cup:
# Create the bowl of the cup as a revolved cross section
with BuildSketch(Plane.XZ) as bowl_section:
with BuildLine():
# Start & end points with control tangents
s = Spline(
(30 * MM, 10 * MM),
(69 * MM, 105 * MM),
tangents=((1, 0.5), (0.7, 1)),
tangent_scalars=(1.75, 1),
)
# Lines to finish creating ½ the bowl shape
Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)
make_face() # Create a filled 2D shape
revolve(axis=Axis.Z)
# Hollow out the bowl with openings on the top and bottom
offset(amount=-wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE))
# Add a bottom to the bowl
with Locations((0, 0, (s @ 0).Y)):
Cylinder(radius=(s @ 0).X, height=wall_thickness)
# Smooth out all the edges
fillet(tea_cup.edges(), radius=fillet_radius)
# Determine where the handle contacts the bowl
handle_intersections = [
tea_cup.part.find_intersection_points(
Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))
)[-1][0]
for vertical_offset in [35 * MM, 80 * MM]
]
# Create a path for handle creation
with BuildLine(Plane.XZ) as handle_path:
Spline(
handle_intersections[0] - (wall_thickness / 2, 0),
handle_intersections[0] + (35 * MM, 30 * MM),
handle_intersections[0] + (40 * MM, 60 * MM),
handle_intersections[1] - (wall_thickness / 2, 0),
tangents=((1, 1.25), (-0.2, -1)),
)
# Align the cross section to the beginning of the path
with BuildSketch(handle_path.line ^ 0) as handle_cross_section:
RectangleRounded(wall_thickness, 8 * MM, fillet_radius)
sweep() # Sweep handle cross section along path
assert abs(tea_cup.part.volume - 130326) < 1
show(tea_cup, names=["tea cup"])
:::
:::
```{=html}
```
``{=html}``{=html}
::: {.admonition .note}
Note
This documentation is available in [pdf](https://build123d.readthedocs.io/_/downloads/en/latest/pdf/){.reference .external}[ \[https://build123d.readthedocs.io/\_/downloads/en/latest/pdf/\]]{.link-target} and [epub](https://build123d.readthedocs.io/_/downloads/en/latest/epub/){.reference .external}[ \[https://build123d.readthedocs.io/\_/downloads/en/latest/epub/\]]{.link-target} formats for reference while offline.
:::
::: {.admonition .note}
Note
There is a [Discord](https://discord.com/invite/Bj9AQPsCfx){.reference .external}[ \[https://discord.com/invite/Bj9AQPsCfx\]]{.link-target} server (shared with CadQuery) where you can ask for help in the build123d channel.
:::
:::
::: {#index.xhtml#table-of-contents .section}
# Table Of Contents
::: {.toctree-wrapper .compound}
- [Introduction](#introduction.xhtml){.reference .internal}
- [Key Aspects](#introduction.xhtml#key-aspects){.reference .internal}
- [Advantages Over CadQuery](#introduction.xhtml#advantages-over-cadquery){.reference .internal}
- [Installation](#installation.xhtml){.reference .internal}
- [Install build123d from GitHub:](#installation.xhtml#install-build123d-from-github){.reference .internal}
- [Development install of build123d:](#installation.xhtml#development-install-of-build123d){.reference .internal}
- [Test your build123d installation:](#installation.xhtml#test-your-build123d-installation){.reference .internal}
- [Adding a nicer GUI](#installation.xhtml#adding-a-nicer-gui){.reference .internal}
- [Key Concepts](#key_concepts.xhtml){.reference .internal}
- [Topology](#key_concepts.xhtml#topology){.reference .internal}
- [Location](#key_concepts.xhtml#location){.reference .internal}
- [Selectors](#key_concepts.xhtml#selectors){.reference .internal}
- [Key Concepts (builder mode)](#key_concepts_builder.xhtml){.reference .internal}
- [Understanding the Builder Paradigm](#key_concepts_builder.xhtml#understanding-the-builder-paradigm){.reference .internal}
- [Builders](#key_concepts_builder.xhtml#builders){.reference .internal}
- [Implicit Builder Instance Variables](#key_concepts_builder.xhtml#implicit-builder-instance-variables){.reference .internal}
- [Workplanes](#key_concepts_builder.xhtml#workplanes){.reference .internal}
- [Locations Context](#key_concepts_builder.xhtml#locations-context){.reference .internal}
- [Operation Inputs](#key_concepts_builder.xhtml#operation-inputs){.reference .internal}
- [Combination Modes](#key_concepts_builder.xhtml#combination-modes){.reference .internal}
- [Using Locations & Rotating Objects](#key_concepts_builder.xhtml#using-locations-rotating-objects){.reference .internal}
- [Builder's Pending Objects](#key_concepts_builder.xhtml#builder-s-pending-objects){.reference .internal}
- [Key Concepts (algebra mode)](#key_concepts_algebra.xhtml){.reference .internal}
- [Object arithmetic](#key_concepts_algebra.xhtml#object-arithmetic){.reference .internal}
- [Placement arithmetic](#key_concepts_algebra.xhtml#placement-arithmetic){.reference .internal}
- [Combing both concepts](#key_concepts_algebra.xhtml#combing-both-concepts){.reference .internal}
- [Moving Objects](#moving_objects.xhtml){.reference .internal}
- [Builder Mode](#moving_objects.xhtml#builder-mode){.reference .internal}
- [Algebra Mode](#moving_objects.xhtml#algebra-mode){.reference .internal}
- [Direct Manipulation Methods](#moving_objects.xhtml#direct-manipulation-methods){.reference .internal}
- [Transitioning from OpenSCAD](#OpenSCAD.xhtml){.reference .internal}
- [Why Transition to build123d?](#OpenSCAD.xhtml#why-transition-to-build123d){.reference .internal}
- [Moving Beyond Constructive Solid Geometry (CSG)](#OpenSCAD.xhtml#moving-beyond-constructive-solid-geometry-csg){.reference .internal}
- [Using a More Traditional CAD Design Workflow](#OpenSCAD.xhtml#using-a-more-traditional-cad-design-workflow){.reference .internal}
- [Tips for Transitioning](#OpenSCAD.xhtml#tips-for-transitioning){.reference .internal}
- [Conclusion](#OpenSCAD.xhtml#conclusion){.reference .internal}
- [Introductory Examples](#introductory_examples.xhtml){.reference .internal}
- [1. Simple Rectangular Plate](#introductory_examples.xhtml#simple-rectangular-plate){.reference .internal}
- [2. Plate with Hole](#introductory_examples.xhtml#plate-with-hole){.reference .internal}
- [3. An extruded prismatic solid](#introductory_examples.xhtml#an-extruded-prismatic-solid){.reference .internal}
- [4. Building Profiles using lines and arcs](#introductory_examples.xhtml#building-profiles-using-lines-and-arcs){.reference .internal}
- [5. Moving the current working point](#introductory_examples.xhtml#moving-the-current-working-point){.reference .internal}
- [6. Using Point Lists](#introductory_examples.xhtml#using-point-lists){.reference .internal}
- [7. Polygons](#introductory_examples.xhtml#polygons){.reference .internal}
- [8. Polylines](#introductory_examples.xhtml#polylines){.reference .internal}
- [9. Selectors, Fillets, and Chamfers](#introductory_examples.xhtml#selectors-fillets-and-chamfers){.reference .internal}
- [10. Select Last and Hole](#introductory_examples.xhtml#select-last-and-hole){.reference .internal}
- [11. Use a face as a plane for BuildSketch and introduce GridLocations](#introductory_examples.xhtml#use-a-face-as-a-plane-for-buildsketch-and-introduce-gridlocations){.reference .internal}
- [12. Defining an Edge with a Spline](#introductory_examples.xhtml#defining-an-edge-with-a-spline){.reference .internal}
- [13. CounterBoreHoles, CounterSinkHoles, and PolarLocations](#introductory_examples.xhtml#counterboreholes-countersinkholes-and-polarlocations){.reference .internal}
- [14. Position on a line with '@', '%' and introduce Sweep](#introductory_examples.xhtml#position-on-a-line-with-and-introduce-sweep){.reference .internal}
- [15. Mirroring Symmetric Geometry](#introductory_examples.xhtml#mirroring-symmetric-geometry){.reference .internal}
- [16. Mirroring 3D Objects](#introductory_examples.xhtml#mirroring-3d-objects){.reference .internal}
- [17. Mirroring From Faces](#introductory_examples.xhtml#mirroring-from-faces){.reference .internal}
- [18. Creating Workplanes on Faces](#introductory_examples.xhtml#creating-workplanes-on-faces){.reference .internal}
- [19. Locating a workplane on a vertex](#introductory_examples.xhtml#locating-a-workplane-on-a-vertex){.reference .internal}
- [20. Offset Sketch Workplane](#introductory_examples.xhtml#offset-sketch-workplane){.reference .internal}
- [21. Create a Workplanes in the center of another shape](#introductory_examples.xhtml#create-a-workplanes-in-the-center-of-another-shape){.reference .internal}
- [22. Rotated Workplanes](#introductory_examples.xhtml#rotated-workplanes){.reference .internal}
- [23. Revolve](#introductory_examples.xhtml#revolve){.reference .internal}
- [24. Loft](#introductory_examples.xhtml#loft){.reference .internal}
- [25. Offset Sketch](#introductory_examples.xhtml#offset-sketch){.reference .internal}
- [26. Offset Part To Create Thin features](#introductory_examples.xhtml#offset-part-to-create-thin-features){.reference .internal}
- [27. Splitting an Object](#introductory_examples.xhtml#splitting-an-object){.reference .internal}
- [28. Locating features based on Faces](#introductory_examples.xhtml#locating-features-based-on-faces){.reference .internal}
- [29. The Classic OCC Bottle](#introductory_examples.xhtml#the-classic-occ-bottle){.reference .internal}
- [30. Bezier Curve](#introductory_examples.xhtml#bezier-curve){.reference .internal}
- [31. Nesting Locations](#introductory_examples.xhtml#nesting-locations){.reference .internal}
- [32. Python For-Loop](#introductory_examples.xhtml#python-for-loop){.reference .internal}
- [33. Python Function and For-Loop](#introductory_examples.xhtml#python-function-and-for-loop){.reference .internal}
- [34. Embossed and Debossed Text](#introductory_examples.xhtml#embossed-and-debossed-text){.reference .internal}
- [35. Slots](#introductory_examples.xhtml#slots){.reference .internal}
- [36. Extrude Until](#introductory_examples.xhtml#extrude-until){.reference .internal}
- [Tutorials](#tutorials.xhtml){.reference .internal}
- [Designing a Part in build123d](#tutorial_design.xhtml){.reference .internal}
- [Selector Tutorial](#tutorial_selectors.xhtml){.reference .internal}
- [Lego Tutorial](#tutorial_lego.xhtml){.reference .internal}
- [Joint Tutorial](#tutorial_joints.xhtml){.reference .internal}
- [The build123d Examples](#examples_1.xhtml){.reference .internal}
- [Too Tall Toby (TTT) Tutorials](#tttt.xhtml){.reference .internal}
- [Surface Modeling](#tutorial_surface_modeling.xhtml){.reference .internal}
- [Technical Drawing Tutorial](#tech_drawing_tutorial.xhtml){.reference .internal}
- [Objects](#objects.xhtml){.reference .internal}
- [Align](#objects.xhtml#align){.reference .internal}
- [Mode](#objects.xhtml#mode){.reference .internal}
- [1D Objects](#objects.xhtml#d-objects){.reference .internal}
- [2D Objects](#objects.xhtml#id1){.reference .internal}
- [3D Objects](#objects.xhtml#id3){.reference .internal}
- [Custom Objects](#objects.xhtml#custom-objects){.reference .internal}
- [Operations](#operations.xhtml){.reference .internal}
- [Reference](#operations.xhtml#reference){.reference .internal}
- [Topology Selection and Exploration](#topology_selection.xhtml){.reference .internal}
- [Selectors](#topology_selection.xhtml#selectors){.reference .internal}
- [Operators](#topology_selection.xhtml#operators){.reference .internal}
- [Builders](#builders.xhtml){.reference .internal}
- [BuildLine](#build_line.xhtml){.reference .internal}
- [BuildSketch](#build_sketch.xhtml){.reference .internal}
- [BuildPart](#build_part.xhtml){.reference .internal}
- [Joints](#joints.xhtml){.reference .internal}
- [Rigid Joint](#joints.xhtml#rigid-joint){.reference .internal}
- [Revolute Joint](#joints.xhtml#revolute-joint){.reference .internal}
- [Linear Joint](#joints.xhtml#linear-joint){.reference .internal}
- [Cylindrical Joint](#joints.xhtml#cylindrical-joint){.reference .internal}
- [Ball Joint](#joints.xhtml#ball-joint){.reference .internal}
- [Assemblies](#assemblies.xhtml){.reference .internal}
- [Assigning Labels](#assemblies.xhtml#assigning-labels){.reference .internal}
- [Create the Assembly Compound](#assemblies.xhtml#create-the-assembly-compound){.reference .internal}
- [Shallow vs. Deep Copies of Shapes](#assemblies.xhtml#shallow-vs-deep-copies-of-shapes){.reference .internal}
- [Shapes are Anytree Nodes](#assemblies.xhtml#shapes-are-anytree-nodes){.reference .internal}
- [Iterating Over Compounds](#assemblies.xhtml#iterating-over-compounds){.reference .internal}
- [pack](#assemblies.xhtml#id1){.reference .internal}
- [Tips, Best Practices and FAQ](#tips.xhtml){.reference .internal}
- [Can't Get There from Here](#tips.xhtml#can-t-get-there-from-here){.reference .internal}
- [2D before 3D](#tips.xhtml#d-before-3d){.reference .internal}
- [Delay Chamfers and Fillets](#tips.xhtml#delay-chamfers-and-fillets){.reference .internal}
- [Parameterize](#tips.xhtml#parameterize){.reference .internal}
- [Use Shallow Copies](#tips.xhtml#use-shallow-copies){.reference .internal}
- [Object Selection](#tips.xhtml#object-selection){.reference .internal}
- [Build123d - CadQuery Integration](#tips.xhtml#build123d-cadquery-integration){.reference .internal}
- [Self Intersection](#tips.xhtml#self-intersection){.reference .internal}
- [Packing Objects on a Plane](#tips.xhtml#packing-objects-on-a-plane){.reference .internal}
- [Isn't `from build123d import *`{.docutils .literal .notranslate} bad practice?](#tips.xhtml#isnt-from-build123d-import-bad-practice){.reference .internal}
- [Why doesn't BuildSketch(Plane.XZ) work?](#tips.xhtml#why-doesn-t-buildsketch-plane-xz-work){.reference .internal}
- [Why is BuildLine not working as expected within the scope of BuildSketch?](#tips.xhtml#why-is-buildline-not-working-as-expected-within-the-scope-of-buildsketch){.reference .internal}
- [Don't Builders inherit workplane/coordinate systems when nested](#tips.xhtml#don-t-builders-inherit-workplane-coordinate-systems-when-nested){.reference .internal}
- [Import/Export](#import_export.xhtml){.reference .internal}
- [File Formats](#import_export.xhtml#file-formats){.reference .internal}
- [2D Exporters](#import_export.xhtml#d-exporters){.reference .internal}
- [3D Exporters](#import_export.xhtml#module-exporters3d){.reference .internal}
- [2D Importers](#import_export.xhtml#module-importers){.reference .internal}
- [3D Importers](#import_export.xhtml#id2){.reference .internal}
- [Advanced Topics](#advanced.xhtml){.reference .internal}
- [Performance considerations in algebra mode](#algebra_performance.xhtml){.reference .internal}
- [Location arithmetic for algebra mode](#location_arithmetic.xhtml){.reference .internal}
- [Algebraic definition](#algebra_definition.xhtml){.reference .internal}
- [CAD Object Centers](#center.xhtml){.reference .internal}
- [Debugging & Logging](#debugging_logging.xhtml){.reference .internal}
- [Cheat Sheet](#cheat_sheet.xhtml){.reference .internal}
- [External Tools and Libraries](#external.xhtml){.reference .internal}
- [Editors & Viewers](#external.xhtml#editors-viewers){.reference .internal}
- [Part Libraries](#external.xhtml#part-libraries){.reference .internal}
- [Tools](#external.xhtml#tools){.reference .internal}
- [Builder Common API Reference](#builder_api_reference.xhtml){.reference .internal}
- [Selector Methods](#builder_api_reference.xhtml#selector-methods){.reference .internal}
- [Enums](#builder_api_reference.xhtml#module-build_enums){.reference .internal}
- [Locations](#builder_api_reference.xhtml#locations){.reference .internal}
- [Direct API Reference](#direct_api_reference.xhtml){.reference .internal}
- [Geometric Objects](#direct_api_reference.xhtml#geometric-objects){.reference .internal}
- [Topological Objects](#direct_api_reference.xhtml#topological-objects){.reference .internal}
- [Import/Export](#direct_api_reference.xhtml#import-export){.reference .internal}
- [Joint Object](#direct_api_reference.xhtml#joint-object){.reference .internal}
:::
::: {#index.xhtml#indices-and-tables .section}
## Indices and tables
- [[Index]{.std .std-ref}](#genindex.xhtml){.reference .internal}
- [[Module Index]{.std .std-ref}](#py-modindex.xhtml){.reference .internal}
- [[Search Page]{.std .std-ref}](search.xhtml){.reference .internal}
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#introduction.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#introduction.xhtml#introduction .section}
# Introduction
::: {#introduction.xhtml#key-aspects .section}
## Key Aspects
The following sections describe some of the key aspects of build123d and illustrate why one might choose this open source system over proprietary options like SolidWorks, OnShape, Fusion 360, or even other open source systems like Blender, or OpenSCAD.
::: {#introduction.xhtml#boundary-representation-brep-modelling .section}
### Boundary Representation (BREP) Modelling
Boundary representation (BREP) and mesh-based CAD systems are both used to create and manipulate 3D models, but they differ in the way they represent and store the models.
Advantages of BREP-based CAD systems (e.g. build123d & SolidWorks):
- Precision: BREP-based CAD systems use mathematical representations to define the shape of an object, which allows for more precise and accurate modeling of complex shapes.
- Topology: BREP-based CAD systems maintain topological information of the 3D model, such as edges, faces and vertices. This allows for more robust and stable modeling, such as Boolean operations.
- Analytical modeling: BREP-based CAD systems can take advantage of the topological information to perform analytical operations such as collision detection, mass properties calculations, and finite element analysis.
- Features-based modeling: BREP-based CAD systems are often feature-based, which means that the model is built by creating and modifying individual features, such as holes, fillets, and chamfers. This allows for parametric design and easy modification of the model.
- Efficient storage: BREP-based CAD systems use a compact representation to store the 3D model, which is more efficient than storing a large number of triangles used in mesh-based systems.
Advantages of Mesh-based CAD systems (e.g. Blender, OpenSCAD):
- Simplicity: Mesh-based CAD systems use a large number of triangles to represent the surface of an object, which makes them easy to use and understand.
- Real-time rendering: Mesh-based CAD systems can be rendered in real-time, which is useful for applications such as video games and virtual reality.
- Flexibility: Mesh-based CAD systems can be easily exported to other 3D modeling and animation software, which makes them a good choice for use in the entertainment industry.
- Handling of freeform surfaces: Mesh-based systems are better equipped to handle freeform surfaces, such as those found in organic shapes, as they do not rely on mathematical representation.
- Handling of large datasets: Mesh-based systems are more suitable for handling large datasets such as point clouds, as they can be easily converted into a mesh representation.
:::
::: {#introduction.xhtml#parameterized-models .section}
### Parameterized Models
Parametrized CAD systems are more effective than non-parametric CAD systems in several ways:
- Reusability: Parametrized CAD models can be easily modified by changing a set of parameters, such as the length or width of an object, rather than having to manually edit the geometry. This makes it easy to create variations of a design without having to start from scratch.
- Design exploration: Parametrized CAD systems allow for easy exploration of different design options by changing the parameters and quickly visualizing the results. This can save a lot of time and effort during the design process.
- Constraints and relationships: Parametrized CAD systems allow for the definition of constraints and relationships between different parameters. This ensures that the model remains valid and functional even when parameters are changed.
- Automation: Parametrized CAD systems can be automated to perform repetitive tasks, such as generating detailed drawings or creating parts lists. This can save a lot of time and effort and reduce the risk of errors.
- Collaboration: Parametrized CAD systems allow different team members to work on different aspects of a design simultaneously and ensure that the model remains consistent across different stages of the development process.
- Document management: Parametrized CAD systems can generate engineering drawings, BOMs, and other documents automatically, which makes it easier to manage and track the design history.
In summary, parametrized CAD systems are more effective than non-parametric CAD systems because they provide a more efficient and flexible way to create and modify designs, and can be easily integrated into the design, manufacturing, and documentation process.
:::
::: {#introduction.xhtml#python-programming-language .section}
### Python Programming Language
Python is a popular, high-level programming language that has several advantages over other programming languages:
- Readability: Python code is easy to read and understand, with a clear and consistent syntax. This makes it a great language for beginners and for teams of developers who need to collaborate on a project.
- Versatility: Python is a general-purpose language that can be used for a wide range of tasks, including web development, scientific computing, data analysis, artificial intelligence, and more. This makes it a great choice for developers who need to work on multiple types of projects.
- Large community: Python has a large and active community of developers who contribute to the language and its ecosystem. This means that there are many libraries and frameworks available for developers to use, which can save a lot of time and effort.
- Good for data science, machine learning, and CAD: Python has a number of libraries such as numpy, pandas, scikit-learn, tensorflow, and cadquery which are popularly used in data science and machine learning and CAD.
- High-level language: Python is a high-level language, which means it abstracts away many of the low-level details of the computer. This makes it easy to write code quickly and focus on solving the problem at hand.
- Cross-platform: Python code runs on many different platforms, including Windows, Mac, and Linux, making it a great choice for developers who need to write code that runs on multiple operating systems.
- Open-source: Python is an open-source programming language, which means it is free to use and distribute. This makes it accessible to developers of all levels and budgets.
- Large number of libraries and modules: Python has a vast collection of libraries and modules that make it easy to accomplish complex tasks such as connecting to web servers, reading and modifying files, and connecting to databases.
:::
::: {#introduction.xhtml#open-source-software .section}
### Open Source Software
Open source and proprietary software systems are different in several ways: B Licensing: Open source software is licensed in a way that allows users to view, modify, and distribute the source code, while proprietary software is closed source and the source code is not available to the public.
- Ownership: Open source software is usually developed and maintained by a community of developers, while proprietary software is owned by a company or individual.
- Cost: Open source software is typically free to use, while proprietary software may require payment for a license or subscription. Customization: Open source software can be modified and customized by users and developers, while proprietary software is typically not modifiable by users.
- Support: Open source software may have a larger community of users who can provide support, while proprietary software may have a smaller community and relies on the company for support. Security: Open source software can be audited by a large community of developers, which can make it more secure, while proprietary software may have fewer eyes on the code and may be more vulnerable to security issues.
- Interoperability: Open source software may have better interoperability with other software and platforms, while proprietary software may have more limited compatibility.
- Reliability: Open source software can be considered as reliable as proprietary software. It is usually used by large companies, governments, and organizations and has been tested by a large number of users.
In summary, open source and proprietary software systems are different in terms of licensing, ownership, cost, customization, support, security, interoperability, and reliability. Open source software is typically free to use and can be modified by users and developers, while proprietary software is closed-source and may require payment for a license or subscription. Open source software may have a larger community of users who can provide support, while proprietary software may have a smaller community and relies on the company for support.
:::
::: {#introduction.xhtml#source-code-control-systems .section}
### Source Code Control Systems
Most GUI based CAD systems provide version control systems which represent the CAD design and its history. They allows developers to see changes made to the design over time, in a format that is easy to understand.
On the other hand, a source code control system like Git, is a command-line tool and it provides more granular control over the code. This makes it suitable for more advanced users and developers who are comfortable working with command-line interfaces. A source code control system like Git is more flexible and allows developers to perform tasks like branching and merging, which are not easily done with a GUI version control system. Systems like Git have several advantages, including:
- Version control: Git allows developers to keep track of changes made to the code over time, making it easy to revert to a previous version if necessary.
- Collaboration: Git makes it easy for multiple developers to work on the same codebase simultaneously, with the ability to merge changes from different branches of development.
- Backup: Git provides a way to backup and store the codebase in a remote repository, like GitHub. This can serve as a disaster recovery mechanism, in case of data loss.
- Branching: Git allows developers to create multiple branches of a project for different features or bug fixes, which can be easily merged into the main codebase once they are complete.
- Auditing: Git allows you to see who made changes to the code, when and what changes were made, which is useful for auditing and debugging.
- Open-source development: Git makes it easy for open-source developers to contribute to a project and share their work with the community.
- Flexibility: Git is a distributed version control system, which means that developers can work independently and offline. They can then push their changes to a remote repository when they are ready to share them with others.
In summary, GUI version control systems are generally more user-friendly and easier to use, while source code control systems like Git offer more flexibility and control over the code. Both can be used to achieve the same goal, but they cater to different types of users and use cases.
:::
::: {#introduction.xhtml#automated-testing .section}
### Automated Testing
Users of source based CAD systems can benefit from automated testing which improves their source code by:
- Finding bugs: Automated tests can detect bugs in the code, which can then be fixed before the code is released. This helps to ensure that the code is of higher quality and less likely to cause issues when used.
- Regression testing: Automated tests can be used to detect regressions, which are bugs that are introduced by changes to the codebase. This helps to ensure that changes to the code do not break existing functionality.
- Documenting code behavior: Automated tests can serve as documentation for how the code is supposed to behave. This makes it easier for developers to understand the code and make changes without breaking it.
- Improving code design: Writing automated tests often requires a good understanding of the code and how it is supposed to behave. This can lead to a better design of the code, as developers will have a better understanding of the requirements and constraints.
- Saving time and cost: Automated testing can save time and cost by reducing the need for manual testing. Automated tests can be run quickly and often, which means that bugs can be found and fixed early in the development process, which is less expensive than finding them later.
- Continuous integration and delivery: Automated testing can be integrated into a continuous integration and delivery (CI/CD) pipeline. This means that tests are run automatically every time code is committed and can be integrated with other tools such as code coverage, static analysis and more.
- Improving maintainability: Automated tests can improve the maintainability of the code by making it easier to refactor and change the codebase. This is because automated tests provide a safety net that ensures that changes to the code do not introduce new bugs.
Overall, automated testing is an essential part of the software development process, it helps to improve the quality of the code by detecting bugs early, documenting code behavior, and reducing the cost of maintaining and updating the code.
:::
::: {#introduction.xhtml#automated-documentation .section}
### Automated Documentation
The Sphinx automated documentation system was used to create the page you are reading now and can be used for user design documentation as well. Such systems are used for several reasons:
- Consistency: Sphinx and other automated documentation systems can generate documentation in a consistent format and style, which makes it easier to understand and use.
- Automation: Sphinx can automatically generate documentation from source code and comments, which saves time and effort compared to manually writing documentation.
- Up-to-date documentation: Automated documentation systems like Sphinx can update the documentation automatically when the code changes, ensuring that the documentation stays up-to-date with the code.
- Searchability: Sphinx and other automated documentation systems can include search functionality, which makes it easy to find the information you need.
- Cross-referencing: Sphinx can automatically create links between different parts of the documentation, making it easy to navigate and understand the relationships between different parts of the code.
- Customizable: Sphinx and other automated documentation systems can be customized to match the look and feel of your company's documentation.
- Multiple output formats: Sphinx can generate documentation in multiple formats such as HTML, PDF, ePub, and more.
- Support for multiple languages: Sphinx can generate documentation in multiple languages, which can make it easier to support international users.
- Integration with code management: Sphinx can be integrated with code management tools like Git, which allows documentation to be versioned along with the code.
In summary, automated documentation systems like Sphinx are used to generate consistent, up-to-date, and searchable documentation from source code and comments. They save time and effort compared to manual documentation, and can be customized to match the look and feel of your company's documentation. They also provide multiple output formats, support for multiple languages and can be integrated with code management tools.
:::
:::
::: {#introduction.xhtml#advantages-over-cadquery .section}
## Advantages Over CadQuery
As mentioned previously, the most significant advantage is that build123d is more pythonic. Specifically:
::: {#introduction.xhtml#standard-python-context-manager .section}
### Standard Python Context Manager
The creation of standard instance variables, looping and other normal python operations is enabled by the replacement of method chaining (fluent programming) with a standard python context manager.
::: {.highlight-python .notranslate}
::: highlight
# CadQuery Fluent API
pillow_block = (cq.Workplane("XY")
.box(height, width, thickness)
.edges("|Z")
.fillet(fillet)
.faces(">Z")
.workplane()
...
)
:::
:::
::: {.highlight-build123d .notranslate}
::: highlight
# build123d API
with BuildPart() as pillow_block:
with BuildSketch() as plan:
Rectangle(width, height)
fillet(plan.vertices(), radius=fillet)
extrude(thickness)
...
:::
:::
The use of the standard ``{=html}with``{=html} block allows standard python instructions to be inserted anyway in the code flow. One can insert a CQ-editor ``{=html}debug``{=html} or standard ``{=html}print``{=html} statement anywhere in the code without impacting functionality. Simple python ``{=html}for``{=html} loops can be used to repetitively create objects instead of forcing users into using more complex ``{=html}lambda``{=html} and ``{=html}iter``{=html} operations.
:::
::: {#introduction.xhtml#instantiated-objects .section}
### Instantiated Objects
Each object and operation is now a class instantiation that interacts with the active context implicitly for the user. These instantiations can be assigned to an instance variable as with standard python programming for direct use.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch() as plan:
r = Rectangle(width, height)
print(r.area)
...
:::
:::
:::
::: {#introduction.xhtml#operators .section}
### Operators
New operators have been created to extract information from objects created previously in the code. The ``{=html}@``{=html} operator extracts the position along an Edge or Wire while the ``{=html}%``{=html} operator extracts the tangent along an Edge or Wire. The position parameter are float values between 0.0 and 1.0 which represent the beginning and end of the line. In the following example, a spline is created from the end of l5 (``{=html}l5 @ 1``{=html}) to the beginning of l6 (``{=html}l6 @ 0``{=html}) with tangents equal to the tangents of l5 and l6 at their end and beginning respectively. Being able to extract information from existing features allows the user to "snap" new features to these points without knowing their numeric values.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildLine() as outline:
...
l5 = Polyline(...)
l6 = Polyline(...)
Spline(l5 @ 1, l6 @ 0, tangents=(l5 % 1, l6 % 0))
:::
:::
:::
::: {#introduction.xhtml#last-operation-objects .section}
### Last Operation Objects
All of the ``{=html}vertices()``{=html}, ``{=html}edges()``{=html}, ``{=html}faces()``{=html}, and ``{=html}solids()``{=html} methods of the builders can either return all of the objects requested or just the objects changed during the last operation. This allows the user to easily access features for further refinement, as shown in the following code where the final line selects the edges that were added by the last operation and fillets them. Such a selection would be quite difficult otherwise.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as pipes:
box = Box(10, 10, 10, rotation=(10, 20, 30))
with BuildSketch(*box.faces()) as pipe:
Circle(4)
extrude(amount=-5, mode=Mode.SUBTRACT)
with BuildSketch(*box.faces()) as pipe:
Circle(4.5)
Circle(4, mode=Mode.SUBTRACT)
extrude(amount=10)
fillet(pipes.edges(Select.LAST), 0.2)
:::
:::
:::
::: {#introduction.xhtml#extensions .section}
### Extensions
Extending build123d is relatively simple in that custom objects or operations can be created as new classes without the need to monkey patch any of the core functionality. These new classes will be seen in IDEs which is not possible with monkey patching the core CadQuery classes.
:::
::: {#introduction.xhtml#enums .section}
### Enums
All ``{=html}Literal``{=html} strings have been replaced with ``{=html}Enum``{=html} which allows IDEs to prompt users for valid options without having to refer to documentation.
:::
::: {#introduction.xhtml#selectors-replaced-by-lists .section}
### Selectors replaced by Lists
String based selectors have been replaced with standard python filters and sorting which opens up the full functionality of python lists. To aid the user, common operations have been optimized as shown here along with a fully custom selection:
::: {.highlight-build123d .notranslate}
::: highlight
top = rail.faces().filter_by(Axis.Z)[-1]
...
outside_vertices = filter(
lambda v: (v.Y == 0.0 or v.Y == height) and -width / 2 < v.X < width / 2,
din.vertices(),
)
:::
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#installation.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#installation.xhtml#installation .section}
# Installation
The recommended method for most users to install **build123d** is:
::: {.highlight-pycon .notranslate}
::: highlight
>>> pip install build123d
:::
:::
::: {.admonition .note}
Note
The [ocp-vscode](https://github.com/bernhard-42/vscode-ocp-cad-viewer){.reference .external}[ \[https://github.com/bernhard-42/vscode-ocp-cad-viewer\]]{.link-target} viewer has the ability to install **build123d**.
:::
::: {#installation.xhtml#install-build123d-from-github .section}
## Install build123d from GitHub:
To get the latest non-released version of **build123d** one can install from GitHub using one of the following two commands:
In Linux/MacOS, use the following command:
::: {.highlight-pycon .notranslate}
::: highlight
>>> python3 -m pip install git+https://github.com/gumyr/build123d
:::
:::
In Windows, use the following command:
::: {.highlight-pycon .notranslate}
::: highlight
>>> python -m pip install git+https://github.com/gumyr/build123d
:::
:::
If you receive errors about conflicting dependencies, you can retry the installation after having upgraded pip to the latest version with the following command:
::: {.highlight-pycon .notranslate}
::: highlight
>>> python3 -m pip install --upgrade pip
:::
:::
If you use [poetry](https://python-poetry.org/){.reference .external}[ \[https://python-poetry.org/\]]{.link-target} to install build123d, you can simply use:
::: {.highlight-pycon .notranslate}
::: highlight
>>> poetry add build123d
:::
:::
However, if you want the latest commit from GitHub you might need to specify the branch that is used for git-based installs; until quite recently, poetry used to checkout the ``{=html}master``{=html} branch when none was specified, and this fails on build123d that uses a ``{=html}dev``{=html} branch.
Pip does not suffer from this issue because it correctly fetches the repository default branch.
If you are a poetry user, you can work around this issue by installing build123d in the following way:
::: {.highlight-pycon .notranslate}
::: highlight
>>> poetry add git+https://github.com/gumyr/build123d.git@dev
:::
:::
Please note that always suffixing the URL with `@dev`{.docutils .literal .notranslate} is safe and will work with both older and recent versions of poetry.
:::
::: {#installation.xhtml#development-install-of-build123d .section}
## Development install of build123d:
**Warning**: it is highly recommended to upgrade pip to the latest version before installing build123d, especially in development mode. This can be done with the following command:
::: {.highlight-pycon .notranslate}
::: highlight
>>> python3 -m pip install --upgrade pip
:::
:::
Once pip is up-to-date, you can install build123d [in development mode](https://setuptools.pypa.io/en/latest/userguide/development_mode.html){.reference .external}[ \[https://setuptools.pypa.io/en/latest/userguide/development_mode.html\]]{.link-target} with the following commands:
::: {.highlight-pycon .notranslate}
::: highlight
>>> git clone https://github.com/gumyr/build123d.git
>>> cd build123d
>>> python3 -m pip install -e .
:::
:::
Please substitute `python3`{.docutils .literal .notranslate} with `python`{.docutils .literal .notranslate} in the lines above if you are using Windows.
If you're working directly with the OpenCascade `OCP`{.docutils .literal .notranslate} layer you will likely want to install the OCP stubs as follows:
::: {.highlight-pycon .notranslate}
::: highlight
>>> python3 -m pip install git+https://github.com/CadQuery/OCP-stubs@7.7.0
:::
:::
:::
::: {#installation.xhtml#test-your-build123d-installation .section}
## Test your build123d installation:
If all has gone well, you can open a command line/prompt, and type:
::: {.highlight-pycon .notranslate}
::: highlight
>>> python
>>> from build123d import *
>>> print(Solid.make_box(1,2,3).show_topology(limit_class="Face"))
:::
:::
Which should return something similar to:
::: {.highlight-default .notranslate}
::: highlight
Solid at 0x165e75379f0, Center(0.5, 1.0, 1.5)
└── Shell at 0x165eab056f0, Center(0.5, 1.0, 1.5)
├── Face at 0x165b35a3570, Center(0.0, 1.0, 1.5)
├── Face at 0x165e77957f0, Center(1.0, 1.0, 1.5)
├── Face at 0x165b3e730f0, Center(0.5, 0.0, 1.5)
├── Face at 0x165e8821570, Center(0.5, 2.0, 1.5)
├── Face at 0x165e88218f0, Center(0.5, 1.0, 0.0)
└── Face at 0x165eb21ee70, Center(0.5, 1.0, 3.0)
:::
:::
:::
::: {#installation.xhtml#adding-a-nicer-gui .section}
## Adding a nicer GUI
If you prefer to have a GUI available, your best option is to choose one from here: [[External Tools and Libraries]{.std .std-ref}](#external.xhtml#external){.reference .internal}
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#key_concepts.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#key_concepts.xhtml#key-concepts .section}
# Key Concepts
The following key concepts will help new users understand build123d quickly.
::: {#key_concepts.xhtml#topology .section}
[]{#key_concepts.xhtml#id1}
## Topology
Topology, in the context of 3D modeling and computational geometry, is the branch of mathematics that deals with the properties and relationships of geometric objects that are preserved under continuous deformations. In the context of CAD and modeling software like build123d, topology refers to the hierarchical structure of geometric elements (vertices, edges, faces, etc.) and their relationships in a 3D model. This structure defines how the components of a model are connected, enabling operations like Boolean operations, transformations, and analysis of complex shapes. Topology provides a formal framework for understanding and manipulating geometric data in a consistent and reliable manner.
The following are the topological objects that compose build123d objects:
[`Vertex`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: A Vertex is a data structure representing a 0D topological element. It defines a precise point in 3D space, often at the endpoints or intersections of edges in a 3D model. These vertices are part of the topological structure used to represent complex shapes in build123d.
[`Edge`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: An Edge in build123d is a fundamental geometric entity representing a 1D element in a 3D model. It defines the shape and position of a 1D curve within the model. Edges play a crucial role in defining the boundaries of faces and in constructing complex 3D shapes.
[`Wire`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: A Wire in build123d is a topological construct that represents a connected sequence of Edges, forming a 1D closed or open loop within a 3D model. Wires define the boundaries of faces and can be used to create complex shapes, making them essential for modeling in build123d.
[`Face`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: A Face in build123d represents a 2D surface in a 3D model. It defines the boundary of a region and can have associated geometric and topological data. Faces are vital for shaping solids, providing surfaces where other elements like edges and wires are connected to form complex structures.
[`Shell`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shell "topology.Shell"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: A Shell in build123d represents a collection of Faces, defining a closed, connected volume in 3D space. It acts as a container for organizing and grouping faces into a single shell, essential for defining complex 3D shapes like solids or assemblies within the build123d modeling framework.
[`Solid`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: A Solid in build123d is a 3D geometric entity that represents a bounded volume with well-defined interior and exterior surfaces. It encapsulates a closed and watertight shape, making it suitable for modeling solid objects and enabling various Boolean operations such as union, intersection, and subtraction.
[`Compound`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: A Compound in build123d is a container for grouping multiple geometric shapes. It can hold various types of entities, such as vertices, edges, wires, faces, shells, or solids, into a single structure. This makes it a versatile tool for managing and organizing complex assemblies or collections of shapes within a single container.
[`Shape`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: A Shape in build123d represents a fundamental building block in 3D modeling. It encompasses various topological elements like vertices, edges, wires, faces, shells, solids, and compounds. The Shape class is the base class for all of the above topological classes.
One can use the [`show_topology()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.show_topology "topology.Shape.show_topology"){.hxr-hoverxref .hxr-tooltip .reference .internal} method to display the topology of a shape as shown here for a unit cube:
::: {.highlight-default .notranslate}
::: highlight
Solid at 0x7f94c55430f0, Center(0.5, 0.5, 0.5)
└── Shell at 0x7f94b95835f0, Center(0.5, 0.5, 0.5)
├── Face at 0x7f94b95836b0, Center(0.0, 0.5, 0.5)
│ └── Wire at 0x7f94b9583730, Center(0.0, 0.5, 0.5)
│ ├── Edge at 0x7f94b95838b0, Center(0.0, 0.0, 0.5)
│ │ ├── Vertex at 0x7f94b9583470, Center(0.0, 0.0, 1.0)
│ │ └── Vertex at 0x7f94b9583bb0, Center(0.0, 0.0, 0.0)
│ ├── Edge at 0x7f94b9583a30, Center(0.0, 0.5, 1.0)
│ │ ├── Vertex at 0x7f94b9583030, Center(0.0, 1.0, 1.0)
│ │ └── Vertex at 0x7f94b9583e70, Center(0.0, 0.0, 1.0)
│ ├── Edge at 0x7f94b9583770, Center(0.0, 1.0, 0.5)
│ │ ├── Vertex at 0x7f94b9583bb0, Center(0.0, 1.0, 1.0)
│ │ └── Vertex at 0x7f94b9583e70, Center(0.0, 1.0, 0.0)
│ └── Edge at 0x7f94b9583db0, Center(0.0, 0.5, 0.0)
│ ├── Vertex at 0x7f94b9583e70, Center(0.0, 1.0, 0.0)
│ └── Vertex at 0x7f94b95862f0, Center(0.0, 0.0, 0.0)
...
└── Face at 0x7f94b958d3b0, Center(0.5, 0.5, 1.0)
└── Wire at 0x7f94b958d670, Center(0.5, 0.5, 1.0)
├── Edge at 0x7f94b958e130, Center(0.0, 0.5, 1.0)
│ ├── Vertex at 0x7f94b958e330, Center(0.0, 1.0, 1.0)
│ └── Vertex at 0x7f94b958e770, Center(0.0, 0.0, 1.0)
├── Edge at 0x7f94b958e630, Center(0.5, 1.0, 1.0)
│ ├── Vertex at 0x7f94b958e8b0, Center(1.0, 1.0, 1.0)
│ └── Vertex at 0x7f94b958ea70, Center(0.0, 1.0, 1.0)
├── Edge at 0x7f94b958e7b0, Center(1.0, 0.5, 1.0)
│ ├── Vertex at 0x7f94b958ebb0, Center(1.0, 1.0, 1.0)
│ └── Vertex at 0x7f94b958ed70, Center(1.0, 0.0, 1.0)
└── Edge at 0x7f94b958eab0, Center(0.5, 0.0, 1.0)
├── Vertex at 0x7f94b958eeb0, Center(1.0, 0.0, 1.0)
└── Vertex at 0x7f94b9592170, Center(0.0, 0.0, 1.0)
:::
:::
Users of build123d will often reference topological objects as part of the process of creating the object as described below.
:::
::: {#key_concepts.xhtml#location .section}
## Location
A [`Location`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal} represents a combination of translation and rotation applied to a topological or geometric object. It encapsulates information about the spatial orientation and position of a shape within its reference coordinate system. This allows for efficient manipulation of shapes within complex assemblies or transformations. The location is typically used to position shapes accurately within a 3D scene, enabling operations like assembly, and boolean operations. It's an essential component in build123d for managing the spatial relationships of geometric entities, providing a foundation for precise 3D modeling and engineering applications.
The topological classes (sub-classes of [`Shape`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}) and the geometric classes [`Axis`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal} and [`Plane`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal} all have a `location`{.docutils .literal .notranslate} property. The [`Location`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal} class itself has `position`{.docutils .literal .notranslate} and `orientation`{.docutils .literal .notranslate} properties that have setters and getters as shown below:
::: {.highlight-pycon .notranslate}
::: highlight
>>> from build123d import *
>>> # Create an object and extract its location
>>> b = Box(1, 1, 1)
>>> box_location = b.location
>>> box_location
(p=(0.00, 0.00, 0.00), o=(-0.00, 0.00, -0.00))
>>> # Set position and orientation independently
>>> box_location.position = (1, 2, 3)
>>> box_location.orientation = (30, 40, 50)
>>> box_location.position
Vector: (1.0, 2.0, 3.0)
>>> box_location.orientation
Vector: (29.999999999999993, 40.00000000000002, 50.000000000000036)
:::
:::
Combining the getter and setter enables relative changes as follows:
::: {.highlight-pycon .notranslate}
::: highlight
>>> # Relative change
>>> box_location.position += (3, 2, 1)
>>> box_location.position
Vector: (4.0, 4.0, 4.0)
:::
:::
There are also four methods that are used to change the location of objects:
- [`locate()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.locate "topology.Shape.locate"){.hxr-hoverxref .hxr-tooltip .reference .internal} - absolute change of this object
- [`located()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.located "topology.Shape.located"){.hxr-hoverxref .hxr-tooltip .reference .internal} - absolute change of copy of this object
- [`move()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.move "topology.Shape.move"){.hxr-hoverxref .hxr-tooltip .reference .internal} - relative change of this object
- [`moved()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.moved "topology.Shape.moved"){.hxr-hoverxref .hxr-tooltip .reference .internal} - relative change of copy of this object
Locations can be combined with the `*`{.docutils .literal .notranslate} operator and have their direction flipped with the `-`{.docutils .literal .notranslate} operator.
:::
::: {#key_concepts.xhtml#selectors .section}
## Selectors
When using a GUI based CAD system the user will often click on a feature to select it for some operation. How does a user "click" when CAD is done entirely in code? Selectors are recipes for how to isolate a feature from a design using python filter and sorting methods typically implemented as a set of custom python operations.
::: {#key_concepts.xhtml#quick-reference .section}
### Quick Reference
The following tables describes the build123d selectors:
Selector Applicability Description Example
------------ ----------------------------------- ------------------- ------------------------------------------------
vertices() BuildLine, BuildSketch, BuildPart Vertex extraction ``{=html}part.vertices()``{=html}
edges() BuildLine, BuildSketch, BuildPart Edge extraction ``{=html}part.edges()``{=html}
wires() BuildLine, BuildSketch, BuildPart Wire extraction ``{=html}part.wires()``{=html}
faces() BuildSketch, BuildPart Face extraction ``{=html}part.faces()``{=html}
solids() BuildPart Solid extraction ``{=html}part.solids()``{=html}
Operator Operand Method Description Example
---------- ----------------------- -------------------- ------------------------------------------------------- --------------------------------------------------------------------------------------------------------
\> SortBy, Axis sort_by Sort ShapeList by operand ``{=html}part.vertices() \> Axis.Z``{=html}
\< SortBy, Axis sort_by Reverse sort ShapeList by operand ``{=html}part.faces() \< Axis.Z``{=html}
\>\> SortBy, Axis group_by Group ShapeList by operand and return last value ``{=html}part.solids() \>\> Axis.X``{=html}
\<\< SortBy, Axis group_by Group ShapeList by operand and return first value ``{=html}part.faces() \<\< Axis.Y``{=html}
\| Axis, Plane, GeomType filter_by Filter and sort ShapeList by Axis, Plane, or GeomType ``{=html}part.faces() \| Axis.Z``{=html}
\[\] Standard python list indexing and slicing ``{=html}part.faces()\[-2:\]``{=html}
Axis filter_by_position Filter ShapeList by Axis & mix / max values ``{=html}part.faces()..filter_by_position(Axis.Z, 1, 2, inclusive=(False, True))``{=html}
The operand types are: Axis, Plane, SortBy, and GeomType. An Axis is a base object with an origin and a direction with several predefined values such as `Axis.X`{.docutils .literal .notranslate}, `Axis.Y`{.docutils .literal .notranslate}, and `Axis.Z`{.docutils .literal .notranslate}; however, any Axis could be used as an operand (e.g. `Axis((1,2,3),(0.5,0,-0.5))`{.docutils .literal .notranslate} is valid) - see [`Axis`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal} for a complete description. A Plane is a coordinate system defined by an origin, x_dir (X direction), y_dir (Y direction), and z_dir (Z direction). See [`Plane`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal} for a complete description. Filtering by a Plane will return faces/edges parallel to it. SortBy and GeomType are python Enum class described here:
[`GeomType`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.GeomType "build_enums.GeomType"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: BEZIER, BSPLINE, CIRCLE, CONE, CYLINDER, ELLIPSE, EXTRUSION, HYPERBOLA, LINE, OFFSET, OTHER, PARABOLA, PLANE, REVOLUTION, SPHERE, TORUS
[`SortBy`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.SortBy "build_enums.SortBy"){.hxr-hoverxref .hxr-tooltip .reference .internal}
: LENGTH, RADIUS, AREA, VOLUME, DISTANCE
:::
::: {#key_concepts.xhtml#shapelist-class .section}
### ShapeList Class
The builders include methods to extract Edges, Faces, Solids, Vertices, or Wires from the objects they are building. All of these methods return objects of a subclass of ``{=html}list``{=html}, a [`ShapeList`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal} with custom filtering and sorting methods and operations as follows.
:::
::: {#key_concepts.xhtml#custom-sorting-and-filtering .section}
### Custom Sorting and Filtering
It is important to note that standard list methods such as ``{=html}sorted``{=html} or ``{=html}filtered``{=html} can be used to easily build complex selectors beyond what is available with the predefined sorts and filters. Here is an example of a custom filters:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch() as din:
...
outside_vertices = filter(
lambda v: (v.Y == 0.0 or v.Y == height)
and -overall_width / 2 < v.X < overall_width / 2,
din.vertices(),
)
:::
:::
The [`filter_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.filter_by "topology.ShapeList.filter_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} method can take lambda expressions as part of a fluent chain of operations which enables integration of custom filters into a larger change of selectors as shown in this example:
::: {.highlight-build123d .notranslate}
::: highlight
obj = Box(1, 1, 1) - Cylinder(0.2, 1)
faces_with_holes = obj.faces().filter_by(lambda f: f.inner_wires())
:::
:::

Here the two faces with "inner_wires" (i.e. holes) have been selected independent of orientation.
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#key_concepts_builder.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#key_concepts_builder.xhtml#key-concepts-builder-mode .section}
# 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.
::: {#key_concepts_builder.xhtml#understanding-the-builder-paradigm .section}
## 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.
::: {#key_concepts_builder.xhtml#how-the-builder-works .section}
### 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 ``{=html}+``{=html} or ``{=html}-``{=html} 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`{.docutils .literal .notranslate}, `.sketch`{.docutils .literal .notranslate}, or `.part`{.docutils .literal .notranslate}, depending on the Builder type. - For example:
>
>
> - **BuildLine**: Use `.line`{.docutils .literal .notranslate} to retrieve the final wireframe geometry.
>
> - **BuildSketch**: Use `.sketch`{.docutils .literal .notranslate} to extract the completed 2D profile.
>
> - **BuildPart**: Use `.part`{.docutils .literal .notranslate} to obtain the 3D solid.
>
>
:::
::: {#key_concepts_builder.xhtml#example-workflow .section}
### Example Workflow
Here is an example of using a Builder to create a simple part:
::: {.highlight-build123d .notranslate}
::: highlight
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_builder.xhtml#key-concepts .section}
### 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.
:::
::: {#key_concepts_builder.xhtml#analogy-adding-numbers-on-paper .section}
### 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 `+`{.docutils .literal .notranslate} or `-`{.docutils .literal .notranslate} 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.
:::
:::
::: {#key_concepts_builder.xhtml#builders .section}
## Builders
The three builders, `BuildLine`{.docutils .literal .notranslate}, `BuildSketch`{.docutils .literal .notranslate}, and `BuildPart`{.docutils .literal .notranslate} 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`{.docutils .literal .notranslate} objects. This is opposed to CadQuery's Fluent API which creates objects of the `Workplane`{.docutils .literal .notranslate} 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:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as my_part:
...
show_object(my_part.part)
:::
:::
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch() as my_sketch:
...
show_object(my_sketch.sketch)
:::
:::
::: {.highlight-build123d .notranslate}
::: highlight
with BuildLine() as my_line:
...
show_object(my_line.line)
:::
:::
:::
::: {#key_concepts_builder.xhtml#implicit-builder-instance-variables .section}
## 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:
::: {.highlight-build123d .notranslate}
::: highlight
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:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as part_builder:
Box(10,10,10)
with BuildSketch() as sketch_builder:
Circle(2)
:::
:::
In this example, `Box`{.docutils .literal .notranslate} is in the scope of `part_builder`{.docutils .literal .notranslate} while `Circle`{.docutils .literal .notranslate} is in the scope of `sketch_builder`{.docutils .literal .notranslate}.
:::
::: {#key_concepts_builder.xhtml#workplanes .section}
## 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`{.docutils .literal .notranslate} 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.
::: {.highlight-build123d .notranslate}
::: highlight
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`{.docutils .literal .notranslate} is invoked it creates the workplane provided as a parameter (which has a default of the `Plane.XY`{.docutils .literal .notranslate}). The `bottom`{.docutils .literal .notranslate} sketch is therefore created on the `Plane.XY`{.docutils .literal .notranslate} but with the normal reversed to point down. Subsequently the user has created the `vertical`{.docutils .literal .notranslate} (`Plane.XZ`{.docutils .literal .notranslate}) 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`{.docutils .literal .notranslate} sketch is positioned on top of `example`{.docutils .literal .notranslate} 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.
::: {.highlight-build123d .notranslate}
::: highlight
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:
{.align-center}
:::
::: {#key_concepts_builder.xhtml#locations-context .section}
[]{#key_concepts_builder.xhtml#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:
::: {.highlight-build123d .notranslate}
::: highlight
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`{.docutils .literal .notranslate} creates two positions on the current workplane at (0,10) and (0,-10). Since `Box`{.docutils .literal .notranslate} is within the scope of `Locations`{.docutils .literal .notranslate}, two boxes are created at these locations. The `GridLocations`{.docutils .literal .notranslate} context creates four positions which apply to the `Sphere`{.docutils .literal .notranslate}. The `Cylinder`{.docutils .literal .notranslate} is out of the scope of `GridLocations`{.docutils .literal .notranslate} but in the scope of `Locations`{.docutils .literal .notranslate} 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`{.docutils .literal .notranslate} 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`{.docutils .literal .notranslate} can be nested. It's easy for a user to retrieve the global locations:
::: {.highlight-build123d .notranslate}
::: highlight
with Locations(Plane.XY, Plane.XZ):
locs = GridLocations(1, 1, 2, 2)
for l in locs:
print(l)
:::
:::
::: {.highlight-default .notranslate}
::: highlight
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))
:::
:::
:::
::: {#key_concepts_builder.xhtml#operation-inputs .section}
## 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 [`fillet()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.fillet "operations_generic.fillet"){.hxr-hoverxref .hxr-tooltip .reference .internal} to help illustrate:
::: {.highlight-build123d .notranslate}
::: highlight
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:
::: {.highlight-build123d .notranslate}
::: highlight
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`{.docutils .literal .notranslate} builder and a radius is provided as a keyword argument.
:::
::: {#key_concepts_builder.xhtml#combination-modes .section}
## Combination Modes
Almost all objects or operations have a `mode`{.docutils .literal .notranslate} parameter which is defined by the `Mode`{.docutils .literal .notranslate} Enum class as follows:
::: {.highlight-build123d .notranslate}
::: highlight
class Mode(Enum):
ADD = auto()
SUBTRACT = auto()
INTERSECT = auto()
REPLACE = auto()
PRIVATE = auto()
:::
:::
The `mode`{.docutils .literal .notranslate} parameter describes how the user would like the object or operation to interact with the object within the builder. For example, `Mode.ADD`{.docutils .literal .notranslate} will integrate a new object(s) in with an existing `part`{.docutils .literal .notranslate}. 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`{.docutils .literal .notranslate} object. As one might expect `Mode.SUBTRACT`{.docutils .literal .notranslate}, `Mode.INTERSECT`{.docutils .literal .notranslate}, and `Mode.REPLACE`{.docutils .literal .notranslate} subtract, intersect, or replace (from) the builder's object. `Mode.PRIVATE`{.docutils .literal .notranslate} instructs the builder that this object should not be combined with the builder's object in any way.
Most commonly, the default `mode`{.docutils .literal .notranslate} is `Mode.ADD`{.docutils .literal .notranslate} but this isn't always true. For example, the `Hole`{.docutils .literal .notranslate} classes use a default `Mode.SUBTRACT`{.docutils .literal .notranslate} as they remove a volume from the part under normal circumstances. However, the `mode`{.docutils .literal .notranslate} used in the `Hole`{.docutils .literal .notranslate} classes can be specified as `Mode.ADD`{.docutils .literal .notranslate} or `Mode.INTERSECT`{.docutils .literal .notranslate} to help in inspection or debugging.
:::
::: {#key_concepts_builder.xhtml#using-locations-rotating-objects .section}
## Using Locations & Rotating Objects
build123d stores points (to be specific `Location`{.docutils .literal .notranslate} (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:
::: {.highlight-build123d .notranslate}
::: highlight
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:
::: {.highlight-build123d .notranslate}
::: highlight
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`{.docutils .literal .notranslate} parameter is available on `` BuildSketch` ``{.docutils .literal .notranslate} and `BuildPart`{.docutils .literal .notranslate} 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`{.docutils .literal .notranslate} object of the form `Rotation(, , )`{.docutils .literal .notranslate} although a simple three tuple of floats can be used as input. As 3D rotations are not cumulative, one can combine rotations with the ``{=html}\*``{=html} operator like this: `Rotation(10, 20, 30) * Rotation(0, 90, 0)`{.docutils .literal .notranslate} to generate any desired rotation.
::: {.admonition .hint}
Hint
Experts Only
`Locations`{.docutils .literal .notranslate} will accept `Location`{.docutils .literal .notranslate} objects for input which allows one to specify both the position and orientation. However, the orientation is often determined by the `Plane`{.docutils .literal .notranslate} that an object was created on. `Rotation`{.docutils .literal .notranslate} is a subclass of `Location`{.docutils .literal .notranslate} and therefore will also accept a position component.
:::
:::
::: {#key_concepts_builder.xhtml#builder-s-pending-objects .section}
## 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:
::: {.highlight-build123d .notranslate}
::: highlight
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`{.docutils .literal .notranslate} exits after the `fillet`{.docutils .literal .notranslate} operation and when doing so it transfers the sketch to the `pillow_block`{.docutils .literal .notranslate} instance of `BuildPart`{.docutils .literal .notranslate} as the internal instance variable `pending_faces`{.docutils .literal .notranslate}. This allows the `extrude`{.docutils .literal .notranslate} operation to be immediately invoked as it extrudes these pending faces into `Solid`{.docutils .literal .notranslate} objects. Likewise, `loft`{.docutils .literal .notranslate} would take all of the `pending_faces`{.docutils .literal .notranslate} and attempt to create a single `Solid`{.docutils .literal .notranslate} 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`{.docutils .literal .notranslate} and `.pending_faces`{.docutils .literal .notranslate} attributes. In the above example, by adding a `print(pillow_block.pending_faces)`{.docutils .literal .notranslate} prior to the `extrude(amount=thickness)`{.docutils .literal .notranslate} the pending `Face`{.docutils .literal .notranslate} from the `BuildSketch`{.docutils .literal .notranslate} will be displayed.
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#key_concepts_algebra.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#key_concepts_algebra.xhtml#key-concepts-algebra-mode .section}
# Key Concepts (algebra mode)
Build123d's algebra mode works on objects of the classes `Shape`{.docutils .literal .notranslate}, `Part`{.docutils .literal .notranslate}, `Sketch`{.docutils .literal .notranslate} and `Curve`{.docutils .literal .notranslate} and is based on two concepts:
1. **Object arithmetic**
2. **Placement arithmetic**
::: {#key_concepts_algebra.xhtml#object-arithmetic .section}
## Object arithmetic
- Creating a box and a cylinder centered at `(0, 0, 0)`{.docutils .literal .notranslate}
::: {.highlight-build123d .notranslate}
::: highlight
b = Box(1, 2, 3)
c = Cylinder(0.2, 5)
:::
:::
- Fusing a box and a cylinder
::: {.highlight-build123d .notranslate}
::: highlight
r = Box(1, 2, 3) + Cylinder(0.2, 5)
:::
:::
- Cutting a cylinder from a box
::: {.highlight-build123d .notranslate}
::: highlight
r = Box(1, 2, 3) - Cylinder(0.2, 5)
:::
:::
- Intersecting a box and a cylinder
::: {.highlight-build123d .notranslate}
::: highlight
r = Box(1, 2, 3) & Cylinder(0.2, 5)
:::
:::
**Notes:**
- ``{=html}b``{=html}, ``{=html}c``{=html} and ``{=html}r``{=html} are instances of class `Compound`{.docutils .literal .notranslate} and can be viewed with every viewer that can show `build123d.Compound`{.docutils .literal .notranslate} objects.
- A discussion around performance can be found in [[Performance considerations in algebra mode]{.std .std-ref}](#algebra_performance.xhtml#algebra-performance){.reference .internal}.
- A mathematically formal definition of the algebra can be found in [[Algebraic definition]{.std .std-ref}](#algebra_definition.xhtml#algebra-definition){.reference .internal}.
:::
::: {#key_concepts_algebra.xhtml#placement-arithmetic .section}
## Placement arithmetic
A `Part`{.docutils .literal .notranslate}, `Sketch`{.docutils .literal .notranslate} or `Curve`{.docutils .literal .notranslate} does not have any location or rotation parameter. The rationale is that an object defines its topology (shape, sizes and its center), but does not know where in space it will be located. Instead, it will be relocated with the `*`{.docutils .literal .notranslate} operator onto a plane and to location relative to the plane (similar `moved`{.docutils .literal .notranslate}).
The generic forms of object placement are:
1. Placement on `plane`{.docutils .literal .notranslate} or at `location`{.docutils .literal .notranslate} relative to XY plane:
>
2\. Placement on the `plane`{.docutils .literal .notranslate} and then moved relative to the `plane`{.docutils .literal .notranslate} by `location`{.docutils .literal .notranslate} (the location is relative to the local coordinate system of the plane).
>
Details can be found in [[Location arithmetic for algebra mode]{.std .std-ref}](#location_arithmetic.xhtml#location-arithmetics){.reference .internal}.
Examples:
- Box on the `XY`{.docutils .literal .notranslate} plane, centered at ``{=html}(0, 0, 0)``{=html} (both forms are equivalent):
::: {.highlight-build123d .notranslate}
::: highlight
Plane.XY * Box(1, 2, 3)
Box(1, 2, 3)
:::
:::
Note: On the `XY`{.docutils .literal .notranslate} plane no placement is needed (mathematically `Plane.XY *`{.docutils .literal .notranslate} will not change the location of an object).
- Box on the `XY`{.docutils .literal .notranslate} plane centered at ``{=html}(0, 1, 0)``{=html} (all three are equivalent):
::: {.highlight-build123d .notranslate}
::: highlight
Plane.XY * Pos(0, 1, 0) * Box(1, 2, 3)
Pos(0, 1, 0) * Box(1, 2, 3)
Pos(Y=1) * Box(1, 2, 3)
:::
:::
Note: Again, `Plane.XY`{.docutils .literal .notranslate} can be omitted.
- Box on plane `Plane.XZ`{.docutils .literal .notranslate}:
::: {.highlight-build123d .notranslate}
::: highlight
Plane.XZ * Box(1, 2, 3)
:::
:::
- Box on plane `Plane.XZ`{.docutils .literal .notranslate} with a location `(X=1, Y=2, Z=3)`{.docutils .literal .notranslate} relative to the `XZ`{.docutils .literal .notranslate} plane, i.e., using the x-, y- and z-axis of the `XZ`{.docutils .literal .notranslate} plane:
::: {.highlight-build123d .notranslate}
::: highlight
Plane.XZ * Pos(1, 2, 3) * Box(1, 2, 3)
:::
:::
- Box on plane `Plane.XZ`{.docutils .literal .notranslate} moved to `(X=1, Y=2, Z=3)`{.docutils .literal .notranslate} relative to this plane and rotated there by the angles ``{=html}(X=0, Y=100, Z=45)``{=html} around `Plane.XZ`{.docutils .literal .notranslate} axes:
::: {.highlight-build123d .notranslate}
::: highlight
Plane.XZ * Pos(1, 2, 3) * Rot(0, 100, 45) * Box(1, 2, 3)
Location((1, 2, 3), (0, 100, 45)) * Box(1, 2, 3)
:::
:::
Note: `Pos * Rot`{.docutils .literal .notranslate} is the same as using `Location`{.docutils .literal .notranslate} directly
- Box on plane `Plane.XZ`{.docutils .literal .notranslate} rotated on this plane by the angles `(X=0, Y=100, Z=45)`{.docutils .literal .notranslate} (using the x-, y- and z-axis of the `XZ`{.docutils .literal .notranslate} plane) and then moved to `(X=1, Y=2, Z=3)`{.docutils .literal .notranslate} relative to the `XZ`{.docutils .literal .notranslate} plane:
::: {.highlight-build123d .notranslate}
::: highlight
Plane.XZ * Rot(0, 100, 45) * Pos(0,1,2) * Box(1, 2, 3)
:::
:::
:::
::: {#key_concepts_algebra.xhtml#combing-both-concepts .section}
## Combing both concepts
**Object arithmetic** and **Placement at locations** can be combined:
>
**Note:** In Python `*`{.docutils .literal .notranslate} binds stronger then `+`{.docutils .literal .notranslate}, `-`{.docutils .literal .notranslate}, `&`{.docutils .literal .notranslate}, hence brackets are not needed.
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#moving_objects.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#moving_objects.xhtml#moving-objects .section}
# Moving Objects
In build123d, there are several methods to move objects. These methods vary based on the mode of operation and provide flexibility for object placement and orientation. Below, we outline the three main approaches to moving objects: builder mode, algebra mode, and direct manipulation methods.
::: {#moving_objects.xhtml#builder-mode .section}
## Builder Mode
In builder mode, object locations are defined before the objects themselves are created. This approach ensures that objects are positioned correctly during the construction process. The following tools are commonly used to specify locations:
1. [`Locations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.Locations "build_common.Locations"){.hxr-hoverxref .hxr-tooltip .reference .internal} Use this to define a specific location for the objects within the ``{=html}with``{=html} block.
2. [`GridLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.GridLocations "build_common.GridLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} Arrange objects in a grid pattern.
3. [`PolarLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.PolarLocations "build_common.PolarLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} Position objects in a circular pattern.
4. [`HexLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.HexLocations "build_common.HexLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} Arrange objects in a hexagonal grid.
::: {.admonition .note}
Note
The location(s) of an object must be defined prior to its creation when using builder mode.
:::
Example:
::: {.highlight-build123d .notranslate}
::: highlight
with Locations((10, 20, 30)):
Box(5, 5, 5)
:::
:::
:::
::: {#moving_objects.xhtml#algebra-mode .section}
## Algebra Mode
In algebra mode, object movement is expressed using algebraic operations. The [`Pos`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Pos "geometry.Pos"){.hxr-hoverxref .hxr-tooltip .reference .internal} function, short for Position, represents a location, which can be combined with objects or planes to define placement.
1. `Pos() * shape`{.docutils .literal .notranslate}: Applies a position to a shape.
2. `Plane() * Pos() * shape`{.docutils .literal .notranslate}: Combines a plane with a position and applies it to a shape.
Rotation is an important concept in this mode. A [`Rotation`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Rotation "geometry.Rotation"){.hxr-hoverxref .hxr-tooltip .reference .internal} represents a location with orientation values set, which can be used to define a new location or modify an existing one.
Example:
::: {.highlight-build123d .notranslate}
::: highlight
rotated_box = Rotation(45, 0, 0) * box
:::
:::
:::
::: {#moving_objects.xhtml#direct-manipulation-methods .section}
## Direct Manipulation Methods
The following methods allow for direct manipulation of a shape's location and orientation after it has been created. These methods offer a mix of absolute and relative transformations.
::: {#moving_objects.xhtml#position .section}
### Position
- **Absolute Position:** Set the position directly.
::: {.highlight-build123d .notranslate}
::: highlight
shape.position = (x, y, z)
:::
:::
- **Relative Position:** Adjust the position incrementally.
::: {.highlight-build123d .notranslate}
::: highlight
shape.position += (x, y, z)
shape.position -= (x, y, z)
:::
:::
:::
::: {#moving_objects.xhtml#orientation .section}
### Orientation
- **Absolute Orientation:** Set the orientation directly.
::: {.highlight-build123d .notranslate}
::: highlight
shape.orientation = (X, Y, Z)
:::
:::
- **Relative Orientation:** Adjust the orientation incrementally.
::: {.highlight-build123d .notranslate}
::: highlight
shape.orientation += (X, Y, Z)
shape.orientation -= (X, Y, Z)
:::
:::
:::
::: {#moving_objects.xhtml#movement-methods .section}
### Movement Methods
- **Relative Move:**
::: {.highlight-build123d .notranslate}
::: highlight
shape.move(Location)
:::
:::
- **Relative Move of Copy:**
::: {.highlight-build123d .notranslate}
::: highlight
relocated_shape = shape.moved(Location)
:::
:::
- **Absolute Move:**
::: {.highlight-build123d .notranslate}
::: highlight
shape.locate(Location)
:::
:::
- **Absolute Move of Copy:**
::: {.highlight-build123d .notranslate}
::: highlight
relocated_shape = shape.located(Location)
:::
:::
:::
::: {#moving_objects.xhtml#transformation-a-k-a-translation-and-rotation .section}
### Transformation a.k.a. Translation and Rotation
::: {.admonition .note}
Note
These methods don't work in the same way as the previous methods in that they don't just change the object's internal [`Location`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal} but transform the base object itself which is quite slow and potentially problematic.
:::
- **Translation:** Move a shape relative to its current position.
::: {.highlight-build123d .notranslate}
::: highlight
relocated_shape = shape.translate(x, y, z)
:::
:::
- **Rotation:** Rotate a shape around a specified axis by a given angle.
::: {.highlight-build123d .notranslate}
::: highlight
rotated_shape = shape.rotate(Axis, angle_in_degrees)
:::
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#OpenSCAD.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#OpenSCAD.xhtml#transitioning-from-openscad .section}
# 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.
::: {#OpenSCAD.xhtml#why-transition-to-build123d .section}
## 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.
:::
::: {#OpenSCAD.xhtml#moving-beyond-constructive-solid-geometry-csg .section}
## 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.
::: {#OpenSCAD.xhtml#why-transition-away-from-csg .section}
### 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.
:::
:::
::: {#OpenSCAD.xhtml#using-a-more-traditional-cad-design-workflow .section}
## 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.
::: {#OpenSCAD.xhtml#refining-the-model .section}
### 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.
:::
::: {#OpenSCAD.xhtml#example-comparison .section}
### Example Comparison
To illustrate the advantages of this approach, compare a simple model in OpenSCAD and build123d of a piece of angle iron:
**OpenSCAD Approach**
::: {.highlight-openscad .notranslate}
::: highlight
$fn = 100; // Increase the resolution for smooth fillets
// Dimensions
length = 100; // 10 cm long
width = 30; // 3 cm wide
thickness = 4; // 4 mm thick
fillet = 5; // 5 mm fillet radius
delta = 0.001; // a small number
// Create the angle iron
difference() {
// Outer shape
cube([width, length, width], center = false);
// Inner shape
union() {
translate([thickness+fillet,-delta,thickness+fillet])
rotate([-90,0,0])
cylinder(length+2*delta, fillet,fillet);
translate([thickness,-delta,thickness+fillet])
cube([width-thickness,length+2*delta,width-fillet],center=false);
translate([thickness+fillet,-delta,thickness])
cube([width-fillet,length+2*delta,width-thickness],center=false);
}
}
:::
:::
**build123d Approach**
::: {.highlight-build123d .notranslate}
::: highlight
# Builder mode
with BuildPart() as angle_iron:
with BuildSketch() as profile:
Rectangle(3 * CM, 4 * MM, align=Align.MIN)
Rectangle(4 * MM, 3 * CM, align=Align.MIN)
extrude(amount=10 * CM)
fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)
:::
:::
::: {.highlight-build123d .notranslate}
::: highlight
# Algebra mode
profile = Rectangle(3 * CM, 4 * MM, align=Align.MIN)
profile += Rectangle(4 * MM, 3 * CM, align=Align.MIN)
angle_iron = extrude(profile, 10 * CM)
angle_iron = fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)
:::
:::

OpenSCAD and build123d offer distinct paradigms for creating 3D models, as demonstrated by the angle iron example. OpenSCAD relies on Constructive Solid Geometry (CSG) operations, combining and subtracting 3D shapes like cubes and cylinders. Fillets are approximated by manually adding high-resolution cylinders, making adjustments cumbersome and less precise. This static approach can handle simple models but becomes challenging for complex or iterative designs.
In contrast, build123d emphasizes a profile-driven workflow. It starts with a 2D sketch, defining the geometry's outline, which is then extruded or otherwise transformed into a 3D model. Features like fillets are applied dynamically by querying topological elements, such as edges, using intuitive filtering methods. This approach ensures precision and flexibility, making changes straightforward without the need for manual repositioning or realignment.
The build123d methodology is computationally efficient, leveraging mathematical precision for features like fillets. By separating the design into manageable steps---sketching, extruding, and refining---it aligns with traditional CAD practices and enhances readability, modularity, and maintainability. Unlike OpenSCAD, build123d's dynamic querying of topological features allows for easy updates and adjustments, making it better suited for modern, complex, and iterative design workflows.
In summary, build123d's sketch-based paradigm and topological querying capabilities provide superior precision, flexibility, and efficiency compared to OpenSCAD's static, CSG-centric approach, making it a better choice for robust and adaptable CAD modeling.
:::
:::
::: {#OpenSCAD.xhtml#tips-for-transitioning .section}
## 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.
- **Operational Equivalency and Beyond**: Build123d provides equivalents to almost all features available in OpenSCAD, with the exception of the 3D **minkowski** operation. However, a 2D equivalent, **make_hull**, is available in build123d. Beyond operational equivalency, build123d offers a wealth of additional functionality, including advanced features like topological queries, dynamic filtering, and robust tools for creating complex geometries. By exploring build123d's extensive operations, you can unlock new possibilities and take your designs far beyond the capabilities of OpenSCAD.
- **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!
:::
::: {#OpenSCAD.xhtml#conclusion .section}
## 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.
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#introductory_examples.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#introductory_examples.xhtml#introductory-examples .section}
# [Introductory Examples](#introductory_examples.xhtml#id1){.toc-backref role="doc-backlink"}
The examples on this page can help you learn how to build objects with build123d, and are intended as a general overview of build123d.
They are organized from simple to complex, so working through them in order is the best way to absorb them.
::: {.admonition .note}
Note
Some important lines are omitted below to save space, so you will most likely need to add 1 & 2 to the provided code below for them to work:
>
>
> 1. `from build123d import *`{.docutils .literal .notranslate}
>
> 2. If you are using build123d *builder mode* or *algebra mode*,
>
> >
> >
> > - in *ocp_vscode* simply use e.g. `show(ex15)`{.docutils .literal .notranslate} to the end of your design to view parts, sketches and curves. `show_all()`{.docutils .literal .notranslate} can be used to automatically show all objects with their variable names as labels.
> >
> > - in *CQ-editor* add e.g. `show_object(ex15.part)`{.docutils .literal .notranslate}, `show_object(ex15.sketch)`{.docutils .literal .notranslate} or `show_object(ex15.line)`{.docutils .literal .notranslate} to the end of your design to view parts, sketches or lines.
> >
> >
>
> 3. If you want to save your resulting object as an STL from *builder mode*, you can use e.g. `export_stl(ex15.part, "file.stl")`{.docutils .literal .notranslate}.
>
> 4. If you want to save your resulting object as an STL from *algebra mode*, you can use e.g. `export_stl(ex15, "file.stl")`{.docutils .literal .notranslate}
>
> 5. build123d also supports exporting to multiple other file formats including STEP, see here for further information: [Import/Export Formats](https://build123d.readthedocs.io/en/latest/import_export.html){.reference .external}[ \[https://build123d.readthedocs.io/en/latest/import_export.html\]]{.link-target}
>
>
:::
```{=html}
```
::: {#introductory_examples.xhtml#simple-rectangular-plate .section}
[]{#introductory_examples.xhtml#ex-1}
## [1. Simple Rectangular Plate](#introductory_examples.xhtml#id2){.toc-backref role="doc-backlink"}
Just about the simplest possible example, a rectangular [`Box`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Box "objects_part.Box"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#plate-with-hole .section}
[]{#introductory_examples.xhtml#ex-2}
## [2. Plate with Hole](#introductory_examples.xhtml#id3){.toc-backref role="doc-backlink"}
A rectangular box, but with a hole added.
{.align-center}
- **Builder mode**
>
>
> In this case we are using [`Mode`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal} `.SUBTRACT`{.docutils .literal .notranslate} to cut the [`Cylinder`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Cylinder "objects_part.Cylinder"){.hxr-hoverxref .hxr-tooltip .reference .internal} from the [`Box`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Box "objects_part.Box"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
> center_hole_dia = 22.0
>
> with BuildPart() as ex2:
> Box(length, width, thickness)
> Cylinder(radius=center_hole_dia / 2, height=thickness, mode=Mode.SUBTRACT)
> :::
> :::
>
>
- **Algebra mode**
>
>
> In this case we are using the subtract operator `-`{.docutils .literal .notranslate} to cut the [`Cylinder`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Cylinder "objects_part.Cylinder"){.hxr-hoverxref .hxr-tooltip .reference .internal} from the [`Box`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Box "objects_part.Box"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
> center_hole_dia = 22.0
>
> ex2 = Box(length, width, thickness)
> ex2 -= Cylinder(center_hole_dia / 2, height=thickness)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#an-extruded-prismatic-solid .section}
[]{#introductory_examples.xhtml#ex-3}
## [3. An extruded prismatic solid](#introductory_examples.xhtml#id4){.toc-backref role="doc-backlink"}
Build a prismatic solid using extrusion.
{.align-center}
- **Builder mode**
>
>
> This time we can first create a 2D [`BuildSketch`{.xref .py .py-class .docutils .literal .notranslate}](#build_sketch.xhtml#build_sketch.BuildSketch "build_sketch.BuildSketch"){.hxr-hoverxref .hxr-tooltip .reference .internal} adding a [`Circle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Circle "objects_sketch.Circle"){.hxr-hoverxref .hxr-tooltip .reference .internal} and a subtracted [`Rectangle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Rectangle "objects_sketch.Rectangle"){.hxr-hoverxref .hxr-tooltip .reference .internal} and then use [`BuildPart`{.xref .py .py-class .docutils .literal .notranslate}](#build_part.xhtml#build_part.BuildPart "build_part.BuildPart"){.hxr-hoverxref .hxr-tooltip .reference .internal}'s [`extrude()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} feature.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> with BuildPart() as ex3:
> with BuildSketch() as ex3_sk:
> Circle(width)
> Rectangle(length / 2, width / 2, mode=Mode.SUBTRACT)
> extrude(amount=2 * thickness)
> :::
> :::
>
>
- **Algebra mode**
>
>
> This time we can first create a 2D [`Circle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Circle "objects_sketch.Circle"){.hxr-hoverxref .hxr-tooltip .reference .internal} with a subtracted `` Rectangle` ``{.xref .py .py-class .docutils .literal .notranslate} and then use the [`extrude()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} operation for parts.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> sk3 = Circle(width) - Rectangle(length / 2, width / 2)
> ex3 = extrude(sk3, amount=2 * thickness)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#building-profiles-using-lines-and-arcs .section}
[]{#introductory_examples.xhtml#ex-4}
## [4. Building Profiles using lines and arcs](#introductory_examples.xhtml#id5){.toc-backref role="doc-backlink"}
Sometimes you need to build complex profiles using lines and arcs. This example builds a prismatic solid from 2D operations. It is not necessary to create variables for the line segments, but it will be useful in a later example.
{.align-center}
- **Builder mode**
>
>
> [`BuildSketch`{.xref .py .py-class .docutils .literal .notranslate}](#build_sketch.xhtml#build_sketch.BuildSketch "build_sketch.BuildSketch"){.hxr-hoverxref .hxr-tooltip .reference .internal} operates on closed Faces, and the operation [`make_face()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_sketch.make_face "operations_sketch.make_face"){.hxr-hoverxref .hxr-tooltip .reference .internal} is used to convert the pending line segments from [`BuildLine`{.xref .py .py-class .docutils .literal .notranslate}](#build_line.xhtml#build_line.BuildLine "build_line.BuildLine"){.hxr-hoverxref .hxr-tooltip .reference .internal} into a closed Face.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> with BuildPart() as ex4:
> with BuildSketch() as ex4_sk:
> with BuildLine() as ex4_ln:
> l1 = Line((0, 0), (length, 0))
> l2 = Line((length, 0), (length, width))
> l3 = ThreePointArc((length, width), (width, width * 1.5), (0.0, width))
> l4 = Line((0.0, width), (0, 0))
> make_face()
> extrude(amount=thickness)
> :::
> :::
>
>
- **Algebra mode**
>
>
> We start with an empty [`Curve`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} and add lines to it (note that `Curve() + [line1, line2, line3]`{.docutils .literal .notranslate} is much more efficient than `line1 + line2 + line3`{.docutils .literal .notranslate}, see [[Performance considerations in algebra mode]{.std .std-ref}](#algebra_performance.xhtml#algebra-performance){.reference .internal}). The operation [`make_face()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_sketch.make_face "operations_sketch.make_face"){.hxr-hoverxref .hxr-tooltip .reference .internal} is used to convert the line segments into a Face.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> lines = Curve() + [
> Line((0, 0), (length, 0)),
> Line((length, 0), (length, width)),
> ThreePointArc((length, width), (width, width * 1.5), (0.0, width)),
> Line((0.0, width), (0, 0)),
> ]
> sk4 = make_face(lines)
> ex4 = extrude(sk4, thickness)
> :::
> :::
>
>
Note that to build a closed face it requires line segments that form a closed shape.
:::
::: {#introductory_examples.xhtml#moving-the-current-working-point .section}
[]{#introductory_examples.xhtml#ex-5}
## [5. Moving the current working point](#introductory_examples.xhtml#id6){.toc-backref role="doc-backlink"}
{.align-center}
- **Builder mode**
>
>
> Using [`Locations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.Locations "build_common.Locations"){.hxr-hoverxref .hxr-tooltip .reference .internal} we can place one (or multiple) objects at one (or multiple) places.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c, d = 90, 45, 15, 7.5
>
> with BuildPart() as ex5:
> with BuildSketch() as ex5_sk:
> Circle(a)
> with Locations((b, 0.0)):
> Rectangle(c, c, mode=Mode.SUBTRACT)
> with Locations((0, b)):
> Circle(d, mode=Mode.SUBTRACT)
> extrude(amount=c)
> :::
> :::
>
>
- **Algebra mode**
>
>
> Using the pattern `Pos(x, y, z=0) * obj`{.docutils .literal .notranslate} (with [`geometry.Pos`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Pos "geometry.Pos"){.hxr-hoverxref .hxr-tooltip .reference .internal}) we can move an object to the provided position. Using `Rot(x_angle, y_angle, z_angle) * obj`{.docutils .literal .notranslate} (with [`geometry.Rot`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Rot "geometry.Rot"){.hxr-hoverxref .hxr-tooltip .reference .internal}) would rotate the object.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c, d = 90, 45, 15, 7.5
>
> sk5 = Circle(a) - Pos(b, 0.0) * Rectangle(c, c) - Pos(0.0, b) * Circle(d)
> ex5 = extrude(sk5, c)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#using-point-lists .section}
[]{#introductory_examples.xhtml#ex-6}
## [6. Using Point Lists](#introductory_examples.xhtml#id7){.toc-backref role="doc-backlink"}
Sometimes you need to create a number of features at various [`Locations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.Locations "build_common.Locations"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
{.align-center}
- **Builder mode**
>
>
> You can use a list of points to construct multiple objects at once.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c = 80, 60, 10
>
> with BuildPart() as ex6:
> with BuildSketch() as ex6_sk:
> Circle(a)
> with Locations((b, 0), (0, b), (-b, 0), (0, -b)):
> Circle(c, mode=Mode.SUBTRACT)
> extrude(amount=c)
> :::
> :::
>
>
- **Algebra mode**
>
>
> You can use loops to iterate over these Locations or list comprehensions as in the example.
>
> The algebra operations are vectorized, which means `obj - [obj1, obj2, obj3]`{.docutils .literal .notranslate} is short for `obj - obj1 - obj2 - ob3`{.docutils .literal .notranslate} (and more efficient, see [[Performance considerations in algebra mode]{.std .std-ref}](#algebra_performance.xhtml#algebra-performance){.reference .internal}).
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c = 80, 60, 10
>
> sk6 = [loc * Circle(c) for loc in Locations((b, 0), (0, b), (-b, 0), (0, -b))]
> ex6 = extrude(Circle(a) - sk6, c)
> :::
> :::
>
>
>
> You can create [`RegularPolygon`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.RegularPolygon "objects_sketch.RegularPolygon"){.hxr-hoverxref .hxr-tooltip .reference .internal} for each stack point if you would like.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c = 60, 80, 5
>
> with BuildPart() as ex7:
> with BuildSketch() as ex7_sk:
> Rectangle(a, b)
> with Locations((0, 3 * c), (0, -3 * c)):
> RegularPolygon(radius=2 * c, side_count=6, mode=Mode.SUBTRACT)
> extrude(amount=c)
> :::
> :::
>
>
- **Algebra mode**
>
>
> You can apply locations to [`RegularPolygon`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.RegularPolygon "objects_sketch.RegularPolygon"){.hxr-hoverxref .hxr-tooltip .reference .internal} instances for each location via loops or list comprehensions.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c = 60, 80, 5
>
> polygons = [
> loc * RegularPolygon(radius=2 * c, side_count=6)
> for loc in Locations((0, 3 * c), (0, -3 * c))
> ]
> sk7 = Rectangle(a, b) - polygons
> ex7 = extrude(sk7, amount=c)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#polylines .section}
[]{#introductory_examples.xhtml#ex-8}
## [8. Polylines](#introductory_examples.xhtml#id9){.toc-backref role="doc-backlink"}
[`Polyline`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Polyline "objects_curve.Polyline"){.hxr-hoverxref .hxr-tooltip .reference .internal} allows creating a shape from a large number of chained points connected by lines. This example uses a polyline to create one half of an i-beam shape, which is [`mirror()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.mirror "operations_generic.mirror"){.hxr-hoverxref .hxr-tooltip .reference .internal} ed to create the final profile.
{.align-center}
- **Builder mode**
>
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> (L, H, W, t) = (100.0, 20.0, 20.0, 1.0)
> pts = [
> (0, H / 2.0),
> (W / 2.0, H / 2.0),
> (W / 2.0, (H / 2.0 - t)),
> (t / 2.0, (H / 2.0 - t)),
> (t / 2.0, (t - H / 2.0)),
> (W / 2.0, (t - H / 2.0)),
> (W / 2.0, H / -2.0),
> (0, H / -2.0),
> ]
>
> with BuildPart() as ex8:
> with BuildSketch(Plane.YZ) as ex8_sk:
> with BuildLine() as ex8_ln:
> Polyline(pts)
> mirror(ex8_ln.line, about=Plane.YZ)
> make_face()
> extrude(amount=L)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#selectors-fillets-and-chamfers .section}
[]{#introductory_examples.xhtml#ex-9}
## [9. Selectors, Fillets, and Chamfers](#introductory_examples.xhtml#id10){.toc-backref role="doc-backlink"}
This example introduces multiple useful and important concepts. Firstly [`chamfer()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.chamfer "operations_generic.chamfer"){.hxr-hoverxref .hxr-tooltip .reference .internal} and [`fillet()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.fillet "operations_generic.fillet"){.hxr-hoverxref .hxr-tooltip .reference .internal} can be used to "bevel" and "round" edges respectively. Secondly, these two methods require an edge or a list of edges to operate on. To select all edges, you could simply pass in `ex9.edges()`{.docutils .literal .notranslate}.
{.align-center}
- **Builder mode**
>
Note that [`group_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.group_by "topology.ShapeList.group_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} `(Axis.Z)`{.docutils .literal .notranslate} returns a list of lists of edges that is grouped by their z-position. In this case we want to use the `[-1]`{.docutils .literal .notranslate} group which, by convention, will be the highest z-dimension group.
:::
::: {#introductory_examples.xhtml#select-last-and-hole .section}
[]{#introductory_examples.xhtml#ex-10}
## [10. Select Last and Hole](#introductory_examples.xhtml#id11){.toc-backref role="doc-backlink"}
{.align-center}
- **Builder mode**
>
>
> Using [`Select`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal} `.LAST`{.docutils .literal .notranslate} you can select the most recently modified edges. It is used to perform a [`fillet()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.fillet "operations_generic.fillet"){.hxr-hoverxref .hxr-tooltip .reference .internal} in this example. This example also makes use of [`Hole`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Hole "objects_part.Hole"){.hxr-hoverxref .hxr-tooltip .reference .internal} which automatically cuts through the entire part.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> with BuildPart() as ex10:
> Box(length, width, thickness)
> Hole(radius=width / 4)
> fillet(ex10.edges(Select.LAST).group_by(Axis.Z)[-1], radius=2)
> :::
> :::
>
>
- **Algebra mode**
>
>
> Using the pattern `snapshot = obj.edges()`{.docutils .literal .notranslate} before and `last_edges = obj.edges() - snapshot`{.docutils .literal .notranslate} after an operation allows to select the most recently modified edges (same for `faces`{.docutils .literal .notranslate}, `vertices`{.docutils .literal .notranslate}, ...). It is used to perform a [`fillet()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.fillet "operations_generic.fillet"){.hxr-hoverxref .hxr-tooltip .reference .internal} in this example. This example also makes use of [`Hole`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Hole "objects_part.Hole"){.hxr-hoverxref .hxr-tooltip .reference .internal}. Different to the *context mode*, you have to add the `depth`{.docutils .literal .notranslate} of the whole.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> ex10 = Part() + Box(length, width, thickness)
>
> snapshot = ex10.edges()
> ex10 -= Hole(radius=width / 4, depth=thickness)
> last_edges = ex10.edges() - snapshot
> ex10 = fillet(last_edges.group_by(Axis.Z)[-1], 2)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#use-a-face-as-a-plane-for-buildsketch-and-introduce-gridlocations .section}
[]{#introductory_examples.xhtml#ex-11}
## [11. Use a face as a plane for BuildSketch and introduce GridLocations](#introductory_examples.xhtml#id12){.toc-backref role="doc-backlink"}
{.align-center}
- **Builder mode**
>
>
> [`BuildSketch`{.xref .py .py-class .docutils .literal .notranslate}](#build_sketch.xhtml#build_sketch.BuildSketch "build_sketch.BuildSketch"){.hxr-hoverxref .hxr-tooltip .reference .internal} accepts a Plane or a Face, so in this case we locate the Sketch on the top of the part. Note that the face used as input to BuildSketch needs to be Planar or unpredictable behavior can result. Additionally [`GridLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.GridLocations "build_common.GridLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} can be used to create a grid of points that are simultaneously used to place 4 pentagons.
>
> Lastly, [`extrude()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} can be used with a negative amount and `Mode.SUBTRACT`{.docutils .literal .notranslate} to cut these from the parent.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> with BuildPart() as ex11:
> Box(length, width, thickness)
> chamfer(ex11.edges().group_by(Axis.Z)[-1], length=4)
> fillet(ex11.edges().filter_by(Axis.Z), radius=5)
> Hole(radius=width / 4)
> fillet(ex11.edges(Select.LAST).sort_by(Axis.Z)[-1], radius=2)
> with BuildSketch(ex11.faces().sort_by(Axis.Z)[-1]) as ex11_sk:
> with GridLocations(length / 2, width / 2, 2, 2):
> RegularPolygon(radius=5, side_count=5)
> extrude(amount=-thickness, mode=Mode.SUBTRACT)
> :::
> :::
>
>
- **Algebra mode**
>
>
> The pattern `plane * obj`{.docutils .literal .notranslate} can be used to locate an object on a plane. Furthermore, the pattern `plane * location * obj`{.docutils .literal .notranslate} first places the object on a plane and then moves it relative to plane according to `location`{.docutils .literal .notranslate}.
>
> [`GridLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.GridLocations "build_common.GridLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} creates a grid of points that can be used in loops or list comprehensions as described earlier.
>
> Lastly, [`extrude()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} can be used with a negative amount and cut (`-`{.docutils .literal .notranslate}) from the parent.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> ex11 = Part() + Box(length, width, thickness)
> ex11 = chamfer(ex11.edges().group_by()[-1], 4)
> ex11 = fillet(ex11.edges().filter_by(Axis.Z), 5)
> last = ex11.edges()
> ex11 -= Hole(radius=width / 4, depth=thickness)
> ex11 = fillet((ex11.edges() - last).sort_by().last, 2)
>
> plane = Plane(ex11.faces().sort_by().last)
> polygons = Sketch() + [
> plane * loc * RegularPolygon(radius=5, side_count=5)
> for loc in GridLocations(length / 2, width / 2, 2, 2)
> ]
> ex11 -= extrude(polygons, -thickness)
> :::
> :::
>
>
Note that the direction implied by positive or negative inputs to amount is relative to the normal direction of the face or plane. As a result of this, unexpected behavior can occur if the extrude direction and mode/operation (ADD / `+`{.docutils .literal .notranslate} or SUBTRACT / `-`{.docutils .literal .notranslate}) are not correctly set.
:::
::: {#introductory_examples.xhtml#defining-an-edge-with-a-spline .section}
[]{#introductory_examples.xhtml#ex-12}
## [12. Defining an Edge with a Spline](#introductory_examples.xhtml#id13){.toc-backref role="doc-backlink"}
This example defines a side using a spline curve through a collection of points. Useful when you have an edge that needs a complex profile.
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#counterboreholes-countersinkholes-and-polarlocations .section}
[]{#introductory_examples.xhtml#ex-13}
## [13. CounterBoreHoles, CounterSinkHoles, and PolarLocations](#introductory_examples.xhtml#id14){.toc-backref role="doc-backlink"}
Counter-sink and counter-bore holes are useful for creating recessed areas for fasteners.
{.align-center}
- **Builder mode**
>
>
> We use a face to establish a location for [`Locations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.Locations "build_common.Locations"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b = 40, 4
> with BuildPart() as ex13:
> Cylinder(radius=50, height=10)
> with Locations(ex13.faces().sort_by(Axis.Z)[-1]):
> with PolarLocations(radius=a, count=4):
> CounterSinkHole(radius=b, counter_sink_radius=2 * b)
> with PolarLocations(radius=a, count=4, start_angle=45, angular_range=360):
> CounterBoreHole(radius=b, counter_bore_radius=2 * b, counter_bore_depth=b)
> :::
> :::
>
>
- **Algebra mode**
>
>
> We use a face to establish a plane that is used later in the code for locating objects onto this plane.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b = 40, 4
>
> ex13 = Cylinder(radius=50, height=10)
> plane = Plane(ex13.faces().sort_by().last)
>
> ex13 -= (
> plane
> * PolarLocations(radius=a, count=4)
> * CounterSinkHole(radius=b, counter_sink_radius=2 * b, depth=10)
> )
> ex13 -= (
> plane
> * PolarLocations(radius=a, count=4, start_angle=45, angular_range=360)
> * CounterBoreHole(
> radius=b, counter_bore_radius=2 * b, depth=10, counter_bore_depth=b
> )
> )
> :::
> :::
>
>
[`PolarLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.PolarLocations "build_common.PolarLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} creates a list of points that are radially distributed.
:::
::: {#introductory_examples.xhtml#position-on-a-line-with-and-introduce-sweep .section}
[]{#introductory_examples.xhtml#ex-14}
## [14. Position on a line with '@', '%' and introduce Sweep](#introductory_examples.xhtml#id15){.toc-backref role="doc-backlink"}
build123d includes a feature for finding the position along a line segment. This is normalized between 0 and 1 and can be accessed using the [`position_at()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Mixin1D.position_at "topology.Mixin1D.position_at"){.hxr-hoverxref .hxr-tooltip .reference .internal} (``{=html}@``{=html}) operator. Similarly the [`tangent_at()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Mixin1D.tangent_at "topology.Mixin1D.tangent_at"){.hxr-hoverxref .hxr-tooltip .reference .internal} (``{=html}%``{=html}) operator returns the line direction at a given point.
These two features are very powerful for chaining line segments together without having to repeat dimensions again and again, which is error prone, time consuming, and more difficult to maintain. The pending faces must lie on the path, please see example 37 for a way to make this placement easier.
{.align-center}
- **Builder mode**
>
>
> The [`sweep()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.sweep "operations_generic.sweep"){.hxr-hoverxref .hxr-tooltip .reference .internal} method takes any pending faces and sweeps them through the provided path (in this case the path is taken from the pending edges from `ex14_ln`{.docutils .literal .notranslate}). [`revolve()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_part.revolve "operations_part.revolve"){.hxr-hoverxref .hxr-tooltip .reference .internal} requires a single connected wire.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b = 40, 20
>
> with BuildPart() as ex14:
> with BuildLine() as ex14_ln:
> l1 = JernArc(start=(0, 0), tangent=(0, 1), radius=a, arc_size=180)
> l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=a, arc_size=-90)
> l3 = Line(l2 @ 1, l2 @ 1 + (-a, a))
> with BuildSketch(Plane.XZ) as ex14_sk:
> Rectangle(b, b)
> sweep()
> :::
> :::
>
>
- **Algebra mode**
>
>
> The [`sweep()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.sweep "operations_generic.sweep"){.hxr-hoverxref .hxr-tooltip .reference .internal} method takes any faces and sweeps them through the provided path (in this case the path is taken from `ex14_ln`{.docutils .literal .notranslate}).
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b = 40, 20
>
> l1 = JernArc(start=(0, 0), tangent=(0, 1), radius=a, arc_size=180)
> l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=a, arc_size=-90)
> l3 = Line(l2 @ 1, l2 @ 1 + (-a, a))
> ex14_ln = l1 + l2 + l3
>
> sk14 = Plane.XZ * Rectangle(b, b)
> ex14 = sweep(sk14, path=ex14_ln)
> :::
> :::
>
>
It is also possible to use tuple or [`Vector`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Vector "geometry.Vector"){.hxr-hoverxref .hxr-tooltip .reference .internal} addition (and other vector math operations) as seen in the `l3`{.docutils .literal .notranslate} variable.
:::
::: {#introductory_examples.xhtml#mirroring-symmetric-geometry .section}
[]{#introductory_examples.xhtml#ex-15}
## [15. Mirroring Symmetric Geometry](#introductory_examples.xhtml#id16){.toc-backref role="doc-backlink"}
Here mirror is used on the BuildLine to create a symmetric shape with fewer line segment commands. Additionally the '@' operator is used to simplify the line segment commands.
`(l4 @ 1).Y`{.docutils .literal .notranslate} is used to extract the y-component of the `l4 @ 1`{.docutils .literal .notranslate} vector.
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#mirroring-3d-objects .section}
[]{#introductory_examples.xhtml#ex-16}
## [16. Mirroring 3D Objects](#introductory_examples.xhtml#id17){.toc-backref role="doc-backlink"}
Mirror can also be used with BuildPart (and BuildSketch) to mirror 3D objects. The `Plane.offset()`{.docutils .literal .notranslate} method shifts the plane in the normal direction (positive or negative).
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#mirroring-from-faces .section}
[]{#introductory_examples.xhtml#ex-17}
## [17. Mirroring From Faces](#introductory_examples.xhtml#id18){.toc-backref role="doc-backlink"}
Here we select the farthest face in the Y-direction and turn it into a [`Plane`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal} using the `Plane()`{.docutils .literal .notranslate} class.
{.align-center}
- **Builder mode**
>
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b = 30, 20
>
> with BuildPart() as ex17:
> with BuildSketch() as ex17_sk:
> RegularPolygon(radius=a, side_count=5)
> extrude(amount=b)
> mirror(ex17.part, about=Plane(ex17.faces().group_by(Axis.Y)[0][0]))
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#creating-workplanes-on-faces .section}
[]{#introductory_examples.xhtml#ex-18}
## [18. Creating Workplanes on Faces](#introductory_examples.xhtml#id19){.toc-backref role="doc-backlink"}
Here we start with an earlier example, select the top face, draw a rectangle and then use Extrude with a negative distance.
{.align-center}
- **Builder mode**
>
>
> We then use `Mode.SUBTRACT`{.docutils .literal .notranslate} to cut it out from the main body.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
> a, b = 4, 5
>
> with BuildPart() as ex18:
> Box(length, width, thickness)
> chamfer(ex18.edges().group_by(Axis.Z)[-1], length=a)
> fillet(ex18.edges().filter_by(Axis.Z), radius=b)
> with BuildSketch(ex18.faces().sort_by(Axis.Z)[-1]):
> Rectangle(2 * b, 2 * b)
> extrude(amount=-thickness, mode=Mode.SUBTRACT)
> :::
> :::
>
>
- **Algebra mode**
>
>
> We then use `-=`{.docutils .literal .notranslate} to cut it out from the main body.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
> a, b = 4, 5
>
> ex18 = Part() + Box(length, width, thickness)
> ex18 = chamfer(ex18.edges().group_by()[-1], a)
> ex18 = fillet(ex18.edges().filter_by(Axis.Z), b)
>
> sk18 = Plane(ex18.faces().sort_by().first) * Rectangle(2 * b, 2 * b)
> ex18 -= extrude(sk18, -thickness)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#locating-a-workplane-on-a-vertex .section}
[]{#introductory_examples.xhtml#ex-19}
## [19. Locating a workplane on a vertex](#introductory_examples.xhtml#id20){.toc-backref role="doc-backlink"}
Here a face is selected and two different strategies are used to select vertices. Firstly `vtx`{.docutils .literal .notranslate} uses [`group_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.group_by "topology.ShapeList.group_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} and `Axis.X`{.docutils .literal .notranslate} to select a particular vertex. The second strategy uses a custom defined Axis `vtx2Axis`{.docutils .literal .notranslate} that is pointing roughly in the direction of a vertex to select, and then [`sort_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.sort_by "topology.ShapeList.sort_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} this custom Axis.
{.align-center}
- **Builder mode**
>
>
> Then the X and Y positions of these vertices are selected and passed to [`Locations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.Locations "build_common.Locations"){.hxr-hoverxref .hxr-tooltip .reference .internal} as center points for two circles that cut through the main part. Note that if you passed the variable `vtx`{.docutils .literal .notranslate} directly to [`Locations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.Locations "build_common.Locations"){.hxr-hoverxref .hxr-tooltip .reference .internal} then the part would be offset from the workplane by the vertex z-position.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, thickness = 80.0, 10.0
>
> with BuildPart() as ex19:
> with BuildSketch() as ex19_sk:
> RegularPolygon(radius=length / 2, side_count=7)
> extrude(amount=thickness)
> topf = ex19.faces().sort_by(Axis.Z)[-1]
> vtx = topf.vertices().group_by(Axis.X)[-1][0]
> vtx2Axis = Axis((0, 0, 0), (-1, -0.5, 0))
> vtx2 = topf.vertices().sort_by(vtx2Axis)[-1]
> with BuildSketch(topf) as ex19_sk2:
> with Locations((vtx.X, vtx.Y), (vtx2.X, vtx2.Y)):
> Circle(radius=length / 8)
> extrude(amount=-thickness, mode=Mode.SUBTRACT)
> :::
> :::
>
>
- **Algebra mode**
>
>
> Then the X and Y positions of these vertices are selected and used to move two circles that cut through the main part. Note that if you passed the variable `vtx`{.docutils .literal .notranslate} directly to [`Pos`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Pos "geometry.Pos"){.hxr-hoverxref .hxr-tooltip .reference .internal} then the part would be offset from the workplane by the vertex z-position.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, thickness = 80.0, 10.0
>
> ex19_sk = RegularPolygon(radius=length / 2, side_count=7)
> ex19 = extrude(ex19_sk, thickness)
>
> topf = ex19.faces().sort_by().last
>
> vtx = topf.vertices().group_by(Axis.X)[-1][0]
>
> vtx2Axis = Axis((0, 0, 0), (-1, -0.5, 0))
> vtx2 = topf.vertices().sort_by(vtx2Axis)[-1]
>
> ex19_sk2 = Circle(radius=length / 8)
> ex19_sk2 = Pos(vtx.X, vtx.Y) * ex19_sk2 + Pos(vtx2.X, vtx2.Y) * ex19_sk2
>
> ex19 -= extrude(ex19_sk2, thickness)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#offset-sketch-workplane .section}
[]{#introductory_examples.xhtml#ex-20}
## [20. Offset Sketch Workplane](#introductory_examples.xhtml#id21){.toc-backref role="doc-backlink"}
The `plane`{.docutils .literal .notranslate} variable is set to be coincident with the farthest face in the negative x-direction. The resulting Plane is offset from the original position.
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#create-a-workplanes-in-the-center-of-another-shape .section}
[]{#introductory_examples.xhtml#ex-21}
## [21. Create a Workplanes in the center of another shape](#introductory_examples.xhtml#id22){.toc-backref role="doc-backlink"}
One cylinder is created, and then the origin and z_dir of that part are used to create a new Plane for positioning another cylinder perpendicular and halfway along the first.
{.align-center}
- **Builder mode**
>
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> width, length = 10.0, 60.0
>
> with BuildPart() as ex21:
> with BuildSketch() as ex21_sk:
> Circle(width / 2)
> extrude(amount=length)
> with BuildSketch(Plane(origin=ex21.part.center(), z_dir=(-1, 0, 0))):
> Circle(width / 2)
> extrude(amount=length)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#rotated-workplanes .section}
[]{#introductory_examples.xhtml#ex-22}
## [22. Rotated Workplanes](#introductory_examples.xhtml#id23){.toc-backref role="doc-backlink"}
It is also possible to create a rotated workplane, building upon some of the concepts in an earlier example.
{.align-center}
- **Builder mode**
>
>
> Use the [`rotated()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Plane.rotated "geometry.Plane.rotated"){.hxr-hoverxref .hxr-tooltip .reference .internal} method to rotate the workplane.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> with BuildPart() as ex22:
> Box(length, width, thickness)
> pln = Plane(ex22.faces().group_by(Axis.Z)[0][0]).rotated((0, -50, 0))
> with BuildSketch(pln) as ex22_sk:
> with GridLocations(length / 4, width / 4, 2, 2):
> Circle(thickness / 4)
> extrude(amount=-100, both=True, mode=Mode.SUBTRACT)
> :::
> :::
>
>
[`GridLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.GridLocations "build_common.GridLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} places 4 Circles on 4 points on this rotated workplane, and then the Circles are extruded in the "both" (positive and negative) normal direction.
:::
::: {#introductory_examples.xhtml#revolve .section}
[]{#introductory_examples.xhtml#ex-23}
## [23. Revolve](#introductory_examples.xhtml#id24){.toc-backref role="doc-backlink"}
Here we build a sketch with a [`Polyline`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Polyline "objects_curve.Polyline"){.hxr-hoverxref .hxr-tooltip .reference .internal}, [`Line`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Line "objects_curve.Line"){.hxr-hoverxref .hxr-tooltip .reference .internal}, and a [`Circle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Circle "objects_sketch.Circle"){.hxr-hoverxref .hxr-tooltip .reference .internal}. It is absolutely critical that the sketch is only on one side of the axis of rotation before Revolve is called. To that end, `split`{.docutils .literal .notranslate} is used with `Plane.ZY`{.docutils .literal .notranslate} to keep only one side of the Sketch.
It is highly recommended to view your sketch before you attempt to call revolve.
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#loft .section}
[]{#introductory_examples.xhtml#ex-24}
## [24. Loft](#introductory_examples.xhtml#id25){.toc-backref role="doc-backlink"}
Loft is a very powerful tool that can be used to join dissimilar shapes. In this case we make a conical-like shape from a circle and a rectangle that is offset vertically. In this case [`loft()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_part.loft "operations_part.loft"){.hxr-hoverxref .hxr-tooltip .reference .internal} automatically takes the pending faces that were added by the two BuildSketches. Loft can behave unexpectedly when the input faces are not parallel to each other.
{.align-center}
- **Builder mode**
>
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness = 80.0, 60.0, 10.0
>
> with BuildPart() as ex24:
> Box(length, length, thickness)
> with BuildSketch(ex24.faces().group_by(Axis.Z)[0][0]) as ex24_sk:
> Circle(length / 3)
> with BuildSketch(ex24_sk.faces()[0].offset(length / 2)) as ex24_sk2:
> Rectangle(length / 6, width / 6)
> loft()
> :::
> :::
>
>
They can be offset inwards or outwards, and with different techniques for extending the corners (see [`Kind`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Kind "build_enums.Kind"){.hxr-hoverxref .hxr-tooltip .reference .internal} in the Offset docs).
:::
::: {#introductory_examples.xhtml#offset-part-to-create-thin-features .section}
[]{#introductory_examples.xhtml#ex-26}
## [26. Offset Part To Create Thin features](#introductory_examples.xhtml#id27){.toc-backref role="doc-backlink"}
Parts can also be transformed using an offset, but in this case with a 3D [`offset()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.offset "operations_generic.offset"){.hxr-hoverxref .hxr-tooltip .reference .internal}. Also commonly known as a shell, this allows creating thin walls using very few operations. This can also be offset inwards or outwards. Faces can be selected to be "deleted" using the `openings`{.docutils .literal .notranslate} parameter of [`offset()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.offset "operations_generic.offset"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
Note that self intersecting edges and/or faces can break both 2D and 3D offsets.
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#splitting-an-object .section}
[]{#introductory_examples.xhtml#ex-27}
## [27. Splitting an Object](#introductory_examples.xhtml#id28){.toc-backref role="doc-backlink"}
You can split an object using a plane, and retain either or both halves. In this case we select a face and offset half the width of the box.
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#locating-features-based-on-faces .section}
[]{#introductory_examples.xhtml#ex-28}
## [28. Locating features based on Faces](#introductory_examples.xhtml#id29){.toc-backref role="doc-backlink"}
{.align-center}
- **Builder mode**
>
>
> We create a triangular prism with [`Mode`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal} `.PRIVATE`{.docutils .literal .notranslate} and then later use the faces of this object to cut holes in a sphere.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> width, thickness = 80.0, 10.0
>
> with BuildPart() as ex28:
> with BuildSketch() as ex28_sk:
> RegularPolygon(radius=width / 4, side_count=3)
> ex28_ex = extrude(amount=thickness, mode=Mode.PRIVATE)
> midfaces = ex28_ex.faces().group_by(Axis.Z)[1]
> Sphere(radius=width / 2)
> for face in midfaces:
> with Locations(face):
> Hole(thickness / 2)
> :::
> :::
>
>
- **Algebra mode**
>
>
> We create a triangular prism and then later use the faces of this object to cut holes in a sphere.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> width, thickness = 80.0, 10.0
>
> sk28 = RegularPolygon(radius=width / 4, side_count=3)
> tmp28 = extrude(sk28, thickness)
> ex28 = Sphere(radius=width / 2)
> for p in [Plane(face) for face in tmp28.faces().group_by(Axis.Z)[1]]:
> ex28 -= p * Hole(thickness / 2, depth=width)
> :::
> :::
>
>
We are able to create multiple workplanes by looping over the list of faces.
:::
::: {#introductory_examples.xhtml#the-classic-occ-bottle .section}
[]{#introductory_examples.xhtml#ex-29}
## [29. The Classic OCC Bottle](#introductory_examples.xhtml#id30){.toc-backref role="doc-backlink"}
build123d is based on the OpenCascade.org (OCC) modeling Kernel. Those who are familiar with OCC know about the famous 'bottle' example. We use a 3D Offset and the openings parameter to create the bottle opening.
{.align-center}
- **Builder mode**
>
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> L, w, t, b, h, n = 60.0, 18.0, 9.0, 0.9, 90.0, 6.0
>
> with BuildPart() as ex29:
> with BuildSketch(Plane.XY.offset(-b)) as ex29_ow_sk:
> with BuildLine() as ex29_ow_ln:
> l1 = Line((0, 0), (0, w / 2))
> l2 = ThreePointArc(l1 @ 1, (L / 2.0, w / 2.0 + t), (L, w / 2.0))
> l3 = Line(l2 @ 1, ((l2 @ 1).X, 0, 0))
> mirror(ex29_ow_ln.line)
> make_face()
> extrude(amount=h + b)
> fillet(ex29.edges(), radius=w / 6)
> with BuildSketch(ex29.faces().sort_by(Axis.Z)[-1]):
> Circle(t)
> extrude(amount=n)
> necktopf = ex29.faces().sort_by(Axis.Z)[-1]
> offset(ex29.solids()[0], amount=-b, openings=necktopf)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#bezier-curve .section}
[]{#introductory_examples.xhtml#ex-30}
## [30. Bezier Curve](#introductory_examples.xhtml#id31){.toc-backref role="doc-backlink"}
Here `pts`{.docutils .literal .notranslate} is used as an input to both [`Polyline`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Polyline "objects_curve.Polyline"){.hxr-hoverxref .hxr-tooltip .reference .internal} and [`Bezier`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Bezier "objects_curve.Bezier"){.hxr-hoverxref .hxr-tooltip .reference .internal} and `wts`{.docutils .literal .notranslate} to Bezier alone. These two together create a closed line that is made into a face and extruded.
{.align-center}
- **Builder mode**
>
:::
::: {#introductory_examples.xhtml#nesting-locations .section}
[]{#introductory_examples.xhtml#ex-31}
## [31. Nesting Locations](#introductory_examples.xhtml#id32){.toc-backref role="doc-backlink"}
Locations contexts can be nested to create groups of shapes. Here 24 triangles, 6 squares, and 1 hexagon are created and then extruded. Notably [`PolarLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.PolarLocations "build_common.PolarLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} rotates any "children" groups by default.
{.align-center}
- **Builder mode**
>
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c = 80.0, 5.0, 3.0
>
> with BuildPart() as ex31:
> with BuildSketch() as ex31_sk:
> with PolarLocations(a / 2, 6):
> with GridLocations(3 * b, 3 * b, 2, 2):
> RegularPolygon(b, 3)
> RegularPolygon(b, 4)
> RegularPolygon(3 * b, 6, rotation=30)
> extrude(amount=c)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#python-for-loop .section}
[]{#introductory_examples.xhtml#ex-32}
## [32. Python For-Loop](#introductory_examples.xhtml#id33){.toc-backref role="doc-backlink"}
In this example, a standard python for-loop is used along with a list of faces extracted from a sketch to progressively modify the extrusion amount. There are 7 faces in the sketch, so this results in 7 separate calls to [`extrude()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
{.align-center}
- **Builder mode**
>
>
> [`Mode`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal} `.PRIVATE`{.docutils .literal .notranslate} is used in [`BuildSketch`{.xref .py .py-class .docutils .literal .notranslate}](#build_sketch.xhtml#build_sketch.BuildSketch "build_sketch.BuildSketch"){.hxr-hoverxref .hxr-tooltip .reference .internal} to avoid adding these faces until the for-loop.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c = 80.0, 10.0, 1.0
>
> with BuildPart() as ex32:
> with BuildSketch(mode=Mode.PRIVATE) as ex32_sk:
> RegularPolygon(2 * b, 6, rotation=30)
> with PolarLocations(a / 2, 6):
> RegularPolygon(b, 4)
> for idx, obj in enumerate(ex32_sk.sketch.faces()):
> add(obj)
> extrude(amount=c + 3 * idx)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#python-function-and-for-loop .section}
[]{#introductory_examples.xhtml#ex-33}
## [33. Python Function and For-Loop](#introductory_examples.xhtml#id34){.toc-backref role="doc-backlink"}
Building on the previous example, a standard python function is used to return a sketch as a function of several inputs to progressively modify the size of each square.
{.align-center}
- **Builder mode**
>
>
> The function returns a [`BuildSketch`{.xref .py .py-class .docutils .literal .notranslate}](#build_sketch.xhtml#build_sketch.BuildSketch "build_sketch.BuildSketch"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c = 80.0, 5.0, 1.0
>
>
> def square(rad, loc):
> with BuildSketch() as sk:
> with Locations(loc):
> RegularPolygon(rad, 4)
> return sk.sketch
>
>
> with BuildPart() as ex33:
> with BuildSketch(mode=Mode.PRIVATE) as ex33_sk:
> locs = PolarLocations(a / 2, 6)
> for i, j in enumerate(locs):
> add(square(b + 2 * i, j))
> for idx, obj in enumerate(ex33_sk.sketch.faces()):
> add(obj)
> extrude(amount=c + 2 * idx)
> :::
> :::
>
>
- **Algebra mode**
>
>
> The function returns a `Sketch`{.docutils .literal .notranslate} object.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> a, b, c = 80.0, 5.0, 1.0
>
>
> def square(rad, loc):
> return loc * RegularPolygon(rad, 4)
>
>
> ex33 = Part() + [
> extrude(square(b + 2 * i, loc), c + 2 * i)
> for i, loc in enumerate(PolarLocations(a / 2, 6))
> ]
> :::
> :::
>
>
>
> The text "Hello" is placed on top of a rectangle and embossed (raised) by placing a BuildSketch on the top face (`topf`{.docutils .literal .notranslate}). Note that [`Align`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} is used to control the text placement. We re-use the `topf`{.docutils .literal .notranslate} variable to select the same face and deboss (indented) the text "World". Note that if we simply ran `BuildSketch(ex34.faces().sort_by(Axis.Z)[-1])`{.docutils .literal .notranslate} for both `ex34_sk1 & 2`{.docutils .literal .notranslate} it would incorrectly locate the 2nd "World" text on the top of the "Hello" text.
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness, fontsz, fontht = 80.0, 60.0, 10.0, 25.0, 4.0
>
> with BuildPart() as ex34:
> Box(length, width, thickness)
> topf = ex34.faces().sort_by(Axis.Z)[-1]
> with BuildSketch(topf) as ex34_sk:
> Text("Hello", font_size=fontsz, align=(Align.CENTER, Align.MIN))
> extrude(amount=fontht)
> with BuildSketch(topf) as ex34_sk2:
> Text("World", font_size=fontsz, align=(Align.CENTER, Align.MAX))
> extrude(amount=-fontht, mode=Mode.SUBTRACT)
> :::
> :::
>
>
- **Algebra mode**
>
>
> The text "Hello" is placed on top of a rectangle and embossed (raised) by placing a sketch on the top face (`topf`{.docutils .literal .notranslate}). Note that [`Align`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} is used to control the text placement. We re-use the `topf`{.docutils .literal .notranslate} variable to select the same face and deboss (indented) the text "World".
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> length, width, thickness, fontsz, fontht = 80.0, 60.0, 10.0, 25.0, 4.0
>
> ex34 = Box(length, width, thickness)
> plane = Plane(ex34.faces().sort_by().last)
> ex34_sk = plane * Text("Hello", font_size=fontsz, align=(Align.CENTER, Align.MIN))
> ex34 += extrude(ex34_sk, amount=fontht)
> ex34_sk2 = plane * Text("World", font_size=fontsz, align=(Align.CENTER, Align.MAX))
> ex34 -= extrude(ex34_sk2, amount=-fontht)
> :::
> :::
>
>
:::
::: {#introductory_examples.xhtml#extrude-until .section}
[]{#introductory_examples.xhtml#ex-36}
## [36. Extrude Until](#introductory_examples.xhtml#id37){.toc-backref role="doc-backlink"}
Sometimes you will want to extrude until a given face that could be non planar or where you might not know easily the distance you have to extrude to. In such cases you can use [`extrude()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} [`Until`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Until "build_enums.Until"){.hxr-hoverxref .hxr-tooltip .reference .internal} with `Until.NEXT`{.docutils .literal .notranslate} or `Until.LAST`{.docutils .literal .notranslate}.
{.align-center}
- **Builder mode**
>
>
> ::: {.highlight-build123d .notranslate}
> ::: highlight
> rad, rev = 6, 50
>
> with BuildPart() as ex36:
> with BuildSketch() as ex36_sk:
> with Locations((0, rev)):
> Circle(rad)
> revolve(axis=Axis.X, revolution_arc=180)
> with BuildSketch() as ex36_sk2:
> Rectangle(rad, rev)
> extrude(until=Until.NEXT)
> :::
> :::
>
>
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tutorials.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tutorials.xhtml#tutorials .section}
# Tutorials
There are several tutorials to help guide uses through the concepts of build123d in a step by step way. Working through these tutorials in order is recommended as later tutorials build on the concepts introduced in earlier ones.
::: {.toctree-wrapper .compound}
- [Designing a Part in build123d](#tutorial_design.xhtml){.reference .internal}
- [Step 1. Examine the Part in All Three Orientations](#tutorial_design.xhtml#step-1-examine-the-part-in-all-three-orientations){.reference .internal}
- [Step 2. Identify Rotational Symmetries](#tutorial_design.xhtml#step-2-identify-rotational-symmetries){.reference .internal}
- [Step 3. Select a Convenient Origin](#tutorial_design.xhtml#step-3-select-a-convenient-origin){.reference .internal}
- [Step 4. Create 2D Profiles](#tutorial_design.xhtml#step-4-create-2d-profiles){.reference .internal}
- [Step 5. Use Extrusion for Prismatic Features](#tutorial_design.xhtml#step-5-use-extrusion-for-prismatic-features){.reference .internal}
- [Step 6. Generate Revolved Features](#tutorial_design.xhtml#step-6-generate-revolved-features){.reference .internal}
- [Step 7. Combine Sub-parts Intelligently](#tutorial_design.xhtml#step-7-combine-sub-parts-intelligently){.reference .internal}
- [Step 8. Apply Chamfers and Fillets](#tutorial_design.xhtml#step-8-apply-chamfers-and-fillets){.reference .internal}
- [Step 9. Design for Assembly](#tutorial_design.xhtml#step-9-design-for-assembly){.reference .internal}
- [Step 10. Plan for Parametric Flexibility](#tutorial_design.xhtml#step-10-plan-for-parametric-flexibility){.reference .internal}
- [Step 11. Test Fit and Tolerances](#tutorial_design.xhtml#step-11-test-fit-and-tolerances){.reference .internal}
- [Summary](#tutorial_design.xhtml#summary){.reference .internal}
- [Selector Tutorial](#tutorial_selectors.xhtml){.reference .internal}
- [Step 1: Setup](#tutorial_selectors.xhtml#step-1-setup){.reference .internal}
- [Step 2: Create Base with BuildPart](#tutorial_selectors.xhtml#step-2-create-base-with-buildpart){.reference .internal}
- [Step 3: Place Sketch on top of base](#tutorial_selectors.xhtml#step-3-place-sketch-on-top-of-base){.reference .internal}
- [Step 4: Create hole shape](#tutorial_selectors.xhtml#step-4-create-hole-shape){.reference .internal}
- [Step 5: Create the hole](#tutorial_selectors.xhtml#step-5-create-the-hole){.reference .internal}
- [Step 6: Fillet the top perimeter Edge](#tutorial_selectors.xhtml#step-6-fillet-the-top-perimeter-edge){.reference .internal}
- [Conclusion](#tutorial_selectors.xhtml#conclusion){.reference .internal}
- [Lego Tutorial](#tutorial_lego.xhtml){.reference .internal}
- [Step 1: Setup](#tutorial_lego.xhtml#step-1-setup){.reference .internal}
- [Step 2: Part Builder](#tutorial_lego.xhtml#step-2-part-builder){.reference .internal}
- [Step 3: Sketch Builder](#tutorial_lego.xhtml#step-3-sketch-builder){.reference .internal}
- [Step 4: Perimeter Rectangle](#tutorial_lego.xhtml#step-4-perimeter-rectangle){.reference .internal}
- [Step 5: Offset to Create Walls](#tutorial_lego.xhtml#step-5-offset-to-create-walls){.reference .internal}
- [Step 6: Create Internal Grid](#tutorial_lego.xhtml#step-6-create-internal-grid){.reference .internal}
- [Step 7: Create Ridges](#tutorial_lego.xhtml#step-7-create-ridges){.reference .internal}
- [Step 8: Hollow Circles](#tutorial_lego.xhtml#step-8-hollow-circles){.reference .internal}
- [Step 9: Extruding Sketch into Walls](#tutorial_lego.xhtml#step-9-extruding-sketch-into-walls){.reference .internal}
- [Step 10: Adding a Top](#tutorial_lego.xhtml#step-10-adding-a-top){.reference .internal}
- [Step 11: Adding Pips](#tutorial_lego.xhtml#step-11-adding-pips){.reference .internal}
- [Joint Tutorial](#tutorial_joints.xhtml){.reference .internal}
- [Step 1: Setup](#tutorial_joints.xhtml#step-1-setup){.reference .internal}
- [Step 2: Create Hinge](#tutorial_joints.xhtml#step-2-create-hinge){.reference .internal}
- [Step 3: Add Joints to the Hinge Leaf](#tutorial_joints.xhtml#step-3-add-joints-to-the-hinge-leaf){.reference .internal}
- [Step 4: Create the Box](#tutorial_joints.xhtml#step-4-create-the-box){.reference .internal}
- [Step 5: Create the Lid](#tutorial_joints.xhtml#step-5-create-the-lid){.reference .internal}
- [Step 6: Import a Screw and bind a Joint to it](#tutorial_joints.xhtml#step-6-import-a-screw-and-bind-a-joint-to-it){.reference .internal}
- [Step 7: Connect the Joints together](#tutorial_joints.xhtml#step-7-connect-the-joints-together){.reference .internal}
- [Conclusion](#tutorial_joints.xhtml#conclusion){.reference .internal}
- [The build123d Examples](#examples_1.xhtml){.reference .internal}
- [Overview](#examples_1.xhtml#overview){.reference .internal}
- [Benchy](#examples_1.xhtml#benchy){.reference .internal}
- [Bicycle Tire](#examples_1.xhtml#bicycle-tire){.reference .internal}
- [Former build123d Logo](#examples_1.xhtml#former-build123d-logo){.reference .internal}
- [Cast Bearing Unit](#examples_1.xhtml#cast-bearing-unit){.reference .internal}
- [Canadian Flag Blowing in The Wind](#examples_1.xhtml#canadian-flag-blowing-in-the-wind){.reference .internal}
- [Circuit Board With Holes](#examples_1.xhtml#circuit-board-with-holes){.reference .internal}
- [Clock Face](#examples_1.xhtml#clock-face){.reference .internal}
- [Fast Grid Holes](#examples_1.xhtml#fast-grid-holes){.reference .internal}
- [Handle](#examples_1.xhtml#handle){.reference .internal}
- [Heat Exchanger](#examples_1.xhtml#heat-exchanger){.reference .internal}
- [Key Cap](#examples_1.xhtml#key-cap){.reference .internal}
- [Maker Coin](#examples_1.xhtml#maker-coin){.reference .internal}
- [Multi-Sketch Loft](#examples_1.xhtml#multi-sketch-loft){.reference .internal}
- [Peg Board Hook](#examples_1.xhtml#peg-board-hook){.reference .internal}
- [Platonic Solids](#examples_1.xhtml#platonic-solids){.reference .internal}
- [Playing Cards](#examples_1.xhtml#playing-cards){.reference .internal}
- [Stud Wall](#examples_1.xhtml#stud-wall){.reference .internal}
- [Tea Cup](#examples_1.xhtml#tea-cup){.reference .internal}
- [Toy Truck](#examples_1.xhtml#toy-truck){.reference .internal}
- [Vase](#examples_1.xhtml#vase){.reference .internal}
- [Too Tall Toby (TTT) Tutorials](#tttt.xhtml){.reference .internal}
- [Party Pack 01-01 Bearing Bracket](#tttt.xhtml#party-pack-01-01-bearing-bracket){.reference .internal}
- [Party Pack 01-02 Post Cap](#tttt.xhtml#party-pack-01-02-post-cap){.reference .internal}
- [Party Pack 01-03 C Clamp Base](#tttt.xhtml#party-pack-01-03-c-clamp-base){.reference .internal}
- [Party Pack 01-04 Angle Bracket](#tttt.xhtml#party-pack-01-04-angle-bracket){.reference .internal}
- [Party Pack 01-05 Paste Sleeve](#tttt.xhtml#party-pack-01-05-paste-sleeve){.reference .internal}
- [Party Pack 01-06 Bearing Jig](#tttt.xhtml#party-pack-01-06-bearing-jig){.reference .internal}
- [Party Pack 01-07 Flanged Hub](#tttt.xhtml#party-pack-01-07-flanged-hub){.reference .internal}
- [Party Pack 01-08 Tie Plate](#tttt.xhtml#party-pack-01-08-tie-plate){.reference .internal}
- [Party Pack 01-09 Corner Tie](#tttt.xhtml#party-pack-01-09-corner-tie){.reference .internal}
- [Party Pack 01-10 Light Cap](#tttt.xhtml#party-pack-01-10-light-cap){.reference .internal}
- [23-02-02 SM Hanger](#tttt.xhtml#sm-hanger){.reference .internal}
- [23-T-24 Curved Support](#tttt.xhtml#t-24-curved-support){.reference .internal}
- [24-SPO-06 Buffer Stand](#tttt.xhtml#spo-06-buffer-stand){.reference .internal}
- [Surface Modeling](#tutorial_surface_modeling.xhtml){.reference .internal}
- [Tutorial: Heart Token (Basics)](#tutorial_surface_heart_token.xhtml){.reference .internal}
- [Tutorial: Spitfire Wing with Gordon Surface](#tutorial_spitfire_wing_gordon.xhtml){.reference .internal}
- [Technical Drawing Tutorial](#tech_drawing_tutorial.xhtml){.reference .internal}
- [Overview](#tech_drawing_tutorial.xhtml#overview){.reference .internal}
- [How It Works](#tech_drawing_tutorial.xhtml#how-it-works){.reference .internal}
- [Result](#tech_drawing_tutorial.xhtml#result){.reference .internal}
- [Try It Yourself](#tech_drawing_tutorial.xhtml#try-it-yourself){.reference .internal}
- [Code](#tech_drawing_tutorial.xhtml#code){.reference .internal}
- [Dependencies](#tech_drawing_tutorial.xhtml#dependencies){.reference .internal}
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tutorial_design.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tutorial_design.xhtml#designing-a-part-in-build123d .section}
[]{#tutorial_design.xhtml#design-tutorial}
# Designing a Part in build123d
Designing a part with build123d involves a systematic approach that leverages the power of 2D profiles, extrusions, and revolutions. Where possible, always work in the lowest possible dimension, 1D lines before 2D sketches before 3D parts. The following guide will get you started:
*As an example, we'll go through the design process for this bracket:*
{.align-center}
::: {#tutorial_design.xhtml#step-1-examine-the-part-in-all-three-orientations .section}
## Step 1. Examine the Part in All Three Orientations
Start by visualizing the part from the front, top, and side views. Identify any symmetries in these orientations, as symmetries can simplify the design by reducing the number of unique features you need to model.
*In the following view of the bracket one can see two planes of symmetry so we'll only need to design one quarter of it.*
{.align-center}
:::
::: {#tutorial_design.xhtml#step-2-identify-rotational-symmetries .section}
## Step 2. Identify Rotational Symmetries
Look for structures that could be created through the rotation of a 2D shape. For instance, cylindrical or spherical features are often the result of revolving a profile around an axis. Identify the axis of rotation and make a note of it.
*There are no rotational structures in the example bracket.*
:::
::: {#tutorial_design.xhtml#step-3-select-a-convenient-origin .section}
## Step 3. Select a Convenient Origin
Choose an origin point that minimizes the need to move or transform components later in the design process. Ideally, the origin should be placed at a natural center of symmetry or a critical reference point on the part.
*The planes of symmetry for the bracket was identified in step 1, making it logical to place the origin at the intersection of these planes on the bracket's front face. Additionally, we'll define the coordinate system we'll be working in: Plane.XY (the default), where the origin is set at the global (0,0,0) position. In this system, the x-axis aligns with the front of the bracket, and the z-axis corresponds to its width. It's important to note that all coordinate systems/planes in build123d adhere to the* [right-hand rule](https://en.wikipedia.org/wiki/Right-hand_rule){.reference .external}[ \[https://en.wikipedia.org/wiki/Right-hand_rule\]]{.link-target} *meaning the y-axis is automatically determined by this convention.*
{.align-center}
:::
::: {#tutorial_design.xhtml#step-4-create-2d-profiles .section}
## Step 4. Create 2D Profiles
Design the 2D profiles of your part in the appropriate orientation(s). These profiles are the foundation of the part's geometry and can often represent cross-sections of the part. Mirror parts of profiles across any axes of symmetry identified earlier.
*The 2D profile of the bracket is as follows:*
{.align-center}
*The build123d code to generate this profile is as follows:*
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch() as sketch:
with BuildLine() as profile:
FilletPolyline(
(0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius
)
offset(amount=thickness, side=Side.LEFT)
make_face()
mirror(about=Plane.YZ)
:::
:::
*This code creates a 2D sketch of a mirrored profile in the build123d CAD system. Here's a step-by-step explanation of what it does:*
>
>
> with BuildSketch() as sketch:
>
> : *This starts a context for creating a 2D sketch, which defines the overall boundary and geometric features. The sketch will be stored in the variable sketch.*
>
> with BuildLine() as profile:
>
> : *This starts another context, this time for drawing lines (or profiles) within the sketch. The profile consists of connected line segments, arcs, or polylines.*
>
> FilletPolyline((0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius)
>
> : *This object draws a polyline with three points: (0,0), (length/2, 0), and (length/2, height). A fillet (curved corner) with a radius of bend_radius is added where applicable between the segments of the polyline.*
>
> offset(amount=thickness, side=Side.LEFT)
>
> : *This applies an offset to the polyline created earlier. The offset creates a parallel line at a distance of thickness to the left side of the original polyline. This operation essentially thickens the profile by a given amount.*
>
> make_face()
>
> : *This command creates a 2D face from the closed profile. The offset operation ensures that the profile is closed, allowing the creation of a solid face from the boundary defined.*
>
> mirror(about=Plane.YZ)
>
> : *This mirrors the entire face about the YZ plane (which runs along the center of the sketch), creating a symmetrical counterpart of the face. The mirrored geometry will complete the final shape.*
>
>
:::
::: {#tutorial_design.xhtml#step-5-use-extrusion-for-prismatic-features .section}
## Step 5. Use Extrusion for Prismatic Features
For solid or prismatic shapes, extrude the 2D profiles along the necessary axis. You can also combine multiple extrusions by intersecting or unionizing them to form complex shapes. Use the resulting geometry as sub-parts if needed.
*The next step in implementing our design in build123d is to convert the above sketch into a part by extruding it as shown in this code:*
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as bracket:
with BuildSketch() as sketch:
with BuildLine() as profile:
FilletPolyline(
(0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius
)
offset(amount=thickness, side=Side.LEFT)
make_face()
mirror(about=Plane.YZ)
extrude(amount=width / 2)
mirror(about=Plane.XY)
:::
:::
*In this example, we've wrapped the sketch within a BuildPart context, which is used for creating 3D parts. We utilized the extrude function to extend the 2D sketch into a solid object, turning it into a 3D part. Additionally, we applied the mirror function to replicate the partial part across a plane of symmetry, ensuring a symmetrical design.*
:::
::: {#tutorial_design.xhtml#step-6-generate-revolved-features .section}
## Step 6. Generate Revolved Features
If any part of the geometry can be created by revolving a 2D profile around an axis, use the revolve operation. This is particularly useful for parts that include cylindrical, conical, or spherical features. Combine these revolved sub-parts with existing features using additive, subtractive, or intersecting operations.
*Our example has no revolved features.*
:::
::: {#tutorial_design.xhtml#step-7-combine-sub-parts-intelligently .section}
## Step 7. Combine Sub-parts Intelligently
When combining multiple sub-parts, keep in mind whether they need to be added, subtracted, or intersected. Subtracting or intersecting can create more refined details, while addition is useful for creating complex assemblies.
*Out example only has one sub-part but further sub-parts could be created in the BuildPart context by defining more sketches and extruding or revolving them.*
:::
::: {#tutorial_design.xhtml#step-8-apply-chamfers-and-fillets .section}
## Step 8. Apply Chamfers and Fillets
Identify critical edges or vertices that need chamfering or filleting. Use build123d's selectors to apply these operations accurately. Always visually inspect the results to ensure the correct edges have been modified.
*The back corners of the bracket need to be rounded off or filleted so the edges that define these corners need to be isolated. The following code, placed to follow the previous code block, captures just these edges:*
::: {.highlight-build123d .notranslate}
::: highlight
corners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)[-1]
fillet(corners, fillet_radius)
:::
:::
*These lines isolates specific corner edges that are then filleted.*
>
>
> corners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)\[-1\]
>
> : *This line is used to select specific edges from the 3D part (bracket) that was created by the extrusion.*
>
> - bracket.edges() *retrieves all the edges of the bracket part.*
>
> - filter_by(Axis.X) *filters the edges to only those that are aligned along the X-axis.*
>
> - group_by(Axis.Y) *groups the edges by their positions along the Y-axis. This operation essentially organizes the filtered X-axis edges into groups based on their Y-coordinate positions.*
>
> - \[-1\] *selects the last group of edges along the Y-axis, which corresponds to the back of the part - the edges we are looking for.*
>
> fillet(corners, fillet_radius)
>
> : *This function applies a fillet (a rounded edge) to the selected corners, with a specified radius (fillet_radius). The fillet smooths the sharp edges at the corners, giving the part a more refined shape.*
>
>
:::
::: {#tutorial_design.xhtml#step-9-design-for-assembly .section}
## Step 9. Design for Assembly
If the part is intended to connect with others, add features like joints, holes, or other attachment points. Ensure that these features are precisely located to ensure proper fitment and functionality in the final assembly.
*Our example has two circular holes and a slot that need to be created. First we'll create the two circular holes:*
::: {.highlight-build123d .notranslate}
::: highlight
with Locations(bracket.faces().sort_by(Axis.X)[-1]):
Hole(hole_diameter / 2)
:::
:::
*This code creates a hole in a specific face of the bracket part.*
>
>
> with Locations(bracket.faces().sort_by(Axis.X)\[-1\]):
>
> : *This context sets a location(s) for subsequent operations.*
>
> - bracket.faces() *retrieves all the faces of the bracket part.*
>
> - sort_by(Axis.X) *sorts these faces based on their position along the X-axis (from one side of the bracket to the other).*
>
> - \[-1\] *selects the last face in this sorted list, which would be the face farthest along the X-axis, the extreme right side of the part.*
>
> - Locations() *creates a new local context or coordinate system at the selected face, effectively setting this face as the working location for any subsequent operations inside the with block.*
>
> Hole(hole_diameter / 2)
>
> : *This creates a hole in the selected face. The radius of the hole is specified as hole_diameter / 2. The hole is placed at the origin of the selected face, based on the local coordinate system created by Locations(). As the depth of the hole is not provided it is assumed to go entirely through the part.*
>
>
*Next the slot needs to be created in the bracket with will be done by sketching a slot on the front of the bracket and extruding the sketch through the part.*
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch(bracket.faces().sort_by(Axis.Y)[0]):
SlotOverall(20 * MM, hole_diameter)
extrude(amount=-thickness, mode=Mode.SUBTRACT)
:::
:::
*Here's a detailed explanation of what each part does:*
>
>
> with BuildSketch(bracket.faces().sort_by(Axis.Y)\[0\]):
>
> : *This line sets up a sketching context.*
>
> - bracket.faces() *retrieves all the faces of the bracket part.*
>
> - sort_by(Axis.Y) *sorts the faces along the Y-axis, arranging them from the lowest Y-coordinate to the highest.*
>
> - \[0\] *selects the first face in this sorted list, which is the one located at the lowest Y-coordinate, the nearest face of the part.*
>
> - BuildSketch() *creates a new sketching context on this selected face, where 2D geometry will be drawn.*
>
> SlotOverall(20, hole_diameter)
>
> : *This command draws a slot (a rounded rectangle or elongated hole) on the selected face. The slot has a total length of 20 mm and a width equal to hole_diameter. The slot is defined within the 2D sketch on the selected face of the bracket.*
>
> extrude(amount=-thickness, mode=Mode.SUBTRACT)
>
> : extrude() *takes the 2D sketch (the slot) and extends it into the 3D space by a distance equal to -thickness, creating a cut into the part. The negative value (-thickness) indicates that the extrusion is directed inward into the part (a cut).* mode=Mode.SUBTRACT *specifies that the extrusion is a subtractive operation, meaning it removes material from the bracket, effectively cutting the slot through the face of the part.*
>
>
*Although beyond the scope of this tutorial, joints could be defined for each of the holes to allow programmatic connection to other parts.*
:::
::: {#tutorial_design.xhtml#step-10-plan-for-parametric-flexibility .section}
## Step 10. Plan for Parametric Flexibility
Wherever possible, make your design parametric, allowing dimensions and features to be easily adjusted later. This flexibility can be crucial if the design needs modifications or if variations of the part are needed.
*The dimensions of the bracket are defined as follows:*
::: {.highlight-build123d .notranslate}
::: highlight
thickness = 3 * MM
width = 25 * MM
length = 50 * MM
height = 25 * MM
hole_diameter = 5 * MM
bend_radius = 5 * MM
fillet_radius = 2 * MM
:::
:::
:::
::: {#tutorial_design.xhtml#step-11-test-fit-and-tolerances .section}
## Step 11. Test Fit and Tolerances
Visualize the fit of the part within its intended assembly. Consider tolerances for manufacturing, such as clearance between moving parts or shrinkage for 3D-printed parts. Adjust the design as needed to ensure real-world functionality.
:::
::: {#tutorial_design.xhtml#summary .section}
## Summary
These steps should guide you through a logical and efficient workflow in build123d (or any CAD tool), helping you to design parts with accuracy and ease.
*The entire code block for the bracket example is shown here:*
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show_all
thickness = 3 * MM
width = 25 * MM
length = 50 * MM
height = 25 * MM
hole_diameter = 5 * MM
bend_radius = 5 * MM
fillet_radius = 2 * MM
with BuildPart() as bracket:
with BuildSketch() as sketch:
with BuildLine() as profile:
FilletPolyline(
(0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius
)
offset(amount=thickness, side=Side.LEFT)
make_face()
mirror(about=Plane.YZ)
extrude(amount=width / 2)
mirror(about=Plane.XY)
corners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)[-1]
fillet(corners, fillet_radius)
with Locations(bracket.faces().sort_by(Axis.X)[-1]):
Hole(hole_diameter / 2)
with BuildSketch(bracket.faces().sort_by(Axis.Y)[0]):
SlotOverall(20 * MM, hole_diameter)
extrude(amount=-thickness, mode=Mode.SUBTRACT)
show_all()
:::
:::
{.align-center}
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tutorial_selectors.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tutorial_selectors.xhtml#selector-tutorial .section}
# Selector Tutorial
This tutorial provides a step by step guide in using selectors as we create this part:
{.align-center}
::: {.admonition .note}
Note
One can see any object in the following tutorial by using the `ocp_vscode`{.docutils .literal .notranslate} (or any other supported viewer) by using the `show(object_to_be_viewed)`{.docutils .literal .notranslate} command. Alternatively, the `show_all()`{.docutils .literal .notranslate} command will display all objects that have been assigned an identifier.
:::
::: {#tutorial_selectors.xhtml#step-1-setup .section}
## Step 1: Setup
Before getting to the CAD operations, this selector script needs to import the build123d environment.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
:::
:::
:::
::: {#tutorial_selectors.xhtml#step-2-create-base-with-buildpart .section}
## Step 2: Create Base with BuildPart
To start off, the part will be based on a cylinder so we'll use the [`Cylinder`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Cylinder "objects_part.Cylinder"){.hxr-hoverxref .hxr-tooltip .reference .internal} object of [`BuildPart`{.xref .py .py-class .docutils .literal .notranslate}](#build_part.xhtml#build_part.BuildPart "build_part.BuildPart"){.hxr-hoverxref .hxr-tooltip .reference .internal}:
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
with BuildPart() as example:
Cylinder(radius=10, height=3)
:::
:::
:::
::: {#tutorial_selectors.xhtml#step-3-place-sketch-on-top-of-base .section}
## Step 3: Place Sketch on top of base
The next set of features in this design will be created on the top of the cylinder and be described by a planar sketch ([`BuildSketch`{.xref .py .py-class .docutils .literal .notranslate}](#build_sketch.xhtml#build_sketch.BuildSketch "build_sketch.BuildSketch"){.hxr-hoverxref .hxr-tooltip .reference .internal} is the tool for drawing on planar surfaces) , so we'll create a sketch centered on the top of the cylinder. To locate this sketch we'll use the cylinder's top Face as shown here:
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
with BuildPart() as example:
Cylinder(radius=10, height=3)
with BuildSketch(example.faces().sort_by(Axis.Z)[-1]):
:::
:::
Here we're using selectors to find that top Face - let's break down `example.faces().sort_by(Axis.Z)[-1]`{.docutils .literal .notranslate}:
::: {#tutorial_selectors.xhtml#step-3a-extract-faces-from-a-part .section}
### Step 3a: Extract Faces from a part
The first sub-step is the extraction of all of the Faces from the part that we're building. The [`BuildPart`{.xref .py .py-class .docutils .literal .notranslate}](#build_part.xhtml#build_part.BuildPart "build_part.BuildPart"){.hxr-hoverxref .hxr-tooltip .reference .internal} instance was assigned the identifier `example`{.docutils .literal .notranslate} so `example.faces()`{.docutils .literal .notranslate} will extract all of the Faces from that part into a custom python `list`{.docutils .literal .notranslate} - a [`ShapeList`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
:::
::: {#tutorial_selectors.xhtml#step-3b-get-top-face .section}
### Step 3b: Get top Face
The next sub-step is to sort the ShapeList of Faces by their position with respect to the Z Axis. The `sort_by`{.docutils .literal .notranslate} method will sort the list by relative position of the object's center to the `Axis.Z`{.docutils .literal .notranslate} and `[-1]`{.docutils .literal .notranslate} selects the last item on that list - or return the top Face of the `example`{.docutils .literal .notranslate} part.
:::
:::
::: {#tutorial_selectors.xhtml#step-4-create-hole-shape .section}
## Step 4: Create hole shape
The object has a hexagonal hole in the top with a central cylinder which we'll describe in the sketch.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
with BuildPart() as example:
Cylinder(radius=10, height=3)
with BuildSketch(example.faces().sort_by(Axis.Z)[-1]):
RegularPolygon(radius=7, side_count=6)
Circle(radius=4, mode=Mode.SUBTRACT)
:::
:::
::: {#tutorial_selectors.xhtml#step-4a-draw-a-hexagon .section}
### Step 4a: Draw a hexagon
We'll create a hexagon with the use of [`RegularPolygon`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.RegularPolygon "objects_sketch.RegularPolygon"){.hxr-hoverxref .hxr-tooltip .reference .internal} object with six sides.
:::
::: {#tutorial_selectors.xhtml#step-4b-create-a-hole-in-the-hexagon .section}
### Step 4b: Create a hole in the hexagon
To create the hole we'll subtract a [`Circle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Circle "objects_sketch.Circle"){.hxr-hoverxref .hxr-tooltip .reference .internal} from the sketch by using `mode=Mode.SUBTRACT`{.docutils .literal .notranslate}. The sketch now described the hexagonal hole that we want to make in the [`Cylinder`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Cylinder "objects_part.Cylinder"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
:::
:::
::: {#tutorial_selectors.xhtml#step-5-create-the-hole .section}
## Step 5: Create the hole
To create the hole we'll [`extrude()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} the sketch we just created into the [`Cylinder`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Cylinder "objects_part.Cylinder"){.hxr-hoverxref .hxr-tooltip .reference .internal} and subtract it.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
with BuildPart() as example:
Cylinder(radius=10, height=3)
with BuildSketch(example.faces().sort_by(Axis.Z)[-1]):
RegularPolygon(radius=7, side_count=6)
Circle(radius=4, mode=Mode.SUBTRACT)
extrude(amount=-2, mode=Mode.SUBTRACT)
:::
:::
Note that `amount=-2`{.docutils .literal .notranslate} indicates extruding into the part and - just like with the sketch - `mode=Mode.SUBTRACT`{.docutils .literal .notranslate} instructs the builder to subtract this hexagonal shape from the part under construction.
At this point the part looks like:
{.align-center}
:::
::: {#tutorial_selectors.xhtml#step-6-fillet-the-top-perimeter-edge .section}
## Step 6: Fillet the top perimeter Edge
The final step is to apply a fillet to the top perimeter.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
with BuildPart() as example:
Cylinder(radius=10, height=3)
with BuildSketch(example.faces().sort_by(Axis.Z)[-1]):
RegularPolygon(radius=7, side_count=6)
Circle(radius=4, mode=Mode.SUBTRACT)
extrude(amount=-2, mode=Mode.SUBTRACT)
fillet(
example.edges()
.filter_by(GeomType.CIRCLE)
.sort_by(SortBy.RADIUS)[-2:]
.sort_by(Axis.Z)[-1],
radius=1,
)
show(example)
:::
:::
Here we're using the [`fillet()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.fillet "operations_generic.fillet"){.hxr-hoverxref .hxr-tooltip .reference .internal} operation which needs two things: the edge(s) to fillet and the radius of the fillet. To provide the edge, we'll use more selectors as described in the following sub-steps.
::: {#tutorial_selectors.xhtml#step-6a-extract-all-the-edges .section}
### Step 6a: Extract all the Edges
Much like selecting Faces in Step 3a, we'll select all of the `example`{.docutils .literal .notranslate} part's edges with `example.edges()`{.docutils .literal .notranslate}.
:::
::: {#tutorial_selectors.xhtml#step-6b-filter-the-edges-for-circles .section}
### Step 6b: Filter the Edges for circles
Since we know that the edge we're looking for is a circle, we can filter the edges selected in Step 6a for just those that are of geometric type `CIRCLE`{.docutils .literal .notranslate} with `example.edges().filter_by(GeomType.CIRCLE)`{.docutils .literal .notranslate}. This step removes all of the Edges of the hexagon hole.
:::
::: {#tutorial_selectors.xhtml#step-6c-sort-the-circles-by-radius .section}
### Step 6c: Sort the circles by radius
The perimeter are the largest circles - the central cylinder must be excluded - so we'll sort all of the circles by their radius with: `example.edges().filter_by(GeomType.CIRCLE).sort_by(SortBy.RADIUS)`{.docutils .literal .notranslate}.
:::
::: {#tutorial_selectors.xhtml#step-6d-slice-the-list-to-get-the-two-largest .section}
### Step 6d: Slice the list to get the two largest
We know that the `example`{.docutils .literal .notranslate} part has two perimeter circles so we'll select just the top two edges from the sorted circle list with: `example.edges().filter_by(GeomType.CIRCLE).sort_by(SortBy.RADIUS)[-2:]`{.docutils .literal .notranslate}. The syntax of this slicing operation is standard python list slicing.
:::
::: {#tutorial_selectors.xhtml#step-6e-select-the-top-edge .section}
### Step 6e: Select the top Edge
The last sub-step is to select the top perimeter edge, the one with the greatest Z value which we'll do with the `sort_by(Axis.Z)[-1]`{.docutils .literal .notranslate} method just like Step 3b - note that these methods work on all Shape objects (Edges, Wires, Faces, Solids, and Compounds) - with: `example.edges().filter_by(GeomType.CIRCLE).sort_by(SortBy.RADIUS)[-2:].sort_by(Axis.Z)[-1]`{.docutils .literal .notranslate}.
:::
:::
::: {#tutorial_selectors.xhtml#conclusion .section}
## Conclusion
By using selectors as we have in this example we've used methods of identifying features that are robust to features changing within the part. We've also avoided the classic CAD "Topological naming problem" by never referring to features with names or tags that could become obsolete as the part changes.
When possible, avoid using static list indices to refer to features extracted from methods like `edges()`{.docutils .literal .notranslate} as the order within the list is not guaranteed to remain the same.
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tutorial_lego.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tutorial_lego.xhtml#lego-tutorial .section}
# Lego Tutorial
This tutorial provides a step by step guide to creating a script to build a parametric Lego block as shown here:
{.align-center}
::: {#tutorial_lego.xhtml#step-1-setup .section}
## Step 1: Setup
Before getting to the CAD operations, this Lego script needs to import the build123d environment. There are over 100 python classes in build123d so we'll just import them all with a `from build123d import *`{.docutils .literal .notranslate} but [[there are other options]{.std .std-ref}](#tips.xhtml#are-glob-imports-bad-practice){.reference .internal} that we won't explore here.
The dimensions of the Lego block follow. A key parameter is `pip_count`{.docutils .literal .notranslate}, the length of the Lego blocks in pips. This parameter must be at least 2.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show_object
pip_count = 6
lego_unit_size = 8
pip_height = 1.8
pip_diameter = 4.8
block_length = lego_unit_size * pip_count
block_width = 16
base_height = 9.6
block_height = base_height + pip_height
support_outer_diameter = 6.5
support_inner_diameter = 4.8
ridge_width = 0.6
ridge_depth = 0.3
wall_thickness = 1.2
:::
:::
:::
::: {#tutorial_lego.xhtml#step-2-part-builder .section}
## Step 2: Part Builder
The Lego block will be created by the `BuildPart`{.docutils .literal .notranslate} builder as it's a discrete three dimensional part; therefore, we'll instantiate a `BuildPart`{.docutils .literal .notranslate} with the name `lego`{.docutils .literal .notranslate}.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
:::
:::
:::
::: {#tutorial_lego.xhtml#step-3-sketch-builder .section}
## Step 3: Sketch Builder
Lego blocks have quite a bit of internal structure. To create this structure we'll draw a two dimensional sketch that will later be extruded into a three dimensional object. As this sketch will be part of the lego part, we'll create a sketch builder in the context of the part builder as follows:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
:::
:::
Note that builder instance names are optional - we'll use `plan`{.docutils .literal .notranslate} to reference the sketch. Also note that all sketch objects are filled or 2D faces not just perimeter lines.
:::
::: {#tutorial_lego.xhtml#step-4-perimeter-rectangle .section}
## Step 4: Perimeter Rectangle
The first object in the sketch is going to be a rectangle with the dimensions of the outside of the Lego block. The following step is going to refer to this rectangle, so it will be assigned the identifier `perimeter`{.docutils .literal .notranslate}.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
# Start with a Rectangle the size of the block
perimeter = Rectangle(width=block_length, height=block_width)
:::
:::
Once the `Rectangle`{.docutils .literal .notranslate} object is created the sketch appears as follows:
{.align-center}
:::
::: {#tutorial_lego.xhtml#step-5-offset-to-create-walls .section}
## Step 5: Offset to Create Walls
To create the walls of the block the rectangle that we've created needs to be hollowed out. This will be done with the `Offset`{.docutils .literal .notranslate} operation which is going to create a new object from `perimeter`{.docutils .literal .notranslate}.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
# Start with a Rectangle the size of the block
perimeter = Rectangle(width=block_length, height=block_width)
# Subtract an offset to create the block walls
offset(
perimeter,
-wall_thickness,
kind=Kind.INTERSECTION,
mode=Mode.SUBTRACT,
)
:::
:::
The first parameter to `Offset`{.docutils .literal .notranslate} is the reference object. The `amount`{.docutils .literal .notranslate} is a negative value to indicate that the offset should be internal. The `kind`{.docutils .literal .notranslate} parameter controls the shape of the corners - `Kind.INTERSECTION`{.docutils .literal .notranslate} will create square corners. Finally, the `mode`{.docutils .literal .notranslate} parameter controls how this object will be placed in the sketch - in this case subtracted from the existing sketch. The result is shown here:
{.align-center}
Now the sketch consists of a hollow rectangle.
:::
::: {#tutorial_lego.xhtml#step-6-create-internal-grid .section}
## Step 6: Create Internal Grid
The interior of the Lego block has small ridges on all four internal walls. These ridges will be created as a grid of thin rectangles so the positions of the centers of these rectangles need to be defined. A pair of `GridLocations`{.docutils .literal .notranslate} location contexts will define these positions, one for the horizontal bars and one for the vertical bars. As the `Rectangle`{.docutils .literal .notranslate} objects are in the scope of a location context (`GridLocations`{.docutils .literal .notranslate} in this case) that defined multiple points, multiple rectangles are created.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
# Start with a Rectangle the size of the block
perimeter = Rectangle(width=block_length, height=block_width)
# Subtract an offset to create the block walls
offset(
perimeter,
-wall_thickness,
kind=Kind.INTERSECTION,
mode=Mode.SUBTRACT,
)
# Add a grid of lengthwise and widthwise bars
with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):
Rectangle(width=block_length, height=ridge_width)
with GridLocations(lego_unit_size, 0, pip_count, 1):
Rectangle(width=ridge_width, height=block_width)
:::
:::
Here we can see that the first `GridLocations`{.docutils .literal .notranslate} creates two positions which causes two horizontal rectangles to be created. The second `GridLocations`{.docutils .literal .notranslate} works in the same way but creates `pip_count`{.docutils .literal .notranslate} positions and therefore `pip_count`{.docutils .literal .notranslate} rectangles. Note that keyword parameter are optional in this case.
The result looks like this:
{.align-center}
:::
::: {#tutorial_lego.xhtml#step-7-create-ridges .section}
## Step 7: Create Ridges
To convert the internal grid to ridges, the center needs to be removed. This will be done with another `Rectangle`{.docutils .literal .notranslate}.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
# Start with a Rectangle the size of the block
perimeter = Rectangle(width=block_length, height=block_width)
# Subtract an offset to create the block walls
offset(
perimeter,
-wall_thickness,
kind=Kind.INTERSECTION,
mode=Mode.SUBTRACT,
)
# Add a grid of lengthwise and widthwise bars
with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):
Rectangle(width=block_length, height=ridge_width)
with GridLocations(lego_unit_size, 0, pip_count, 1):
Rectangle(width=ridge_width, height=block_width)
# Subtract a rectangle leaving ribs on the block walls
Rectangle(
block_length - 2 * (wall_thickness + ridge_depth),
block_width - 2 * (wall_thickness + ridge_depth),
mode=Mode.SUBTRACT,
)
:::
:::
The `Rectangle`{.docutils .literal .notranslate} is subtracted from the sketch to leave the ridges as follows:
{.align-center}
:::
::: {#tutorial_lego.xhtml#step-8-hollow-circles .section}
## Step 8: Hollow Circles
Lego blocks use a set of internal hollow cylinders that the pips push against to hold two blocks together. These will be created with `Circle`{.docutils .literal .notranslate}.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
# Start with a Rectangle the size of the block
perimeter = Rectangle(width=block_length, height=block_width)
# Subtract an offset to create the block walls
offset(
perimeter,
-wall_thickness,
kind=Kind.INTERSECTION,
mode=Mode.SUBTRACT,
)
# Add a grid of lengthwise and widthwise bars
with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):
Rectangle(width=block_length, height=ridge_width)
with GridLocations(lego_unit_size, 0, pip_count, 1):
Rectangle(width=ridge_width, height=block_width)
# Subtract a rectangle leaving ribs on the block walls
Rectangle(
block_length - 2 * (wall_thickness + ridge_depth),
block_width - 2 * (wall_thickness + ridge_depth),
mode=Mode.SUBTRACT,
)
# Add a row of hollow circles to the center
with GridLocations(
x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1
):
Circle(radius=support_outer_diameter / 2)
Circle(radius=support_inner_diameter / 2, mode=Mode.SUBTRACT)
:::
:::
Here another `GridLocations`{.docutils .literal .notranslate} is used to position the centers of the circles. Note that since both `Circle`{.docutils .literal .notranslate} objects are in the scope of the location context, both Circles will be positioned at these locations.
Once the Circles are added, the sketch is complete and looks as follows:
{.align-center}
:::
::: {#tutorial_lego.xhtml#step-9-extruding-sketch-into-walls .section}
## Step 9: Extruding Sketch into Walls
Now that the sketch is complete it needs to be extruded into the three dimensional wall object.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
# Start with a Rectangle the size of the block
perimeter = Rectangle(width=block_length, height=block_width)
# Subtract an offset to create the block walls
offset(
perimeter,
-wall_thickness,
kind=Kind.INTERSECTION,
mode=Mode.SUBTRACT,
)
# Add a grid of lengthwise and widthwise bars
with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):
Rectangle(width=block_length, height=ridge_width)
with GridLocations(lego_unit_size, 0, pip_count, 1):
Rectangle(width=ridge_width, height=block_width)
# Subtract a rectangle leaving ribs on the block walls
Rectangle(
block_length - 2 * (wall_thickness + ridge_depth),
block_width - 2 * (wall_thickness + ridge_depth),
mode=Mode.SUBTRACT,
)
# Add a row of hollow circles to the center
with GridLocations(
x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1
):
Circle(radius=support_outer_diameter / 2)
Circle(radius=support_inner_diameter / 2, mode=Mode.SUBTRACT)
# Extrude this base sketch to the height of the walls
extrude(amount=base_height - wall_thickness)
:::
:::
Note how the `Extrude`{.docutils .literal .notranslate} operation is no longer in the `BuildSketch`{.docutils .literal .notranslate} scope and has returned back into the `BuildPart`{.docutils .literal .notranslate} scope. This causes `BuildSketch`{.docutils .literal .notranslate} to exit and transfer the sketch that we've created to `BuildPart`{.docutils .literal .notranslate} for further processing by `Extrude`{.docutils .literal .notranslate}.
The result is:
{.align-center}
:::
::: {#tutorial_lego.xhtml#step-10-adding-a-top .section}
## Step 10: Adding a Top
Now that the walls are complete, the top of the block needs to be added. Although this could be done with another sketch, we'll add a box to the top of the walls.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
# Start with a Rectangle the size of the block
perimeter = Rectangle(width=block_length, height=block_width)
# Subtract an offset to create the block walls
offset(
perimeter,
-wall_thickness,
kind=Kind.INTERSECTION,
mode=Mode.SUBTRACT,
)
# Add a grid of lengthwise and widthwise bars
with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):
Rectangle(width=block_length, height=ridge_width)
with GridLocations(lego_unit_size, 0, pip_count, 1):
Rectangle(width=ridge_width, height=block_width)
# Subtract a rectangle leaving ribs on the block walls
Rectangle(
block_length - 2 * (wall_thickness + ridge_depth),
block_width - 2 * (wall_thickness + ridge_depth),
mode=Mode.SUBTRACT,
)
# Add a row of hollow circles to the center
with GridLocations(
x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1
):
Circle(radius=support_outer_diameter / 2)
Circle(radius=support_inner_diameter / 2, mode=Mode.SUBTRACT)
# Extrude this base sketch to the height of the walls
extrude(amount=base_height - wall_thickness)
# Create a box on the top of the walls
with Locations((0, 0, lego.vertices().sort_by(Axis.Z)[-1].Z)):
# Create the top of the block
Box(
length=block_length,
width=block_width,
height=wall_thickness,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
:::
:::
To position the top, we'll describe the top center of the lego walls with a `Locations`{.docutils .literal .notranslate} context. To determine the height we'll extract that from the `lego.part`{.docutils .literal .notranslate} by using the `vertices()`{.docutils .literal .notranslate} method which returns a list of the positions of all of the vertices of the Lego block so far. Since we're interested in the top, we'll sort by the vertical (Z) axis and take the top of the list `sort_by(Axis.Z)[-1]`{.docutils .literal .notranslate}. Finally, the `Z`{.docutils .literal .notranslate} property of this vertex will return just the height of the top. Note that the `X`{.docutils .literal .notranslate} and `Y`{.docutils .literal .notranslate} values are not used from the selected vertex as there are no vertices in the center of the block.
Within the scope of this `Locations`{.docutils .literal .notranslate} context, a `Box`{.docutils .literal .notranslate} is created, centered at the intersection of the x and y axis but not in the z thus aligning with the top of the walls.
The base is closed now as shown here:
{.align-center}
:::
::: {#tutorial_lego.xhtml#step-11-adding-pips .section}
## Step 11: Adding Pips
The final step is to add the pips to the top of the Lego block. To do this we'll create a new workplane on top of the block where we can position the pips.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch() as plan:
# Start with a Rectangle the size of the block
perimeter = Rectangle(width=block_length, height=block_width)
# Subtract an offset to create the block walls
offset(
perimeter,
-wall_thickness,
kind=Kind.INTERSECTION,
mode=Mode.SUBTRACT,
)
# Add a grid of lengthwise and widthwise bars
with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):
Rectangle(width=block_length, height=ridge_width)
with GridLocations(lego_unit_size, 0, pip_count, 1):
Rectangle(width=ridge_width, height=block_width)
# Subtract a rectangle leaving ribs on the block walls
Rectangle(
block_length - 2 * (wall_thickness + ridge_depth),
block_width - 2 * (wall_thickness + ridge_depth),
mode=Mode.SUBTRACT,
)
# Add a row of hollow circles to the center
with GridLocations(
x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1
):
Circle(radius=support_outer_diameter / 2)
Circle(radius=support_inner_diameter / 2, mode=Mode.SUBTRACT)
# Extrude this base sketch to the height of the walls
extrude(amount=base_height - wall_thickness)
# Create a box on the top of the walls
with Locations((0, 0, lego.vertices().sort_by(Axis.Z)[-1].Z)):
# Create the top of the block
Box(
length=block_length,
width=block_width,
height=wall_thickness,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
# Create a workplane on the top of the block
with BuildPart(lego.faces().sort_by(Axis.Z)[-1]):
# Create a grid of pips
with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):
Cylinder(
radius=pip_diameter / 2,
height=pip_height,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
:::
:::
In this case, the workplane is created from the top Face of the Lego block by using the `faces`{.docutils .literal .notranslate} method and then sorted vertically and taking the top one `sort_by(Axis.Z)[-1]`{.docutils .literal .notranslate}.
On the new workplane, a grid of locations is created and a number of `Cylinder`{.docutils .literal .notranslate}'s are positioned at each location.
{.align-center}
This completes the Lego block. To access the finished product, refer to the builder's internal object as shown here:
Builder Object
------------- --------
BuildLine line
BuildSketch sketch
BuildPart part
so in this case the Lego block is `lego.part`{.docutils .literal .notranslate}. To display the part use `show_object(lego.part)`{.docutils .literal .notranslate} or `show(lego.part)`{.docutils .literal .notranslate} depending on the viewer. The part could also be exported to a STL or STEP file by referencing `lego.part`{.docutils .literal .notranslate}.
::: {.admonition .note}
Note
Viewers that don't directly support build123d my require a raw OpenCascade object. In this case, append `.wrapped`{.docutils .literal .notranslate} to the object (e.g.) `show_object(lego.part.wrapped)`{.docutils .literal .notranslate}.
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tutorial_joints.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tutorial_joints.xhtml#joint-tutorial .section}
[]{#tutorial_joints.xhtml#id1}
# Joint Tutorial
This tutorial provides a step by step guide in using [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}'s as we create a box with a hinged lid to illustrate the use of three different [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal} types.
{.align-center}
::: {#tutorial_joints.xhtml#step-1-setup .section}
## Step 1: Setup
Before getting to the CAD operations, this selector script needs to import the build123d environment.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
:::
:::
:::
::: {#tutorial_joints.xhtml#step-2-create-hinge .section}
## Step 2: Create Hinge
This example uses a common Butt Hinge to connect the lid to the box base so a `Hinge`{.docutils .literal .notranslate} class is used to create that can create either of the two hinge leaves. As the focus of this tutorial is the joints and not the CAD operations to create objects, this code is not described in detail.
::: {.highlight-build123d .notranslate}
::: highlight
class Hinge(Compound):
"""Hinge
Half a simple hinge with several joints. The joints are:
- "leaf": RigidJoint where hinge attaches to object
- "hinge_axis": RigidJoint (inner) or RevoluteJoint (outer)
- "hole0", "hole1", "hole2": CylindricalJoints for attachment screws
Args:
width (float): width of one leaf
length (float): hinge length
barrel_diameter (float): size of hinge pin barrel
thickness (float): hinge leaf thickness
pin_diameter (float): hinge pin diameter
inner (bool, optional): inner or outer half of hinge . Defaults to True.
"""
def __init__(
self,
width: float,
length: float,
barrel_diameter: float,
thickness: float,
pin_diameter: float,
inner: bool = True,
):
# The profile of the hinge used to create the tabs
with BuildPart() as hinge_profile:
with BuildSketch():
for i, loc in enumerate(
GridLocations(0, length / 5, 1, 5, align=(Align.MIN, Align.MIN))
):
if i % 2 == inner:
with Locations(loc):
Rectangle(width, length / 5, align=(Align.MIN, Align.MIN))
Rectangle(
width - barrel_diameter,
length,
align=(Align.MIN, Align.MIN),
)
extrude(amount=-barrel_diameter)
# The hinge pin
with BuildPart() as pin:
Cylinder(
radius=pin_diameter / 2,
height=length,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
with BuildPart(pin.part.faces().sort_by(Axis.Z)[-1]) as pin_head:
Cylinder(
radius=barrel_diameter / 2,
height=pin_diameter,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
fillet(
pin_head.edges(Select.LAST).filter_by(GeomType.CIRCLE),
radius=pin_diameter / 3,
)
# Either the external and internal leaf with joints
with BuildPart() as leaf_builder:
with BuildSketch():
with BuildLine():
l1 = Line((0, 0), (width - barrel_diameter / 2, 0))
l2 = RadiusArc(
l1 @ 1,
l1 @ 1 + Vector(0, barrel_diameter),
-barrel_diameter / 2,
)
l3 = RadiusArc(
l2 @ 1,
(
width - barrel_diameter,
barrel_diameter / 2,
),
-barrel_diameter / 2,
)
l4 = Line(l3 @ 1, (width - barrel_diameter, thickness))
l5 = Line(l4 @ 1, (0, thickness))
Line(l5 @ 1, l1 @ 0)
make_face()
with Locations(
(width - barrel_diameter / 2, barrel_diameter / 2)
) as pin_center:
Circle(pin_diameter / 2 + 0.1 * MM, mode=Mode.SUBTRACT)
extrude(amount=length)
add(hinge_profile.part, rotation=(90, 0, 0), mode=Mode.INTERSECT)
# Create holes for fasteners
with Locations(leaf_builder.part.faces().filter_by(Axis.Y)[-1]):
with GridLocations(0, length / 3, 1, 3):
holes = CounterSinkHole(3 * MM, 5 * MM)
# Add the hinge pin to the external leaf
if not inner:
with Locations(pin_center.locations[0]):
add(pin.part)
:::
:::
Once the two leaves have been created they will look as follows:
{style="width: 40%;"} {style="width: 40%;"}
Note that the XYZ indicators and a circle around the hinge pin indicate joints that are discussed below.
:::
::: {#tutorial_joints.xhtml#step-3-add-joints-to-the-hinge-leaf .section}
## Step 3: Add Joints to the Hinge Leaf
The hinge includes five joints:
- A `RigidJoint`{.xref .py .py-class .docutils .literal .notranslate} to attach the leaf
- A `RigidJoint`{.xref .py .py-class .docutils .literal .notranslate} or `RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate} as the hinge Axis
- Three `CylindricalJoint`{.xref .py .py-class .docutils .literal .notranslate}'s for the countersunk screws
::: {#tutorial_joints.xhtml#step-3a-leaf-joint .section}
### Step 3a: Leaf Joint
The first joint to add is a `RigidJoint`{.xref .py .py-class .docutils .literal .notranslate} that is used to fix the hinge leaf to the box or lid.
::: {.highlight-build123d .notranslate}
::: highlight
#
# Leaf attachment
RigidJoint(
label="leaf",
joint_location=Location(
(width - barrel_diameter, 0, length / 2), (90, 0, 0)
),
)
:::
:::
Each joint has a label which identifies it - here the string "leaf" is used, the `to_part`{.docutils .literal .notranslate} binds the joint to `leaf_builder.part`{.docutils .literal .notranslate} (i.e. the part being built), and `joint_location`{.docutils .literal .notranslate} is specified as middle of the leaf along the edge of the pin. Note that [`Location`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal} objects describe both a position and orientation which is why there are two tuples (the orientation listed is rotate about the X axis 90 degrees).
:::
::: {#tutorial_joints.xhtml#step-3b-hinge-joint .section}
### Step 3b: Hinge Joint
The second joint to add is either a `RigidJoint`{.xref .py .py-class .docutils .literal .notranslate} (on the inner leaf) or a `RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate} (on the outer leaf) that describes the hinge axis.
::: {.highlight-build123d .notranslate}
::: highlight
#
# Leaf attachment
RigidJoint(
label="leaf",
joint_location=Location(
(width - barrel_diameter, 0, length / 2), (90, 0, 0)
),
)
# [Hinge Axis] (fixed with inner)
if inner:
RigidJoint(
"hinge_axis",
joint_location=Location(
(width - barrel_diameter / 2, barrel_diameter / 2, 0)
),
)
else:
RevoluteJoint(
"hinge_axis",
axis=Axis(
(width - barrel_diameter / 2, barrel_diameter / 2, 0), (0, 0, 1)
),
angular_range=(90, 270),
)
:::
:::
The inner leaf just pivots around the outer leaf and therefore the simple `RigidJoint`{.xref .py .py-class .docutils .literal .notranslate} is used to define the Location of this pivot. The outer leaf contains the more complex `RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate} which defines an axis of rotation and angular limits to that rotation (90 and 270 in this example as the two leaves will interfere with each other outside of this range). Note that the maximum angle must be greater than the minimum angle and therefore may be greater than 360°. Other types of joints have linear ranges as well as angular ranges.
:::
::: {#tutorial_joints.xhtml#step-3c-fastener-joints .section}
### Step 3c: Fastener Joints
The third set of joints to add are `CylindricalJoint`{.xref .py .py-class .docutils .literal .notranslate}'s that describe how the countersunk screws used to attach the leaves move.
::: {.highlight-build123d .notranslate}
::: highlight
hole_locations = [hole.location for hole in holes]
for hole, hole_location in enumerate(hole_locations):
CylindricalJoint(
label="hole" + str(hole),
axis=Axis(hole_location),
linear_range=(-2 * CM, 2 * CM),
angular_range=(0, 360),
)
:::
:::
Much like the `RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate}, a `CylindricalJoint`{.xref .py .py-class .docutils .literal .notranslate} has an Axis of motion but this type of joint allows both movement around and along this axis - exactly as a screw would move. Here is the Axis is setup such that a position of 0 aligns with the screw being fully set in the hole and positive numbers indicate the distance the head of the screw is above the leaf surface. One could have reversed the direction of the Axis such that negative position values would correspond to a screw now fully in the hole - whatever makes sense to the situation. The angular range of this joint is set to (0°, 360°) as there is no limit to the angular rotation of the screw (one could choose to model thread pitch and calculate position from angle or vice-versa).
:::
::: {#tutorial_joints.xhtml#step-3d-call-super .section}
### Step 3d: Call Super
To finish off, the base class for the Hinge class is initialized:
::: {.highlight-build123d .notranslate}
::: highlight
super().__init__(leaf_builder.part.wrapped, joints=leaf_builder.part.joints)
:::
:::
:::
::: {#tutorial_joints.xhtml#step-3e-instantiate-hinge-leaves .section}
### Step 3e: Instantiate Hinge Leaves
Now that the Hinge class is complete it can be used to instantiate the two hinge leaves required to attach the box and lid together.
::: {.highlight-build123d .notranslate}
::: highlight
hinge_inner = Hinge(
width=5 * CM,
length=12 * CM,
barrel_diameter=1 * CM,
thickness=2 * MM,
pin_diameter=4 * MM,
)
hinge_outer = Hinge(
width=5 * CM,
length=12 * CM,
barrel_diameter=1 * CM,
thickness=2 * MM,
pin_diameter=4 * MM,
inner=False,
)
:::
:::
:::
:::
::: {#tutorial_joints.xhtml#step-4-create-the-box .section}
## Step 4: Create the Box
The box is created with [`BuildPart`{.xref .py .py-class .docutils .literal .notranslate}](#build_part.xhtml#build_part.BuildPart "build_part.BuildPart"){.hxr-hoverxref .hxr-tooltip .reference .internal} as a simple object - as shown below - let's focus on the joint used to attach the outer hinge leaf.
{.align-center}
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as box_builder:
box = Box(30 * CM, 30 * CM, 10 * CM)
offset(amount=-1 * CM, openings=box_builder.faces().sort_by(Axis.Z)[-1])
# Create a notch for the hinge
with Locations((-15 * CM, 0, 5 * CM)):
Box(2 * CM, 12 * CM, 4 * MM, mode=Mode.SUBTRACT)
bbox = box.bounding_box()
with Locations(
Plane(origin=(bbox.min.X, 0, bbox.max.Z - 30 * MM), z_dir=(-1, 0, 0))
):
with GridLocations(0, 40 * MM, 1, 3):
Hole(3 * MM, 1 * CM)
RigidJoint(
"hinge_attachment",
joint_location=Location((-15 * CM, 0, 4 * CM), (180, 90, 0)),
)
:::
:::
Since the hinge will be fixed to the box another `RigidJoint`{.xref .py .py-class .docutils .literal .notranslate} is used mark where the hinge will go. Note that the orientation of this [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal} will control how the hinge leaf is attached and is independent of the orientation of the hinge as it was constructed.
::: {#tutorial_joints.xhtml#step-4a-relocate-box .section}
### Step 4a: Relocate Box
Note that the position and orientation of the box's joints are given as a global [`Location`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal} when created but will be translated to a relative [`Location`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal} internally to allow the [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal} to "move" with the parent object. This allows users the freedom to relocate objects without having to recreate or modify [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}'s. Here is the box is moved upwards to show this property.
::: {.highlight-build123d .notranslate}
::: highlight
box = box_builder.part.moved(Location((0, 0, 5 * CM)))
:::
:::
:::
:::
::: {#tutorial_joints.xhtml#step-5-create-the-lid .section}
## Step 5: Create the Lid
Much like the box, the lid is created in a [`BuildPart`{.xref .py .py-class .docutils .literal .notranslate}](#build_part.xhtml#build_part.BuildPart "build_part.BuildPart"){.hxr-hoverxref .hxr-tooltip .reference .internal} context and is assigned a `RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}.
{.align-center}
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as lid_builder:
Box(30 * CM, 30 * CM, 1 * CM, align=(Align.MIN, Align.CENTER, Align.MIN))
with Locations((2 * CM, 0, 0)):
with GridLocations(0, 40 * MM, 1, 3):
Hole(3 * MM, 1 * CM)
RigidJoint(
"hinge_attachment",
joint_location=Location((0, 0, 0), (0, 0, 180)),
)
lid = lid_builder.part
:::
:::
Again, the original orientation of the lid and hinge inner leaf are not important, when the joints are connected together the parts will move into the correct position.
:::
::: {#tutorial_joints.xhtml#step-6-import-a-screw-and-bind-a-joint-to-it .section}
## Step 6: Import a Screw and bind a Joint to it
[`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}'s can be bound to simple objects the a [`Compound`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} imported - in this case a screw.
- screw STEP model: `M6-1x12-countersunk-screw.step`{.xref .download .docutils .literal .notranslate}
{.align-center}
::: {.highlight-build123d .notranslate}
::: highlight
m6_screw = import_step("M6-1x12-countersunk-screw.step")
m6_joint = RigidJoint("head", m6_screw, Location((0, 0, 0), (0, 0, 0)))
:::
:::
Here a simple `RigidJoint`{.xref .py .py-class .docutils .literal .notranslate} is bound to the top of the screw head such that it can be connected to the hinge's `CylindricalJoint`{.xref .py .py-class .docutils .literal .notranslate}.
:::
::: {#tutorial_joints.xhtml#step-7-connect-the-joints-together .section}
## Step 7: Connect the Joints together
This last step is the most interesting. Now that all of the joints have been defined and bound to their parent objects, they can be connected together.
::: {#tutorial_joints.xhtml#step-7a-hinge-to-box .section}
### Step 7a: Hinge to Box
To start, the outer hinge leaf will be connected to the box, as follows:
::: {.highlight-build123d .notranslate}
::: highlight
box.joints["hinge_attachment"].connect_to(hinge_outer.joints["leaf"])
:::
:::
Here the `hinge_attachment`{.docutils .literal .notranslate} joint of the `box`{.docutils .literal .notranslate} is connected to the `leaf`{.docutils .literal .notranslate} joint of `hinge_outer`{.docutils .literal .notranslate}. Note that the hinge leaf is the object to move. Once this line is executed, we get the following:
{.align-center}
:::
::: {#tutorial_joints.xhtml#step-7b-hinge-to-hinge .section}
### Step 7b: Hinge to Hinge
Next, the hinge inner leaf is connected to the hinge outer leaf which is attached to the box.
::: {.highlight-build123d .notranslate}
::: highlight
hinge_outer.joints["hinge_axis"].connect_to(hinge_inner.joints["hinge_axis"], angle=120)
:::
:::
As `hinge_outer.joints["hinge_axis"]`{.docutils .literal .notranslate} is a `RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate} there is an `angle`{.docutils .literal .notranslate} parameter that can be set (angles default to the minimum range value) - here to 120°. This is what that looks like:
{.align-center}
:::
::: {#tutorial_joints.xhtml#step-7c-lid-to-hinge .section}
### Step 7c: Lid to Hinge
Now the `lid`{.docutils .literal .notranslate} is connected to the `hinge_inner`{.docutils .literal .notranslate}:
::: {.highlight-build123d .notranslate}
::: highlight
hinge_inner.joints["leaf"].connect_to(lid.joints["hinge_attachment"])
:::
:::
which results in:
{.align-center}
Note how the lid is now in an open position. To close the lid just change the above `angle`{.docutils .literal .notranslate} parameter from 120° to 90°.
:::
::: {#tutorial_joints.xhtml#step-7d-screw-to-hinge .section}
### Step 7d: Screw to Hinge
The last step in this example is to place a screw in one of the hinges:
::: {.highlight-build123d .notranslate}
::: highlight
hinge_outer.joints["hole2"].connect_to(m6_joint, position=5 * MM, angle=30)
:::
:::
As the position is a positive number the screw is still proud of the hinge face as shown here:
{.align-center}
Try changing these position and angle values to "tighten" the screw.
:::
:::
::: {#tutorial_joints.xhtml#conclusion .section}
## Conclusion
Use a [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal} to locate two objects relative to each other with some degree of motion. Keep in mind that when using the `connect_to`{.docutils .literal .notranslate} method, `self`{.docutils .literal .notranslate} is always fixed and `other`{.docutils .literal .notranslate} will move to the appropriate [`Location`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
::: {.admonition .note}
Note
The joint symbols can be displayed as follows (your viewer may use `show`{.docutils .literal .notranslate} instead of `show_object`{.docutils .literal .notranslate}):
::: {.highlight-python .notranslate}
::: highlight
show_object(box.joints["hinge_attachment"].symbol, name="box attachment point")
:::
:::
or
::: {.highlight-python .notranslate}
::: highlight
show_object(m6_joint.symbol, name="m6 screw symbol")
:::
:::
or, with the ocp_vscode viewer
::: {.highlight-python .notranslate}
::: highlight
show(box, render_joints=True)
:::
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#examples_1.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#examples_1.xhtml#the-build123d-examples .section}
# The build123d Examples
::: {#examples_1.xhtml#overview .section}
## Overview
In the GitHub repository you will find an [examples folder](https://github.com/42sol-eu/build123d/tree/examples){.reference .external}[ \[https://github.com/42sol-eu/build123d/tree/examples\]]{.link-target}.
Most of the examples show the builder and algebra modes.
::: {.sd-container-fluid .sd-sphinx-override .sd-mb-4 .docutils}
::: {.sd-row .sd-row-cols-3 .sd-row-cols-xs-3 .sd-row-cols-sm-3 .sd-row-cols-md-3 .sd-row-cols-lg-3 .docutils}
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Benchy 🔨
:::
:::
[[Benchy]{.std .std-ref}](#examples_1.xhtml#examples-benchy){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Bicycle Tire 🔨
:::
:::
[[Bicycle Tire]{.std .std-ref}](#examples_1.xhtml#examples-bicycle-tire){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Canadian Flag Blowing in The Wind 🔨 ✏️
:::
:::
[[Canadian Flag Blowing in The Wind]{.std .std-ref}](#examples_1.xhtml#examples-canadian-flag){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Cast Bearing Unit 🔨
:::
:::
[[Cast Bearing Unit]{.std .std-ref}](#examples_1.xhtml#examples-cast-bearing-unit){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Circuit Board With Holes 🔨 ✏️
:::
:::
[[Circuit Board With Holes]{.std .std-ref}](#examples_1.xhtml#examples-circuit-board){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Clock Face 🔨 ✏️
:::
:::
[[Clock Face]{.std .std-ref}](#examples_1.xhtml#clock-face){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Fast Grid Holes ✏️
:::
:::
[[Fast Grid Holes]{.std .std-ref}](#examples_1.xhtml#fast-grid-holes){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Handle 🔨 ✏️
:::
:::
[[Handle]{.std .std-ref}](#examples_1.xhtml#handle){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Heat Exchanger 🔨 ✏️
:::
:::
[[Heat Exchanger]{.std .std-ref}](#examples_1.xhtml#heat-exchanger){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Key Cap 🔨 ✏️
:::
:::
[[Key Cap]{.std .std-ref}](#examples_1.xhtml#key-cap){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
(former) build123d Logo 🔨 ✏️
:::
:::
[[Former build123d Logo]{.std .std-ref}](#examples_1.xhtml#examples-build123d-logo){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Maker Coin 🔨
:::
:::
[[Maker Coin]{.std .std-ref}](#examples_1.xhtml#maker-coin){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Multi-Sketch Loft 🔨 ✏️
:::
:::
[[Multi-Sketch Loft]{.std .std-ref}](#examples_1.xhtml#multi-sketch-loft){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Peg Board J Hook 🔨 ✏️
:::
:::
[[Peg Board Hook]{.std .std-ref}](#examples_1.xhtml#peg-board-hook){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Platonic Solids ✏️
:::
:::
[[Platonic Solids]{.std .std-ref}](#examples_1.xhtml#platonic-solids){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Playing Cards 🔨
:::
:::
[[Playing Cards]{.std .std-ref}](#examples_1.xhtml#playing-cards){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Stud Wall ✏️
:::
:::
[[Stud Wall]{.std .std-ref}](#examples_1.xhtml#stud-wall){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Tea Cup 🔨 ✏️
:::
:::
[[Tea Cup]{.std .std-ref}](#examples_1.xhtml#tea-cup){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Toy Truck 🔨
:::
:::
[[Toy Truck]{.std .std-ref}](#examples_1.xhtml#toy-truck){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Vase 🔨 ✏️
:::
:::
[[Vase]{.std .std-ref}](#examples_1.xhtml#vase){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
:::
:::
:::
::: {#examples_1.xhtml#benchy .section}
[]{#examples_1.xhtml#examples-benchy}
## Benchy
{.align-center}
The Benchy examples shows how to import a STL model as a ``{=html}Solid``{=html} object with the class ``{=html}Mesher``{=html} and modify it by replacing chimney with a BREP version.
- Benchy STL model: `low_poly_benchy.stl`{.xref .download .docutils .literal .notranslate}
```{=html}
```
```{=html}
```
[Gallery]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
{.align-center} {.align-center}
:::
```{=html}
```
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
# Import the benchy as a Solid model
importer = Mesher()
benchy_stl = importer.read("low_poly_benchy.stl")[0]
with BuildPart() as benchy:
add(benchy_stl)
# Determine the plane that defines the top of the roof
vertices = benchy.vertices()
roof_vertices = vertices.filter_by_position(Axis.Z, 38, 42)
roof_plane_vertices = [
roof_vertices.group_by(Axis.Y, tol_digits=2)[-1].sort_by(Axis.X)[0],
roof_vertices.sort_by(Axis.Z)[0],
roof_vertices.group_by(Axis.Y, tol_digits=2)[0].sort_by(Axis.X)[0],
]
roof_plane = Plane(
Face(Wire.make_polygon([v.to_tuple() for v in roof_plane_vertices]))
)
# Remove the faceted smoke stack
split(bisect_by=roof_plane, keep=Keep.BOTTOM)
# Determine the position and size of the smoke stack
smoke_stack_vertices = vertices.group_by(Axis.Z, tol_digits=0)[-1]
smoke_stack_center = sum(
[Vector(v.X, v.Y, v.Z) for v in smoke_stack_vertices], Vector()
) * (1 / len(smoke_stack_vertices))
smoke_stack_radius = max(
[
(Vector(*v.to_tuple()) - smoke_stack_center).length
for v in smoke_stack_vertices
]
)
# Create the new smoke stack
with BuildSketch(Plane(smoke_stack_center)):
Circle(smoke_stack_radius)
Circle(smoke_stack_radius - 2 * MM, mode=Mode.SUBTRACT)
extrude(amount=-3 * MM)
with BuildSketch(Plane(smoke_stack_center)):
Circle(smoke_stack_radius - 0.5 * MM)
Circle(smoke_stack_radius - 2 * MM, mode=Mode.SUBTRACT)
extrude(amount=roof_plane_vertices[1].Z - smoke_stack_center.Z)
show(benchy)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#bicycle-tire .section}
[]{#examples_1.xhtml#examples-bicycle-tire}
## Bicycle Tire
{.align-center}
This example demonstrates how to model a realistic bicycle tire with a patterned tread using build123d. The key concept showcased here is the use of wrap_faces to project 2D planar geometry onto a curved 3D surface.
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
import copy
from build123d import *
from ocp_vscode import show
wheel_diameter = 740 * MM
with BuildSketch() as tire_profile:
with BuildLine() as build_profile:
l00 = Bezier((0.0, 0.0), (7.05, 0.0), (12.18, 1.54), (15.13, 4.54))
l01 = Bezier(l00 @ 1, (15.81, 5.22), (15.98, 5.44), (16.5, 6.23))
l02 = Bezier(l01 @ 1, (18.45, 9.19), (19.61, 13.84), (19.94, 20.06))
l03 = Bezier(l02 @ 1, (20.1, 23.24), (19.93, 27.48), (19.56, 29.45))
l04 = Bezier(l03 @ 1, (19.13, 31.69), (18.23, 33.67), (16.91, 35.32))
l05 = Bezier(l04 @ 1, (16.26, 36.12), (15.57, 36.77), (14.48, 37.58))
l06 = Bezier(l05 @ 1, (12.77, 38.85), (11.51, 40.28), (10.76, 41.78))
l07 = Bezier(l06 @ 1, (10.07, 43.16), (10.15, 43.81), (11.03, 43.98))
l08 = Bezier(l07 @ 1, (11.82, 44.13), (12.15, 44.55), (12.08, 45.33))
l09 = Bezier(l08 @ 1, (12.01, 46.07), (11.84, 46.43), (11.43, 46.69))
l10 = Bezier(l09 @ 1, (10.98, 46.97), (10.07, 46.7), (9.47, 46.1))
l11 = Bezier(l10 @ 1, (9.03, 45.65), (8.88, 45.31), (8.84, 44.65))
l12 = Bezier(l11 @ 1, (8.78, 43.6), (9.11, 42.26), (9.72, 41.0))
l13 = Bezier(l12 @ 1, (10.43, 39.54), (11.52, 38.2), (12.78, 37.22))
l14 = Bezier(l13 @ 1, (15.36, 35.23), (16.58, 33.76), (17.45, 31.62))
l15 = Bezier(l14 @ 1, (17.91, 30.49), (18.22, 29.27), (18.4, 27.8))
l16 = Bezier(l15 @ 1, (18.53, 26.78), (18.52, 23.69), (18.37, 22.61))
l17 = Bezier(l16 @ 1, (17.8, 18.23), (16.15, 14.7), (13.39, 11.94))
l18 = Bezier(l17 @ 1, (11.89, 10.45), (10.19, 9.31), (8.09, 8.41))
l19 = Bezier(l18 @ 1, (3.32, 6.35), (0.0, 6.64))
mirror(about=Plane.YZ)
make_face()
tire = revolve(Pos(Y=-wheel_diameter / 2) * tire_profile.face(), Axis.X)
with BuildSketch() as tread_pattern:
with Locations((1, 1)):
Trapezoid(15, 12, 60, 120, align=Align.MIN)
with Locations((1, 8)):
with GridLocations(0, 5, 1, 2):
Rectangle(50, 2, mode=Mode.SUBTRACT)
# Define the surface and path that the tread pattern will be wrapped onto
half_road_surface = Face.revolve(Pos(Y=-wheel_diameter / 2) * l00, 360, Axis.X)
tread_path = half_road_surface.edges().sort_by(Axis.X)[0]
# Wrap the planar tread pattern onto the tire's outside surface
tread_faces = half_road_surface.wrap_faces(tread_pattern.faces(), tread_path)
# Mirror the faces to the other half of the tire
tread_faces.extend([mirror(t, Plane.YZ) for t in tread_faces])
# Thicken the tread to become solid nubs
# tread_prime = [Solid.thicken(f, 3 * MM) for f in tread_faces]
tread_prime = [thicken(f, 3 * MM) for f in tread_faces]
# Copy the nubs around the whole tire
tread = [Rot(X=r) * copy.copy(t) for t in tread_prime for r in range(0, 360, 2)]
show(tire, tread)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#former-build123d-logo .section}
[]{#examples_1.xhtml#examples-build123d-logo}
## Former build123d Logo
{.align-center}
This example creates the former build123d logo (new logo was created in the end of 2023).
Using text and lines to create the first build123d logo. The builder mode example also generates the SVG file ``{=html}logo.svg``{=html}.
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch() as logo_text:
Text("123d", font_size=10, align=(Align.MIN, Align.MIN))
font_height = logo_text.vertices().sort_by(Axis.Y)[-1].Y
with BuildSketch() as build_text:
Text("build", font_size=5, align=(Align.CENTER, Align.CENTER))
build_bb = bounding_box(build_text.sketch, mode=Mode.PRIVATE)
build_vertices = build_bb.vertices().sort_by(Axis.X)
build_width = build_vertices[-1].X - build_vertices[0].X
with BuildLine() as one:
l1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))
TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)
with BuildSketch() as two:
with Locations((font_height * 0.35, 0)):
Text("2", font_size=10, align=(Align.MIN, Align.MIN))
with BuildPart() as three_d:
with BuildSketch(Plane((font_height * 1.1, 0))):
Text("3d", font_size=10, align=(Align.MIN, Align.MIN))
extrude(amount=font_height * 0.3)
logo_width = three_d.vertices().sort_by(Axis.X)[-1].X
with BuildLine() as arrow_left:
t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))
mirror(t1, Plane.XZ)
ext_line_length = font_height * 0.5
dim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2
with BuildLine() as extension_lines:
l1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))
l2 = Line(
(logo_width, -font_height * 0.1),
(logo_width, -ext_line_length - font_height * 0.1),
)
with Locations(l1 @ 0.5):
add(arrow_left.line)
with Locations(l2 @ 0.5):
add(arrow_left.line, rotation=180.0)
Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))
Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))
# Precisely center the build Faces
with BuildSketch() as build:
with Locations(
(l1 @ 0.5 + l2 @ 0.5) / 2
- Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0)
):
add(build_text.sketch)
if True:
logo = Compound(
children=[
one.line,
two.sketch,
three_d.part,
extension_lines.line,
build.sketch,
]
)
# logo.export_step("logo.step")
def add_svg_shape(svg: ExportSVG, shape: Shape, color: tuple[float, float, float]):
global counter
try:
counter += 1
except:
counter = 1
visible, _hidden = shape.project_to_viewport(
(-5, 1, 10), viewport_up=(0, 1, 0), look_at=(0, 0, 0)
)
if color is not None:
svg.add_layer(str(counter), fill_color=color, line_weight=1)
else:
svg.add_layer(str(counter), line_weight=1)
svg.add_shape(visible, layer=str(counter))
svg = ExportSVG(scale=20)
add_svg_shape(svg, logo, None)
# add_svg_shape(svg, Compound(children=[one.line, extension_lines.line]), None)
# add_svg_shape(svg, Compound(children=[two.sketch, build.sketch]), (170, 204, 255))
# add_svg_shape(svg, three_d.part, (85, 153, 255))
svg.write("logo.svg")
show_object(one, name="one")
show_object(two, name="two")
show_object(three_d, name="three_d")
show_object(extension_lines, name="extension_lines")
show_object(build, name="build")
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
logo_text = Text("123d", font_size=10, align=Align.MIN)
font_height = logo_text.vertices().sort_by(Axis.Y).last.Y
build_text = Text("build", font_size=5, align=Align.CENTER)
build_bb = build_text.bounding_box()
build_width = build_bb.max.X - build_bb.min.X
l1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))
one = l1 + TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)
two = Pos(font_height * 0.35, 0) * Text("2", font_size=10, align=Align.MIN)
three_d = Text("3d", font_size=10, align=Align.MIN)
three_d = Pos(font_height * 1.1, 0) * extrude(three_d, amount=font_height * 0.3)
logo_width = three_d.vertices().sort_by(Axis.X).last.X
t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))
arrow_left = t1 + mirror(t1, Plane.XZ)
ext_line_length = font_height * 0.5
dim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2
l1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))
l2 = Line(
(logo_width, -font_height * 0.1),
(logo_width, -ext_line_length - font_height * 0.1),
)
extension_lines = Curve() + (l1 + l2)
extension_lines += Pos(*(l1 @ 0.5)) * arrow_left
extension_lines += (Pos(*(l2 @ 0.5)) * Rot(Z=180)) * arrow_left
extension_lines += Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))
extension_lines += Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))
# Precisely center the build Faces
p1 = Pos((l1 @ 0.5 + l2 @ 0.5) / 2 - Vector((build_bb.max.X + build_bb.min.X) / 2, 0))
build = p1 * build_text
cmpd = Compound([three_d, two, one, build, extension_lines])
show_object(cmpd, name="compound")
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#cast-bearing-unit .section}
[]{#examples_1.xhtml#examples-cast-bearing-unit}
## Cast Bearing Unit
{.align-center}
This example demonstrates the creation of a castable flanged bearing housing using the ``{=html}draft``{=html} operation to add appropriate draft angles for mold release.
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
A, A1, Db2, H, J = 26, 11, 57, 98.5, 76.5
with BuildPart() as oval_flanged_bearing_unit:
with BuildSketch() as plan:
housing = Circle(Db2 / 2)
with GridLocations(J, 0, 2, 1) as bolt_centers:
Circle((H - J) / 2)
make_hull()
extrude(amount=A1)
extrude(housing, amount=A)
drafted_faces = oval_flanged_bearing_unit.faces().filter_by(Axis.Z, reverse=True)
draft(drafted_faces, Plane.XY, 4)
fillet(oval_flanged_bearing_unit.edges(), 1)
with Locations(oval_flanged_bearing_unit.faces().sort_by(Axis.Z)[-1]):
CounterBoreHole(14 / 2, 47 / 2, 14)
with Locations(*bolt_centers):
Hole(5)
oval_flanged_bearing_unit.part.color = Color(0x4C6377)
show(oval_flanged_bearing_unit)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#canadian-flag-blowing-in-the-wind .section}
[]{#examples_1.xhtml#examples-canadian-flag}
## Canadian Flag Blowing in The Wind
{.align-center}
A Canadian Flag blowing in the wind created by projecting planar faces onto a non-planar face (the_wind).
This example also demonstrates building complex lines that snap to existing features.
```{=html}
```
```{=html}
```
[More Images]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
{.align-center} {.align-center}
:::
```{=html}
```
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
def surface(amplitude, u, v):
"""Calculate the surface displacement of the flag at a given position"""
return v * amplitude / 20 * cos(3.5 * pi * u) + amplitude / 10 * v * sin(
1.1 * pi * v
)
# Note that the surface to project on must be a little larger than the faces
# being projected onto it to create valid projected faces
the_wind = Face.make_surface_from_array_of_points(
[
[
Vector(
width * (v * 1.1 / 40 - 0.05),
height * (u * 1.2 / 40 - 0.1),
height * surface(wave_amplitude, u / 40, v / 40) / 2,
)
for u in range(41)
]
for v in range(41)
]
)
with BuildSketch(Plane.XY.offset(10)) as west_field_builder:
Rectangle(width / 4, height, align=(Align.MIN, Align.MIN))
west_field_planar = west_field_builder.sketch.faces()[0]
east_field_planar = west_field_planar.mirror(Plane.YZ.offset(width / 2))
with BuildSketch(Plane((width / 2, 0, 10))) as center_field_builder:
Rectangle(width / 2, height, align=(Align.CENTER, Align.MIN))
with BuildSketch(
Plane((width / 2, 0, 10)), mode=Mode.SUBTRACT
) as maple_leaf_builder:
with BuildLine() as outline:
l1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569))
l2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125))
RadiusArc(l1 @ 1, l2 @ 0, 0.0271)
l3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071))
TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1)
l4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188))
SagittaArc(l3 @ 1, l4 @ 0, 0.003)
l5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835))
ThreePointArc(
l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0
)
l6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752))
Spline(
l5 @ 1,
l6 @ 0,
tangents=(l5 % 1, l6 % 0),
tangent_scalars=(2, 2),
)
l7 = Line((0.0692, 0.7808), (0.0000, 0.9167))
TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1)
mirror(outline.edges(), Plane.YZ)
make_face()
scale(by=height)
maple_leaf_planar = maple_leaf_builder.sketch.faces()[0]
center_field_planar = center_field_builder.sketch.faces()[0]
west_field = west_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]
west_field.color = Color("red")
east_field = east_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]
east_field.color = Color("red")
center_field = center_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]
center_field.color = Color("white")
maple_leaf = maple_leaf_planar.project_to_shape(the_wind, (0, 0, -1))[0]
maple_leaf.color = Color("red")
canadian_flag = Compound(children=[west_field, east_field, center_field, maple_leaf])
show(Rot(90, 0, 0) * canadian_flag)
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
def surface(amplitude, u, v):
"""Calculate the surface displacement of the flag at a given position"""
return v * amplitude / 20 * cos(3.5 * pi * u) + amplitude / 10 * v * sin(
1.1 * pi * v
)
# Note that the surface to project on must be a little larger than the faces
# being projected onto it to create valid projected faces
the_wind = Face.make_surface_from_array_of_points(
[
[
Vector(
width * (v * 1.1 / 40 - 0.05),
height * (u * 1.2 / 40 - 0.1),
height * surface(wave_amplitude, u / 40, v / 40) / 2,
)
for u in range(41)
]
for v in range(41)
]
)
field_planar = Plane.XY.offset(10) * Rectangle(width / 4, height, align=Align.MIN)
west_field_planar = field_planar.faces()[0]
east_field_planar = mirror(west_field_planar, Plane.YZ.offset(width / 2))
l1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569))
l2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125))
r1 = RadiusArc(l1 @ 1, l2 @ 0, 0.0271)
l3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071))
r2 = TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1)
l4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188))
r3 = SagittaArc(l3 @ 1, l4 @ 0, 0.003)
l5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835))
r4 = ThreePointArc(l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0)
l6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752))
s = Spline(
l5 @ 1,
l6 @ 0,
tangents=(l5 % 1, l6 % 0),
tangent_scalars=(2, 2),
)
l7 = Line((0.0692, 0.7808), (0.0000, 0.9167))
r5 = TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1)
outline = l1 + [l2, r1, l3, r2, l4, r3, l5, r4, l6, s, l7, r5]
outline += mirror(outline, Plane.YZ)
maple_leaf_planar = make_face(outline)
center_field_planar = (
Rectangle(1, 1, align=(Align.CENTER, Align.MIN)) - maple_leaf_planar
)
def scale_move(obj):
return Plane((width / 2, 0, 10)) * scale(obj, height)
def project(obj):
return obj.faces()[0].project_to_shape(the_wind, (0, 0, -1))[0]
maple_leaf_planar = scale_move(maple_leaf_planar)
center_field_planar = scale_move(center_field_planar)
west_field = project(west_field_planar)
west_field.color = Color("red")
east_field = project(east_field_planar)
east_field.color = Color("red")
center_field = project(center_field_planar)
center_field.color = Color("white")
maple_leaf = project(maple_leaf_planar)
maple_leaf.color = Color("red")
canadian_flag = Compound(children=[west_field, east_field, center_field, maple_leaf])
show(Rot(90, 0, 0) * canadian_flag)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#circuit-board-with-holes .section}
[]{#examples_1.xhtml#examples-circuit-board}
## Circuit Board With Holes
{.align-center}
This example demonstrates placing holes around a part.
- Builder mode uses ``{=html}Locations``{=html} context to place the positions.
- Algebra mode uses ``{=html}product``{=html} and ``{=html}range``{=html} to calculate the positions.
```{=html}
```
```{=html}
```
[More Images]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
{.align-center}
:::
```{=html}
```
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as pcb:
with BuildSketch():
Rectangle(pcb_length, pcb_width)
for i in range(65 // 5):
x = i * 5 - 30
with Locations((x, -15), (x, -10), (x, 10), (x, 15)):
Circle(1, mode=Mode.SUBTRACT)
for i in range(30 // 5 - 1):
y = i * 5 - 10
with Locations((30, y), (35, y)):
Circle(1, mode=Mode.SUBTRACT)
with GridLocations(60, 20, 2, 2):
Circle(2, mode=Mode.SUBTRACT)
extrude(amount=pcb_height)
show_object(pcb.part.wrapped)
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
x_coords = product(range(65 // 5), (-15, -10, 10, 15))
y_coords = product((30, 35), range(30 // 5 - 1))
pcb = Rectangle(pcb_length, pcb_width)
pcb -= [Pos(i * 5 - 30, y) * Circle(1) for i, y in x_coords]
pcb -= [Pos(x, i * 5 - 10) * Circle(1) for x, i in y_coords]
pcb -= [loc * Circle(2) for loc in GridLocations(60, 20, 2, 2)]
pcb = extrude(pcb, pcb_height)
show(pcb)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#clock-face .section}
[]{#examples_1.xhtml#id1}
## Clock Face
{.align-center}
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
clock_radius = 10
with BuildSketch() as minute_indicator:
with BuildLine() as outline:
l1 = CenterArc((0, 0), clock_radius * 0.975, 0.75, 4.5)
l2 = CenterArc((0, 0), clock_radius * 0.925, 0.75, 4.5)
Line(l1 @ 0, l2 @ 0)
Line(l1 @ 1, l2 @ 1)
make_face()
fillet(minute_indicator.vertices(), radius=clock_radius * 0.01)
with BuildSketch() as clock_face:
Circle(clock_radius)
with PolarLocations(0, 60):
add(minute_indicator.sketch, mode=Mode.SUBTRACT)
with PolarLocations(clock_radius * 0.875, 12):
SlotOverall(clock_radius * 0.05, clock_radius * 0.025, mode=Mode.SUBTRACT)
for hour in range(1, 13):
with PolarLocations(clock_radius * 0.75, 1, -hour * 30 + 90, 360, rotate=False):
Text(
str(hour),
font_size=clock_radius * 0.175,
font_style=FontStyle.BOLD,
mode=Mode.SUBTRACT,
)
show(clock_face)
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
clock_radius = 10
l1 = CenterArc((0, 0), clock_radius * 0.975, 0.75, 4.5)
l2 = CenterArc((0, 0), clock_radius * 0.925, 0.75, 4.5)
l3 = Line(l1 @ 0, l2 @ 0)
l4 = Line(l1 @ 1, l2 @ 1)
minute_indicator = make_face([l1, l3, l2, l4])
minute_indicator = fillet(minute_indicator.vertices(), radius=clock_radius * 0.01)
clock_face = Circle(clock_radius)
clock_face -= PolarLocations(0, 60) * minute_indicator
clock_face -= PolarLocations(clock_radius * 0.875, 12) * SlotOverall(
clock_radius * 0.05, clock_radius * 0.025
)
clock_face -= [
loc
* Text(
str(hour + 1),
font_size=clock_radius * 0.175,
font_style=FontStyle.BOLD,
align=Align.CENTER,
)
for hour, loc in enumerate(
PolarLocations(clock_radius * 0.75, 12, 60, -360, rotate=False)
)
]
show(clock_face)
:::
:::
:::
```{=html}
```
The Python code utilizes the build123d library to create a 3D model of a clock face. It defines a minute indicator with arcs and lines, applying fillets, and then integrates it into the clock face sketch. The clock face includes a circular outline, hour labels, and slots at specified positions. The resulting 3D model represents a detailed and visually appealing clock design.
[`PolarLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.PolarLocations "build_common.PolarLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} are used to position features on the clock face.
:::
::: {#examples_1.xhtml#fast-grid-holes .section}
[]{#examples_1.xhtml#id2}
## Fast Grid Holes
{.align-center}
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
import timeit
from build123d import *
from ocp_vscode import show
start_time = timeit.default_timer()
# Calculate the locations of 625 holes
major_r = 10
hole_locs = HexLocations(major_r, 25, 25)
# Create wires for both the perimeter and all the holes
face_perimeter = Rectangle(500, 600).wire()
hex_hole = RegularPolygon(major_r - 1, 6, major_radius=True).wire()
holes = hole_locs * hex_hole
# Create a new Face from the perimeter and hole wires
grid_pattern = Face(face_perimeter, holes)
# Extrude to a 3D part
grid = extrude(grid_pattern, 1)
print(f"Time: {timeit.default_timer() - start_time:0.3f}s")
show(grid)
:::
:::
:::
```{=html}
```
This example demonstrates an efficient approach to creating a large number of holes (625 in this case) in a planar part using build123d.
Instead of modeling and subtracting 3D solids for each hole---which is computationally expensive---this method constructs a 2D Face from an outer perimeter wire and a list of hole wires. The entire face is then extruded in a single operation to form the final 3D object. This approach significantly reduces modeling time and complexity.
The hexagonal hole pattern is generated using HexLocations, and each location is populated with a hexagonal wire. These wires are passed directly to the Face constructor as holes. On a typical Linux laptop, this script completes in approximately 1.02 seconds, compared to substantially longer runtimes for boolean subtraction of individual holes in 3D.
:::
::: {#examples_1.xhtml#handle .section}
[]{#examples_1.xhtml#id3}
## Handle
{.align-center}
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show_object
segment_count = 6
with BuildPart() as handle:
# Create a path for the sweep along the handle - added to pending_edges
with BuildLine() as handle_center_line:
Spline(
(-10, 0, 0),
(0, 0, 5),
(10, 0, 0),
tangents=((0, 0, 1), (0, 0, -1)),
tangent_scalars=(1.5, 1.5),
)
# Create the cross sections - added to pending_faces
for i in range(segment_count + 1):
with BuildSketch(handle_center_line.line ^ (i / segment_count)) as section:
if i % segment_count == 0:
Circle(1)
else:
Rectangle(1.25, 3)
fillet(section.vertices(), radius=0.2)
# Record the sections for display
sections = handle.pending_faces
# Create the handle by sweeping along the path
sweep(multisection=True)
assert abs(handle.part.volume - 94.77361455046953) < 1e-3
show_object(handle_center_line.line, name="handle_center_line")
for i, section in enumerate(sections):
show_object(section, name="section" + str(i))
show_object(handle.part, name="handle", options=dict(alpha=0.6))
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show_object
segment_count = 6
# Create a path for the sweep along the handle - added to pending_edges
handle_center_line = Spline(
(-10, 0, 0),
(0, 0, 5),
(10, 0, 0),
tangents=((0, 0, 1), (0, 0, -1)),
tangent_scalars=(1.5, 1.5),
)
# Create the cross sections - added to pending_faces
sections = Sketch()
for i in range(segment_count + 1):
location = handle_center_line ^ (i / segment_count)
if i % segment_count == 0:
circle = location * Circle(1)
else:
circle = location * Rectangle(1.25, 3)
circle = fillet(circle.vertices(), radius=0.2)
sections += circle
# Create the handle by sweeping along the path
handle = sweep(sections, path=handle_center_line, multisection=True)
show_object(handle_center_line, name="handle_path")
for i, circle in enumerate(sections):
show_object(circle, name="section" + str(i))
show_object(handle, name="handle", options=dict(alpha=0.6))
:::
:::
:::
```{=html}
```
This example demonstrates multisection sweep creating a drawer handle.
:::
::: {#examples_1.xhtml#heat-exchanger .section}
[]{#examples_1.xhtml#id4}
## Heat Exchanger
{.align-center}
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
exchanger_diameter = 10 * CM
exchanger_length = 30 * CM
plate_thickness = 5 * MM
# 149 tubes
tube_diameter = 5 * MM
tube_spacing = 2 * MM
tube_wall_thickness = 0.5 * MM
tube_extension = 3 * MM
bundle_diameter = exchanger_diameter - 2 * tube_diameter
fillet_radius = tube_spacing / 3
assert tube_extension > fillet_radius
# Build the heat exchanger
with BuildPart() as heat_exchanger:
# Generate list of tube locations
tube_locations = [
l
for l in HexLocations(
radius=(tube_diameter + tube_spacing) / 2,
x_count=exchanger_diameter // tube_diameter,
y_count=exchanger_diameter // tube_diameter,
)
if l.position.length < bundle_diameter / 2
]
tube_count = len(tube_locations)
with BuildSketch() as tube_plan:
with Locations(*tube_locations):
Circle(radius=tube_diameter / 2)
Circle(radius=tube_diameter / 2 - tube_wall_thickness, mode=Mode.SUBTRACT)
extrude(amount=exchanger_length / 2)
with BuildSketch(
Plane(
origin=(0, 0, exchanger_length / 2 - tube_extension - plate_thickness),
z_dir=(0, 0, 1),
)
) as plate_plan:
Circle(radius=exchanger_diameter / 2)
with Locations(*tube_locations):
Circle(radius=tube_diameter / 2 - tube_wall_thickness, mode=Mode.SUBTRACT)
extrude(amount=plate_thickness)
half_volume_before_fillet = heat_exchanger.part.volume
# Simulate welded tubes by adding a fillet to the outside radius of the tubes
fillet(
heat_exchanger.edges()
.filter_by(GeomType.CIRCLE)
.sort_by(SortBy.RADIUS)
.sort_by(Axis.Z, reverse=True)[2 * tube_count : 3 * tube_count],
radius=fillet_radius,
)
half_volume_after_fillet = heat_exchanger.part.volume
mirror(about=Plane.XY)
fillet_volume = 2 * (half_volume_after_fillet - half_volume_before_fillet)
assert abs(fillet_volume - 469.88331045553787) < 1e-3
show(heat_exchanger)
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
exchanger_diameter = 10 * CM
exchanger_length = 30 * CM
plate_thickness = 5 * MM
# 149 tubes
tube_diameter = 5 * MM
tube_spacing = 2 * MM
tube_wall_thickness = 0.5 * MM
tube_extension = 3 * MM
bundle_diameter = exchanger_diameter - 2 * tube_diameter
fillet_radius = tube_spacing / 3
assert tube_extension > fillet_radius
# Build the heat exchanger
tube_locations = [
l
for l in HexLocations(
radius=(tube_diameter + tube_spacing) / 2,
x_count=exchanger_diameter // tube_diameter,
y_count=exchanger_diameter // tube_diameter,
)
if l.position.length < bundle_diameter / 2
]
ring = Circle(tube_diameter / 2) - Circle(tube_diameter / 2 - tube_wall_thickness)
tube_plan = Sketch() + tube_locations * ring
heat_exchanger = extrude(tube_plan, exchanger_length / 2)
plate_plane = Plane(
origin=(0, 0, exchanger_length / 2 - tube_extension - plate_thickness),
z_dir=(0, 0, 1),
)
plate = Circle(radius=exchanger_diameter / 2) - tube_locations * Circle(
radius=tube_diameter / 2 - tube_wall_thickness
)
heat_exchanger += extrude(plate_plane * plate, plate_thickness)
edges = (
heat_exchanger.edges()
.filter_by(GeomType.CIRCLE)
.group_by(SortBy.RADIUS)[1]
.group_by()[2]
)
half_volume_before_fillet = heat_exchanger.volume
heat_exchanger = fillet(edges, radius=fillet_radius)
half_volume_after_fillet = heat_exchanger.volume
heat_exchanger += mirror(heat_exchanger, Plane.XY)
fillet_volume = 2 * (half_volume_after_fillet - half_volume_before_fillet)
assert abs(fillet_volume - 469.88331045553787) < 1e-3
show(heat_exchanger)
:::
:::
:::
```{=html}
```
This example creates a model of a parametric heat exchanger core. The positions of the tubes are defined with [`HexLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.HexLocations "build_common.HexLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} and further limited to fit within the circular end caps. The ends of the tubes are filleted to the end plates to simulate welding.
:::
::: {#examples_1.xhtml#key-cap .section}
[]{#examples_1.xhtml#id5}
## Key Cap
{.align-center}
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
with BuildPart() as key_cap:
# Start with the plan of the key cap and extrude it
with BuildSketch() as plan:
Rectangle(18 * MM, 18 * MM)
extrude(amount=10 * MM, taper=15)
# Create a dished top
with Locations((0, -3 * MM, 47 * MM)):
Sphere(40 * MM, mode=Mode.SUBTRACT, rotation=(90, 0, 0))
# Fillet all the edges except the bottom
fillet(
key_cap.edges().filter_by_position(Axis.Z, 0, 30 * MM, inclusive=(False, True)),
radius=1 * MM,
)
# Hollow out the key by subtracting a scaled version
scale(by=(0.925, 0.925, 0.85), mode=Mode.SUBTRACT)
# Add supporting ribs while leaving room for switch activation
with BuildSketch(Plane(origin=(0, 0, 4 * MM))):
Rectangle(15 * MM, 0.5 * MM)
Rectangle(0.5 * MM, 15 * MM)
Circle(radius=5.5 * MM / 2)
# Extrude the mount and ribs to the key cap underside
extrude(until=Until.NEXT)
# Find the face on the bottom of the ribs to build onto
rib_bottom = key_cap.faces().filter_by_position(Axis.Z, 4 * MM, 4 * MM)[0]
# Add the switch socket
with BuildSketch(rib_bottom) as cruciform:
Circle(radius=5.5 * MM / 2)
Rectangle(4.1 * MM, 1.17 * MM, mode=Mode.SUBTRACT)
Rectangle(1.17 * MM, 4.1 * MM, mode=Mode.SUBTRACT)
extrude(amount=3.5 * MM, mode=Mode.ADD)
assert abs(key_cap.part.volume - 644.8900473617498) < 1e-3
show(key_cap, alphas=[0.3])
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
# Taper Extrude and Extrude to "next" while creating a Cherry MX key cap
# See: https://www.cherrymx.de/en/dev.html
plan = Rectangle(18 * MM, 18 * MM)
key_cap = extrude(plan, amount=10 * MM, taper=15)
# Create a dished top
key_cap -= Location((0, -3 * MM, 47 * MM), (90, 0, 0)) * Sphere(40 * MM)
# Fillet all the edges except the bottom
key_cap = fillet(
key_cap.edges().filter_by_position(Axis.Z, 0, 30 * MM, inclusive=(False, True)),
radius=1 * MM,
)
# Hollow out the key by subtracting a scaled version
key_cap -= scale(key_cap, (0.925, 0.925, 0.85))
# Add supporting ribs while leaving room for switch activation
ribs = Rectangle(17.5 * MM, 0.5 * MM)
ribs += Rectangle(0.5 * MM, 17.5 * MM)
ribs += Circle(radius=5.51 * MM / 2)
# Extrude the mount and ribs to the key cap underside
key_cap += extrude(Pos(0, 0, 4 * MM) * ribs, until=Until.NEXT, target=key_cap)
# Find the face on the bottom of the ribs to build onto
rib_bottom = key_cap.faces().filter_by_position(Axis.Z, 4 * MM, 4 * MM)[0]
# Add the switch socket
socket = Circle(radius=5.5 * MM / 2)
socket -= Rectangle(4.1 * MM, 1.17 * MM)
socket -= Rectangle(1.17 * MM, 4.1 * MM)
key_cap += extrude(Plane(rib_bottom) * socket, amount=3.5 * MM)
show(key_cap, alphas=[0.3])
:::
:::
:::
```{=html}
```
This example demonstrates the design of a Cherry MX key cap by using extrude with a taper and extrude until next.
:::
::: {#examples_1.xhtml#maker-coin .section}
[]{#examples_1.xhtml#id6}
## Maker Coin
{.align-center}
This example creates the maker coin as defined by Angus on the Maker's Muse YouTube channel. There are two key features:
1. the use of [`DoubleTangentArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.DoubleTangentArc "objects_curve.DoubleTangentArc"){.hxr-hoverxref .hxr-tooltip .reference .internal} to create a smooth transition from the central dish to the outside arc, and
2. embossing the text into the top of the coin not just as a simple extrude but from a projection which results in text with even depth.
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
# Coin Parameters
diameter, thickness = 50 * MM, 10 * MM
with BuildPart() as maker_coin:
# On XZ plane draw the profile of half the coin
with BuildSketch(Plane.XZ) as profile:
with BuildLine() as outline:
l1 = Polyline((0, thickness * 0.6), (0, 0), ((diameter - thickness) / 2, 0))
l2 = JernArc(
start=l1 @ 1, tangent=l1 % 1, radius=thickness / 2, arc_size=300
) # extend the arc beyond the intersection but not closed
l3 = DoubleTangentArc(l1 @ 0, tangent=(1, 0), other=l2)
make_face() # make it a 2D shape
revolve() # revolve 360°
# Pattern the detents around the coin
with BuildSketch() as detents:
with PolarLocations(radius=(diameter + 5) / 2, count=8):
Circle(thickness * 1.4 / 2)
extrude(amount=thickness, mode=Mode.SUBTRACT) # cut away the detents
fillet(maker_coin.edges(Select.NEW), 2) # fillet the cut edges
# Add an embossed label
with BuildSketch(Plane.XY.offset(thickness)) as label: # above coin
Text("OS", font_size=15)
project() # label on top of coin
extrude(amount=-thickness / 5, mode=Mode.SUBTRACT) # emboss label
show(maker_coin)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#multi-sketch-loft .section}
[]{#examples_1.xhtml#id7}
## Multi-Sketch Loft
{.align-center}
This example demonstrates lofting a set of sketches, selecting the top and bottom by type, and shelling.
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from math import pi, sin
from build123d import *
from ocp_vscode import show
with BuildPart() as art:
slice_count = 10
for i in range(slice_count + 1):
with BuildSketch(Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))) as slice:
Circle(10 * sin(i * pi / slice_count) + 5)
loft()
top_bottom = art.faces().filter_by(GeomType.PLANE)
offset(openings=top_bottom, amount=0.5)
want = 1306.3405290344635
got = art.part.volume
delta = abs(got - want)
tolerance = want * 1e-5
assert delta < tolerance, f"{delta=} is greater than {tolerance=}; {got=}, {want=}"
show(art, names=["art"])
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from math import pi, sin
from build123d import *
from ocp_vscode import show
slice_count = 10
art = Sketch()
for i in range(slice_count + 1):
plane = Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))
art += plane * Circle(10 * sin(i * pi / slice_count) + 5)
art = loft(art)
top_bottom = art.faces().filter_by(GeomType.PLANE)
art = offset(art, openings=top_bottom, amount=0.5)
show(art, names=["art"])
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#peg-board-hook .section}
[]{#examples_1.xhtml#id8}
## Peg Board Hook
{.align-center}
This script creates a a J-shaped pegboard hook. These hooks are commonly used for organizing tools in garages, workshops, or other spaces where tools and equipment need to be stored neatly and accessibly. The hook is created by defining a complex path and then sweeping it to define the hook. The sides of the hook are flattened to aid 3D printing.
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
pegd = 6.35 + 0.1 # mm ~0.25inch
c2c = 25.4 # mm 1.0inch
arcd = 7.2
both = 10
topx = 6
midx = 8
maind = 0.82 * pegd
midd = 1.0 * pegd
hookd = 23
hookx = 10
splitz = maind / 2 - 0.1
topangs = 70
with BuildPart() as mainp:
with BuildLine(mode=Mode.PRIVATE) as sprof:
l1 = Line((-both, 0), (c2c - arcd / 2 - 0.5, 0))
l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=arcd / 2, arc_size=topangs)
l3 = PolarLine(
start=l2 @ 1,
length=topx,
direction=l2 % 1,
)
l4 = JernArc(start=l3 @ 1, tangent=l3 % 1, radius=arcd / 2, arc_size=-topangs)
l5 = PolarLine(
start=l4 @ 1,
length=topx,
direction=l4 % 1,
)
l6 = JernArc(
start=l1 @ 0, tangent=(l1 % 0).reverse(), radius=hookd / 2, arc_size=170
)
l7 = PolarLine(
start=l6 @ 1,
length=hookx,
direction=l6 % 1,
)
with BuildSketch(Plane.YZ):
Circle(radius=maind / 2)
sweep(path=sprof.wires()[0])
with BuildLine(mode=Mode.PRIVATE) as stub:
l7 = Line((0, 0), (0, midx + maind / 2))
with BuildSketch(Plane.XZ):
Circle(radius=midd / 2)
sweep(path=stub.wires()[0])
# splits help keep the object 3d printable by reducing overhang
split(bisect_by=Plane(origin=(0, 0, -splitz)))
split(bisect_by=Plane(origin=(0, 0, splitz)), keep=Keep.BOTTOM)
show(mainp)
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
pegd = 6.35 + 0.1 # mm ~0.25inch
c2c = 25.4 # mm 1.0inch
arcd = 7.2
both = 10
topx = 6
midx = 8
maind = 0.82 * pegd
midd = 1.0 * pegd
hookd = 23
hookx = 10
splitz = maind / 2 - 0.1
topangs = 70
l1 = Line((-both, 0), (c2c - arcd / 2 - 0.5, 0))
l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=arcd / 2, arc_size=topangs)
l3 = PolarLine(
start=l2 @ 1,
length=topx,
direction=l2 % 1,
)
l4 = JernArc(start=l3 @ 1, tangent=l3 % 1, radius=arcd / 2, arc_size=-topangs)
l5 = PolarLine(
start=l4 @ 1,
length=topx,
direction=l4 % 1,
)
l6 = JernArc(start=l1 @ 0, tangent=(l1 % 0).reverse(), radius=hookd / 2, arc_size=170)
l7 = PolarLine(
start=l6 @ 1,
length=hookx,
direction=l6 % 1,
)
sprof = Curve() + (l1, l2, l3, l4, l5, l6, l7)
wire = Wire(sprof.edges()) # TODO sprof.wires() fails
mainp = sweep(Plane.YZ * Circle(radius=maind / 2), path=wire)
stub = Line((0, 0), (0, midx + maind / 2))
mainp += sweep(Plane.XZ * Circle(radius=midd / 2), path=stub)
# splits help keep the object 3d printable by reducing overhang
mainp = split(mainp, Plane(origin=(0, 0, -splitz)))
mainp = split(mainp, Plane(origin=(0, 0, splitz)), keep=Keep.BOTTOM)
show(mainp)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#platonic-solids .section}
[]{#examples_1.xhtml#id9}
## Platonic Solids
{.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.
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
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(Wire.make_polygon(corner_vertices)))
# Create the solid from the Faces
platonic_solid = Solid(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)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#playing-cards .section}
[]{#examples_1.xhtml#id10}
## Playing Cards
{.align-center}
This example creates a customs Sketch objects: Club, Spade, Heart, Diamond, and PlayingCard in addition to a two part playing card box which has suit cutouts in the lid. The four suits are created with Bézier curves that were imported as code from an SVG file and modified to the code found here.
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from typing import Literal
from build123d import *
from ocp_vscode import show_object
# [Club]
class Club(BaseSketchObject):
def __init__(
self,
height: float,
rotation: float = 0,
align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
):
with BuildSketch() as club:
with BuildLine():
l0 = Line((0, -188), (76, -188))
b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))
b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))
b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))
b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))
mirror(about=Plane.YZ)
make_face()
scale(by=height / club.sketch.bounding_box().size.Y)
super().__init__(obj=club.sketch, rotation=rotation, align=align, mode=mode)
# [Club]
class Spade(BaseSketchObject):
def __init__(
self,
height: float,
rotation: float = 0,
align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
):
with BuildSketch() as spade:
with BuildLine():
b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61))
b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105))
b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198))
l0 = Line(b2 @ 1, (0, -198))
mirror(about=Plane.YZ)
make_face()
scale(by=height / spade.sketch.bounding_box().size.Y)
super().__init__(obj=spade.sketch, rotation=rotation, align=align, mode=mode)
class Heart(BaseSketchObject):
def __init__(
self,
height: float,
rotation: float = 0,
align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
):
with BuildSketch() as heart:
with BuildLine():
b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198))
b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167))
b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31))
b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95))
b5 = Bezier(b4 @ 1, (40, -128), (0, -198))
mirror(about=Plane.YZ)
make_face()
scale(by=height / heart.sketch.bounding_box().size.Y)
super().__init__(obj=heart.sketch, rotation=rotation, align=align, mode=mode)
class Diamond(BaseSketchObject):
def __init__(
self,
height: float,
rotation: float = 0,
align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
):
with BuildSketch() as diamond:
with BuildLine():
Bezier((135, 0), (94, 69), (47, 134), (0, 198))
mirror(about=Plane.XZ)
mirror(about=Plane.YZ)
make_face()
scale(by=height / diamond.sketch.bounding_box().size.Y)
super().__init__(obj=diamond.sketch, rotation=rotation, align=align, mode=mode)
card_width = 2.5 * IN
card_length = 3.5 * IN
deck = 0.5 * IN
wall = 4 * MM
gap = 0.5 * MM
with BuildPart() as box_builder:
with BuildSketch() as plan:
Rectangle(card_width + 2 * wall, card_length + 2 * wall)
fillet(plan.vertices(), radius=card_width / 15)
extrude(amount=wall / 2)
with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as walls:
add(plan.sketch)
offset(plan.sketch, amount=-wall, mode=Mode.SUBTRACT)
extrude(amount=deck / 2)
with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as inset_walls:
offset(plan.sketch, amount=-(wall + gap) / 2, mode=Mode.ADD)
offset(plan.sketch, amount=-wall, mode=Mode.SUBTRACT)
extrude(amount=deck / 2)
with BuildPart() as lid_builder:
with BuildSketch() as outset_walls:
add(plan.sketch)
offset(plan.sketch, amount=-(wall - gap) / 2, mode=Mode.SUBTRACT)
extrude(amount=deck / 2)
with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]) as top:
add(plan.sketch)
extrude(amount=wall / 2)
with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]):
holes = GridLocations(
3 * card_width / 5, 3 * card_length / 5, 2, 2
).local_locations
for i, hole in enumerate(holes):
with Locations(hole) as hole_loc:
if i == 0:
Heart(card_length / 5)
elif i == 1:
Diamond(card_length / 5)
elif i == 2:
Spade(card_length / 5)
elif i == 3:
Club(card_length / 5)
extrude(amount=-wall, mode=Mode.SUBTRACT)
box = Compound(
[box_builder.part, lid_builder.part.moved(Location((0, 0, (wall + deck) / 2)))]
)
visible, hidden = box.project_to_viewport((70, -50, 120))
max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)
exporter = ExportSVG(scale=100 / max_dimension)
exporter.add_layer("Visible")
exporter.add_layer("Hidden", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)
exporter.add_shape(visible, layer="Visible")
exporter.add_shape(hidden, layer="Hidden")
# exporter.write(f"assets/card_box.svg")
class PlayingCard(BaseSketchObject):
"""PlayingCard
A standard playing card modelled as a Face.
Args:
rank (Literal['A', '2' .. '10', 'J', 'Q', 'K']): card rank
suit (Literal['Clubs', 'Spades', 'Hearts', 'Diamonds']): card suit
"""
width = 2.5 * IN
height = 3.5 * IN
suits = {"Clubs": Club, "Spades": Spade, "Hearts": Heart, "Diamonds": Diamond}
ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
def __init__(
self,
rank: Literal["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"],
suit: Literal["Clubs", "Spades", "Hearts", "Diamonds"],
rotation: float = 0,
align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
):
with BuildSketch() as playing_card:
Rectangle(
PlayingCard.width, PlayingCard.height, align=(Align.MIN, Align.MIN)
)
fillet(playing_card.vertices(), radius=PlayingCard.width / 15)
with Locations(
(
PlayingCard.width / 7,
8 * PlayingCard.height / 9,
)
):
Text(
txt=rank,
font_size=PlayingCard.width / 7,
mode=Mode.SUBTRACT,
)
with Locations(
(
PlayingCard.width / 7,
7 * PlayingCard.height / 9,
)
):
PlayingCard.suits[suit](
height=PlayingCard.width / 12, mode=Mode.SUBTRACT
)
with Locations(
(
6 * PlayingCard.width / 7,
1 * PlayingCard.height / 9,
)
):
Text(
txt=rank,
font_size=PlayingCard.width / 7,
rotation=180,
mode=Mode.SUBTRACT,
)
with Locations(
(
6 * PlayingCard.width / 7,
2 * PlayingCard.height / 9,
)
):
PlayingCard.suits[suit](
height=PlayingCard.width / 12, rotation=180, mode=Mode.SUBTRACT
)
rank_int = PlayingCard.ranks.index(rank) + 1
rank_int = rank_int if rank_int < 10 else 1
with Locations((PlayingCard.width / 2, PlayingCard.height / 2)):
center_radius = 0 if rank_int == 1 else PlayingCard.width / 3.5
suit_rotation = 0 if rank_int == 1 else -90
suit_height = (
0.00159 * rank_int**2 - 0.0380 * rank_int + 0.37
) * PlayingCard.width
with PolarLocations(
radius=center_radius,
count=rank_int,
start_angle=90 if rank_int > 1 else 0,
):
PlayingCard.suits[suit](
height=suit_height,
rotation=suit_rotation,
mode=Mode.SUBTRACT,
)
super().__init__(
obj=playing_card.sketch, rotation=rotation, align=align, mode=mode
)
ace_spades = PlayingCard(rank="A", suit="Spades", align=Align.MIN)
ace_spades.color = Color("white")
king_hearts = PlayingCard(rank="K", suit="Hearts", align=Align.MIN)
king_hearts.color = Color("white")
queen_clubs = PlayingCard(rank="Q", suit="Clubs", align=Align.MIN)
queen_clubs.color = Color("white")
jack_diamonds = PlayingCard(rank="J", suit="Diamonds", align=Align.MIN)
jack_diamonds.color = Color("white")
ten_spades = PlayingCard(rank="10", suit="Spades", align=Align.MIN)
ten_spades.color = Color("white")
hand = Compound(
children=[
Rot(0, 0, -20) * Pos(0, 0, 0) * ace_spades,
Rot(0, 0, -10) * Pos(0, 0, -1) * king_hearts,
Rot(0, 0, 0) * Pos(0, 0, -2) * queen_clubs,
Rot(0, 0, 10) * Pos(0, 0, -3) * jack_diamonds,
Rot(0, 0, 20) * Pos(0, 0, -4) * ten_spades,
]
)
show_object(Pos(-20, 40) * hand)
show_object(box_builder.part, "box_builder")
show_object(
Pos(0, 0, (wall + deck) / 2) * lid_builder.part,
"lid_builder",
options={"alpha": 0.7},
)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#stud-wall .section}
[]{#examples_1.xhtml#id11}
## Stud Wall
{.align-center}
This example demonstrates creating custom ``{=html}Part``{=html} objects and putting them into assemblies. The custom object is a ``{=html}Stud``{=html} used in the building industry while the assembly is a ``{=html}StudWall``{=html} created from copies of ``{=html}Stud``{=html} objects for efficiency. Both the ``{=html}Stud``{=html} and ``{=html}StudWall``{=html} objects use ``{=html}RigidJoints``{=html} to define snap points which are used to position all of objects.
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
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)
:::
:::
:::
```{=html}
```
:::
::: {#examples_1.xhtml#tea-cup .section}
[]{#examples_1.xhtml#id12}
## Tea Cup
{.align-center}
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
wall_thickness = 3 * MM
fillet_radius = wall_thickness * 0.49
with BuildPart() as tea_cup:
# Create the bowl of the cup as a revolved cross section
with BuildSketch(Plane.XZ) as bowl_section:
with BuildLine():
# Start & end points with control tangents
s = Spline(
(30 * MM, 10 * MM),
(69 * MM, 105 * MM),
tangents=((1, 0.5), (0.7, 1)),
tangent_scalars=(1.75, 1),
)
# Lines to finish creating ½ the bowl shape
Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)
make_face() # Create a filled 2D shape
revolve(axis=Axis.Z)
# Hollow out the bowl with openings on the top and bottom
offset(amount=-wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE))
# Add a bottom to the bowl
with Locations((0, 0, (s @ 0).Y)):
Cylinder(radius=(s @ 0).X, height=wall_thickness)
# Smooth out all the edges
fillet(tea_cup.edges(), radius=fillet_radius)
# Determine where the handle contacts the bowl
handle_intersections = [
tea_cup.part.find_intersection_points(
Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))
)[-1][0]
for vertical_offset in [35 * MM, 80 * MM]
]
# Create a path for handle creation
with BuildLine(Plane.XZ) as handle_path:
Spline(
handle_intersections[0] - (wall_thickness / 2, 0),
handle_intersections[0] + (35 * MM, 30 * MM),
handle_intersections[0] + (40 * MM, 60 * MM),
handle_intersections[1] - (wall_thickness / 2, 0),
tangents=((1, 1.25), (-0.2, -1)),
)
# Align the cross section to the beginning of the path
with BuildSketch(handle_path.line ^ 0) as handle_cross_section:
RectangleRounded(wall_thickness, 8 * MM, fillet_radius)
sweep() # Sweep handle cross section along path
assert abs(tea_cup.part.volume - 130326) < 1
show(tea_cup, names=["tea cup"])
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
wall_thickness = 3 * MM
fillet_radius = wall_thickness * 0.49
# Create the bowl of the cup as a revolved cross section
# Start & end points with control tangents
s = Spline(
(30 * MM, 10 * MM),
(69 * MM, 105 * MM),
tangents=((1, 0.5), (0.7, 1)),
tangent_scalars=(1.75, 1),
)
# Lines to finish creating ½ the bowl shape
s += Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)
bowl_section = Plane.XZ * make_face(s) # Create a filled 2D shape
tea_cup = revolve(bowl_section, axis=Axis.Z)
# Hollow out the bowl with openings on the top and bottom
tea_cup = offset(
tea_cup, -wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE)
)
# Add a bottom to the bowl
tea_cup += Pos(0, 0, (s @ 0).Y) * Cylinder(radius=(s @ 0).X, height=wall_thickness)
# Smooth out all the edges
tea_cup = fillet(tea_cup.edges(), radius=fillet_radius)
# Determine where the handle contacts the bowl
handle_intersections = [
tea_cup.find_intersection_points(
Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))
)[-1][0]
for vertical_offset in [35 * MM, 80 * MM]
]
# Create a path for handle creation
path_spline = Spline(
handle_intersections[0] - (wall_thickness / 2, 0, 0),
handle_intersections[0] + (35 * MM, 0, 30 * MM),
handle_intersections[0] + (40 * MM, 0, 60 * MM),
handle_intersections[1] - (wall_thickness / 2, 0, 0),
tangents=((1, 0, 1.25), (-0.2, 0, -1)),
)
# Align the cross section to the beginning of the path
location = path_spline ^ 0
handle_cross_section = location * RectangleRounded(wall_thickness, 8 * MM, fillet_radius)
# Sweep handle cross section along path
tea_cup += sweep(handle_cross_section, path=path_spline)
# assert abs(tea_cup.part.volume - 130326.77052487945) < 1e-3
show(tea_cup, names=["tea cup"])
:::
:::
:::
```{=html}
```
This example demonstrates the creation a tea cup, which serves as an example of constructing complex, non-flat geometrical shapes programmatically.
The tea cup model involves several CAD techniques, such as:
- Revolve Operations: There is 1 occurrence of a revolve operation. This is used to create the main body of the tea cup by revolving a profile around an axis, a common technique for generating symmetrical objects like cups.
- Sweep Operations: There are 2 occurrences of sweep operations. The handle are created by sweeping a profile along a path to generate non-planar surfaces.
- Offset/Shell Operations: the bowl of the cup is hollowed out with the offset operation leaving the top open.
- Fillet Operations: There is 1 occurrence of a fillet operation which is used to round the edges for aesthetic improvement and to mimic real-world objects more closely.
:::
::: {#examples_1.xhtml#toy-truck .section}
[]{#examples_1.xhtml#id13}
## Toy Truck
{.align-center} {.align-center}
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
# Toy Truck Blue
truck_color = Color(0x4683CE)
# Create the main truck body — from bumper to bed, excluding the cab
with BuildPart() as body:
# The body has two axes of symmetry, so we start with a centered sketch.
# The default workplane is Plane.XY.
with BuildSketch() as body_skt:
Rectangle(20, 35)
# Fillet all the corners of the sketch.
# Alternatively, you could use RectangleRounded.
fillet(body_skt.vertices(), 1)
# Extrude the body shape upward
extrude(amount=10, taper=4)
# Reuse the sketch by accessing it explicitly
extrude(body_skt.sketch, amount=8, taper=2)
# Create symmetric fenders on Plane.YZ
with BuildSketch(Plane.YZ) as fender:
# The trapezoid has asymmetric angles (80°, 88°)
Trapezoid(18, 6, 80, 88, align=Align.MIN)
# Fillet top edge vertices (Y-direction highest group)
fillet(fender.vertices().group_by(Axis.Y)[-1], 1.5)
# Extrude the fender in both directions
extrude(amount=10.5, both=True)
# Create wheel wells with a shifted sketch on Plane.YZ
with BuildSketch(Plane.YZ.shift_origin((0, 3.5, 0))) as wheel_well:
Trapezoid(12, 4, 70, 85, align=Align.MIN)
fillet(wheel_well.vertices().group_by(Axis.Y)[-1], 2)
# Subtract the wheel well geometry
extrude(amount=10.5, both=True, mode=Mode.SUBTRACT)
# Fillet the top edges of the body
fillet(body.edges().group_by(Axis.Z)[-1], 1)
# Isolate a set of body edges and preview before filleting
body_edges = body.edges().group_by(Axis.Z)[-6]
fillet(body_edges, 0.1)
# Combine edge groups from both sides of the fender and fillet them
fender_edges = body.edges().group_by(Axis.X)[0] + body.edges().group_by(Axis.X)[-1]
fender_edges = fender_edges.group_by(Axis.Z)[1:]
fillet(fender_edges, 0.4)
# Create a sketch on the front of the truck for the grill
with BuildSketch(
Plane.XZ.offset(-body.vertices().sort_by(Axis.Y)[-1].Y - 0.5)
) as grill:
Rectangle(16, 8.5, align=(Align.CENTER, Align.MIN))
fillet(grill.vertices().group_by(Axis.Y)[-1], 1)
# Add headlights (subtractive circles)
with Locations((0, 6.5)):
with GridLocations(12, 0, 2, 1):
Circle(1, mode=Mode.SUBTRACT)
# Add air vents (subtractive slots)
with Locations((0, 3)):
with GridLocations(0, 0.8, 1, 4):
SlotOverall(10, 0.5, mode=Mode.SUBTRACT)
# Extrude the grill forward
extrude(amount=2)
# Fillet only the outer grill edges (exclude headlight/vent cuts)
grill_perimeter = body.faces().sort_by(Axis.Y)[-1].outer_wire()
fillet(grill_perimeter.edges(), 0.2)
# Create the bumper as a separate part inside the body
with BuildPart() as bumper:
# Find the midpoint of a front edge and shift slightly to position the bumper
front_cnt = body.edges().group_by(Axis.Z)[0].sort_by(Axis.Y)[-1] @ 0.5 - (0, 3)
with BuildSketch() as bumper_plan:
# Use BuildLine to draw an elliptical arc and offset
with BuildLine():
EllipticalCenterArc(front_cnt, 20, 4, start_angle=60, end_angle=120)
offset(amount=1)
make_face()
# Extrude the bumper symmetrically
extrude(amount=1, both=True)
fillet(bumper.edges(), 0.25)
# Define a joint on top of the body to connect the cab later
RigidJoint("body_top", joint_location=Location((0, -7.5, 10)))
body.part.color = truck_color
# Create the cab as an independent part to mount on the body
with BuildPart() as cab:
with BuildSketch() as cab_plan:
RectangleRounded(16, 16, 1)
# Split the sketch to work on one symmetric half
split(bisect_by=Plane.YZ)
# Extrude the cab forward and upward at an angle
extrude(amount=7, dir=(0, 0.15, 1))
fillet(cab.edges().group_by(Axis.Z)[-1].group_by(Axis.X)[1:], 1)
# Rear window
with BuildSketch(Plane.XZ.shift_origin((0, 0, 3))) as rear_window:
RectangleRounded(8, 4, 0.75)
extrude(amount=10, mode=Mode.SUBTRACT)
# Front window
with BuildSketch(Plane.XZ) as front_window:
RectangleRounded(15.2, 11, 0.75)
extrude(amount=-10, mode=Mode.SUBTRACT)
# Side windows
with BuildSketch(Plane.YZ) as side_window:
with Locations((3.5, 0)):
with GridLocations(10, 0, 2, 1):
Trapezoid(9, 5.5, 80, 100, align=(Align.CENTER, Align.MIN))
fillet(side_window.vertices().group_by(Axis.Y)[-1], 0.5)
extrude(amount=12, both=True, mode=Mode.SUBTRACT)
# Mirror to complete the cab
mirror(about=Plane.YZ)
# Define joint on cab base
RigidJoint("cab_base", joint_location=Location((0, 0, 0)))
cab.part.color = truck_color
# Attach the cab to the truck body using joints
body.joints["body_top"].connect_to(cab.joints["cab_base"])
# Show the result
show(body.part, cab.part)
:::
:::
:::
```{=html}
```
This example demonstrates how to design a toy truck using BuildPart and BuildSketch in Builder mode. The model includes a detailed body, cab, grill, and bumper, showcasing techniques like sketch reuse, symmetry, tapered extrusions, selective filleting, and the use of joints for part assembly. Ideal for learning complex part construction and hierarchical modeling in build123d.
:::
::: {#examples_1.xhtml#vase .section}
[]{#examples_1.xhtml#id14}
## Vase
{.align-center}
```{=html}
```
```{=html}
```
[🔨 Reference Implementation (Builder Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show_object
with BuildPart() as vase:
with BuildSketch() as profile:
with BuildLine() as outline:
l1 = Line((0, 0), (12, 0))
l2 = RadiusArc(l1 @ 1, (15, 20), 50)
l3 = Spline(l2 @ 1, (22, 40), (20, 50), tangents=(l2 % 1, (-0.75, 1)))
l4 = RadiusArc(l3 @ 1, l3 @ 1 + Vector(0, 5), 5)
l5 = Spline(
l4 @ 1,
l4 @ 1 + Vector(2.5, 2.5),
l4 @ 1 + Vector(0, 5),
tangents=(l4 % 1, (-1, 0)),
)
Polyline(
l5 @ 1,
l5 @ 1 + Vector(0, 1),
(0, (l5 @ 1).Y + 1),
l1 @ 0,
)
make_face()
revolve(axis=Axis.Y)
offset(openings=vase.faces().filter_by(Axis.Y)[-1], amount=-1)
top_edges = (
vase.edges().filter_by_position(Axis.Y, 60, 62).filter_by(GeomType.CIRCLE)
)
fillet(top_edges, radius=0.25)
fillet(vase.edges().sort_by(Axis.Y)[0], radius=0.5)
show_object(Rot(90, 0, 0) * vase.part, name="vase")
:::
:::
:::
```{=html}
```
```{=html}
```
```{=html}
```
[✏️ Reference Implementation (Algebra Mode)]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show_object
l1 = Line((0, 0), (12, 0))
l2 = RadiusArc(l1 @ 1, (15, 20), 50)
l3 = Spline(l2 @ 1, (22, 40), (20, 50), tangents=(l2 % 1, (-0.75, 1)))
l4 = RadiusArc(l3 @ 1, l3 @ 1 + Vector(0, 5), 5)
l5 = Spline(
l4 @ 1,
l4 @ 1 + Vector(2.5, 2.5),
l4 @ 1 + Vector(0, 5),
tangents=(l4 % 1, (-1, 0)),
)
outline = l1 + l2 + l3 + l4 + l5
outline += Polyline(
l5 @ 1,
l5 @ 1 + Vector(0, 1),
(0, (l5 @ 1).Y + 1),
l1 @ 0,
)
profile = make_face(outline.edges())
vase = revolve(profile, Axis.Y)
vase = offset(vase, openings=vase.faces().sort_by(Axis.Y).last, amount=-1)
top_edges = vase.edges().filter_by(GeomType.CIRCLE).filter_by_position(Axis.Y, 60, 62)
vase = fillet(top_edges, radius=0.25)
vase = fillet(vase.edges().sort_by(Axis.Y).first, radius=0.5)
show_object(Rot(90, 0, 0) * vase, name="vase")
:::
:::
:::
```{=html}
```
This example demonstrates the build123d techniques involving the creation of a vase. Specifically, it showcases the processes of revolving a sketch, shelling (creating a hollow object by removing material from its interior), and selecting edges by position range and type for the application of fillets (rounding off the edges).
- Sketching: Drawing a 2D profile or outline that represents the side view of the vase.
- Revolving: Rotating the sketch around an axis to create a 3D object. This step transforms the 2D profile into a 3D vase shape.
- Offset/Shelling: Removing material from the interior of the solid vase to create a hollow space, making it resemble a real vase more closely.
- Edge Filleting: Selecting specific edges of the vase for filleting, which involves rounding those edges. The edges are selected based on their position and type.
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tttt.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tttt.xhtml#too-tall-toby-ttt-tutorials .section}
# Too Tall Toby (TTT) Tutorials
{.align-center}
To enhance users' proficiency with Build123D, this section offers a series of challenges. In these challenges, users are presented with a CAD drawing and tasked with designing the part. Their goal is to match the part's mass to a specified target.
These drawings were skillfully crafted and generously provided to Build123D by Too Tall Toby, a renowned figure in the realm of 3D CAD. Too Tall Toby is the host of the World Championship of 3D CAD Speedmodeling. For additional 3D CAD challenges and content, be sure to visit [Toby's youtube channel](https://www.Youtube.com/TooTallToby){.reference .external}[ \[https://www.Youtube.com/TooTallToby\]]{.link-target}.
Feel free to click on the parts below to embark on these engaging challenges.
::: {.sd-container-fluid .sd-sphinx-override .sd-mb-4 .docutils}
::: {.sd-row .sd-row-cols-3 .sd-row-cols-xs-3 .sd-row-cols-sm-3 .sd-row-cols-md-3 .sd-row-cols-lg-3 .docutils}
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-01 Bearing Bracket
:::
:::
[[Party Pack 01-01 Bearing Bracket]{.std .std-ref}](#tttt.xhtml#ttt-ppp0101){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-02 Post Cap
:::
:::
[[Party Pack 01-02 Post Cap]{.std .std-ref}](#tttt.xhtml#ttt-ppp0102){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-03 C Clamp Base
:::
:::
[[Party Pack 01-03 C Clamp Base]{.std .std-ref}](#tttt.xhtml#ttt-ppp0103){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-04 Angle Bracket
:::
:::
[[Party Pack 01-04 Angle Bracket]{.std .std-ref}](#tttt.xhtml#ttt-ppp0104){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-05 Paste Sleeve
:::
:::
[[Party Pack 01-05 Paste Sleeve]{.std .std-ref}](#tttt.xhtml#ttt-ppp0105){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-06 Bearing Jig
:::
:::
[[Party Pack 01-06 Bearing Jig]{.std .std-ref}](#tttt.xhtml#ttt-ppp0106){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-07 Flanged Hub
:::
:::
[[Party Pack 01-07 Flanged Hub]{.std .std-ref}](#tttt.xhtml#ttt-ppp0107){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-08 Tie Plate
:::
:::
[[Party Pack 01-08 Tie Plate]{.std .std-ref}](#tttt.xhtml#ttt-ppp0108){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-09 Corner Tie
:::
:::
[[Party Pack 01-09 Corner Tie]{.std .std-ref}](#tttt.xhtml#ttt-ppp0109){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Party Pack 01-10 Light Cap
:::
:::
[[Party Pack 01-10 Light Cap]{.std .std-ref}](#tttt.xhtml#ttt-ppp0110){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
23-02-02 SM Hanger
:::
:::
[[23-02-02 SM Hanger]{.std .std-ref}](#tttt.xhtml#ttt-23-02-02-sm-hanger){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
23-T-24 Curved Support
:::
:::
[[23-T-24 Curved Support]{.std .std-ref}](#tttt.xhtml#ttt-23-t-24){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
24-SPO-06 Buffer Stand
:::
:::
[[24-SPO-06 Buffer Stand]{.std .std-ref}](#tttt.xhtml#ttt-24-spo-06){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
:::
:::
::: {#tttt.xhtml#party-pack-01-01-bearing-bracket .section}
[]{#tttt.xhtml#ttt-ppp0101}
## Party Pack 01-01 Bearing Bracket
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
797.15 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-01 Bearing Bracket
"""
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
with BuildPart() as p:
with BuildSketch() as s:
Rectangle(115, 50)
with Locations((5 / 2, 0)):
SlotOverall(90, 12, mode=Mode.SUBTRACT)
extrude(amount=15)
with BuildSketch(Plane.XZ.offset(50 / 2)) as s3:
with Locations((-115 / 2 + 26, 15)):
SlotOverall(42 + 2 * 26 + 12, 2 * 26, rotation=90)
zz = extrude(amount=-12)
split(bisect_by=Plane.XY)
edgs = p.part.edges().filter_by(Axis.Y).group_by(Axis.X)[-2]
fillet(edgs, 9)
with Locations(zz.faces().sort_by(Axis.Y)[0]):
with Locations((42 / 2 + 6, 0)):
CounterBoreHole(24 / 2, 34 / 2, 4)
mirror(about=Plane.XZ)
with BuildSketch() as s4:
RectangleRounded(115, 50, 6)
extrude(amount=80, mode=Mode.INTERSECT)
# fillet does not work right, mode intersect is safer
with BuildSketch(Plane.YZ) as s4:
with BuildLine() as bl:
l1 = Line((0, 0), (18 / 2, 0))
l2 = PolarLine(l1 @ 1, 8, 60, length_mode=LengthMode.VERTICAL)
l3 = Line(l2 @ 1, (0, 8))
mirror(about=Plane.YZ)
make_face()
extrude(amount=115/2, both=True, mode=Mode.SUBTRACT)
show_object(p)
got_mass = p.part.volume*densa
want_mass = 797.15
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-02-post-cap .section}
[]{#tttt.xhtml#ttt-ppp0102}
## Party Pack 01-02 Post Cap
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
43.09 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-02 Post Cap
"""
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
# TTT Party Pack 01: PPP0102, mass(abs) = 43.09g
with BuildPart() as p:
with BuildSketch(Plane.XZ) as sk1:
Rectangle(49, 48 - 8, align=(Align.CENTER, Align.MIN))
Rectangle(9, 48, align=(Align.CENTER, Align.MIN))
with Locations((9 / 2, 40)):
Ellipse(20, 8)
split(bisect_by=Plane.YZ)
revolve(axis=Axis.Z)
with BuildSketch(Plane.YZ.offset(-15)) as xc1:
with Locations((0, 40 / 2 - 17)):
Ellipse(10 / 2, 4 / 2)
with BuildLine(Plane.XZ) as l1:
CenterArc((-15, 40 / 2), 17, 90, 180)
sweep(path=l1)
fillet(p.edges().filter_by(GeomType.CIRCLE, reverse=True).group_by(Axis.X)[0], 1)
with BuildLine(mode=Mode.PRIVATE) as lc1:
PolarLine(
(42 / 2, 0), 37, 94, length_mode=LengthMode.VERTICAL
) # construction line
pts = [
(0, 0),
(42 / 2, 0),
((lc1.line @ 1).X, (lc1.line @ 1).Y),
(0, (lc1.line @ 1).Y),
]
with BuildSketch(Plane.XZ) as sk2:
Polygon(*pts, align=None)
fillet(sk2.vertices().group_by(Axis.X)[1], 3)
revolve(axis=Axis.Z, mode=Mode.SUBTRACT)
show(p)
got_mass = p.part.volume*densc
want_mass = 43.09
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-03-c-clamp-base .section}
[]{#tttt.xhtml#ttt-ppp0103}
## Party Pack 01-03 C Clamp Base
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
96.13 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-03 C Clamp Base
"""
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
with BuildPart() as ppp0103:
with BuildSketch() as sk1:
RectangleRounded(34 * 2, 95, 18)
with Locations((0, -2)):
RectangleRounded((34 - 16) * 2, 95 - 18 - 14, 7, mode=Mode.SUBTRACT)
with Locations((-34 / 2, 0)):
Rectangle(34, 95, 0, mode=Mode.SUBTRACT)
extrude(amount=16)
with BuildSketch(Plane.XZ.offset(-95 / 2)) as cyl1:
with Locations((0, 16 / 2)):
Circle(16 / 2)
extrude(amount=18)
with BuildSketch(Plane.XZ.offset(95 / 2 - 14)) as cyl2:
with Locations((0, 16 / 2)):
Circle(16 / 2)
extrude(amount=23)
with Locations(Plane.XZ.offset(95 / 2 + 9)):
with Locations((0, 16 / 2)):
CounterSinkHole(5.5 / 2, 11.2 / 2, None, 90)
show(ppp0103)
got_mass = ppp0103.part.volume*densb
want_mass = 96.13
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-04-angle-bracket .section}
[]{#tttt.xhtml#ttt-ppp0104}
## Party Pack 01-04 Angle Bracket
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
310.00 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-04 Angle Bracket
"""
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
d1, d2, d3 = 38, 26, 16
h1, h2, h3, h4 = 20, 8, 7, 23
w1, w2, w3 = 80, 10, 5
f1, f2, f3 = 4, 10, 5
sloth1, sloth2 = 18, 12
slotw1, slotw2 = 17, 14
with BuildPart() as p:
with BuildSketch() as s:
Circle(d1 / 2)
extrude(amount=h1)
with BuildSketch(Plane.XY.offset(h1)) as s2:
Circle(d2 / 2)
extrude(amount=h2)
with BuildSketch(Plane.YZ) as s3:
Rectangle(d1 + 15, h3, align=(Align.CENTER, Align.MIN))
extrude(amount=w1 - d1 / 2)
# fillet workaround \/
ped = p.part.edges().group_by(Axis.Z)[2].filter_by(GeomType.CIRCLE)
fillet(ped, f1)
with BuildSketch(Plane.YZ) as s3a:
Rectangle(d1 + 15, 15, align=(Align.CENTER, Align.MIN))
Rectangle(d1, 15, mode=Mode.SUBTRACT, align=(Align.CENTER, Align.MIN))
extrude(amount=w1 - d1 / 2, mode=Mode.SUBTRACT)
# end fillet workaround /\
with BuildSketch() as s4:
Circle(d3 / 2)
extrude(amount=h1 + h2, mode=Mode.SUBTRACT)
with BuildSketch() as s5:
with Locations((w1 - d1 / 2 - w2 / 2, 0)):
Rectangle(w2, d1)
extrude(amount=-h4)
fillet(p.part.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1], f2)
fillet(p.part.edges().group_by(Axis.X)[-4].sort_by(Axis.Z)[-2], f3)
pln = Plane.YZ.offset(w1 - d1 / 2)
with BuildSketch(pln) as s6:
with Locations((0, -h4)):
SlotOverall(slotw1 * 2, sloth1, 90)
extrude(amount=-w3, mode=Mode.SUBTRACT)
with BuildSketch(pln) as s6b:
with Locations((0, -h4)):
SlotOverall(slotw2 * 2, sloth2, 90)
extrude(amount=-w2, mode=Mode.SUBTRACT)
show(p)
got_mass = p.part.volume*densa
want_mass = 310
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-05-paste-sleeve .section}
[]{#tttt.xhtml#ttt-ppp0105}
## Party Pack 01-05 Paste Sleeve
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
57.08 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-05 Paste Sleeve
"""
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
with BuildPart() as p:
with BuildSketch() as s:
SlotOverall(45, 38)
offset(amount=3)
with BuildSketch(Plane.XY.offset(133 - 30)) as s2:
SlotOverall(60, 4)
offset(amount=3)
loft()
with BuildSketch() as s3:
SlotOverall(45, 38)
with BuildSketch(Plane.XY.offset(133 - 30)) as s4:
SlotOverall(60, 4)
loft(mode=Mode.SUBTRACT)
extrude(p.part.faces().sort_by(Axis.Z)[0], amount=30)
show(p)
got_mass = p.part.volume*densc
want_mass = 57.08
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-06-bearing-jig .section}
[]{#tttt.xhtml#ttt-ppp0106}
## Party Pack 01-06 Bearing Jig
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
328.02 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-06 Bearing Jig
"""
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
r1, r2, r3, r4, r5 = 30 / 2, 13 / 2, 12 / 2, 10, 6 # radii used
x1 = 44 # lengths used
y1, y2, y3, y4, y_tot = 36, 36 - 22 / 2, 22 / 2, 42, 69 # widths used
with BuildSketch(Location((0, -r1, y3))) as sk_body:
with BuildLine() as l:
c1 = Line((r1, 0), (r1, y_tot), mode=Mode.PRIVATE) # construction line
m1 = Line((0, y_tot), (x1 / 2, y_tot))
m2 = JernArc(m1 @ 1, m1 % 1, r4, -90 - 45)
m3 = IntersectingLine(m2 @ 1, m2 % 1, c1)
m4 = Line(m3 @ 1, (r1, r1))
m5 = JernArc(m4 @ 1, m4 % 1, r1, -90)
mirror(about=Plane.YZ)
make_face()
fillet(sk_body.vertices().group_by(Axis.Y)[1], 12)
with Locations((x1 / 2, y_tot - 10), (-x1 / 2, y_tot - 10)):
Circle(r2, mode=Mode.SUBTRACT)
# Keyway
with Locations((0, r1)):
Circle(r3, mode=Mode.SUBTRACT)
Rectangle(4, 3 + 6, align=(Align.CENTER, Align.MIN), mode=Mode.SUBTRACT)
with BuildPart() as p:
Box(200, 200, 22) # Oversized plate
# Cylinder underneath
Cylinder(r1, y2, align=(Align.CENTER, Align.CENTER, Align.MAX))
fillet(p.edges(Select.NEW), r5) # Weld together
extrude(sk_body.sketch, amount=-y1, mode=Mode.INTERSECT) # Cut to shape
# Remove slot
with Locations((0, y_tot - r1 - y4, 0)):
Box(
y_tot,
y_tot,
10,
align=(Align.CENTER, Align.MIN, Align.CENTER),
mode=Mode.SUBTRACT,
)
show(p)
got_mass = p.part.volume*densa
want_mass = 328.02
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-07-flanged-hub .section}
[]{#tttt.xhtml#ttt-ppp0107}
## Party Pack 01-07 Flanged Hub
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
372.99 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-07 Flanged Hub
"""
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
with BuildPart() as p:
with BuildSketch() as s:
Circle(130 / 2)
extrude(amount=8)
with BuildSketch(Plane.XY.offset(8)) as s2:
Circle(84 / 2)
extrude(amount=25 - 8)
with BuildSketch(Plane.XY.offset(25)) as s3:
Circle(35 / 2)
extrude(amount=52 - 25)
with BuildSketch() as s4:
Circle(73 / 2)
extrude(amount=18, mode=Mode.SUBTRACT)
pln2 = p.part.faces().sort_by(Axis.Z)[5]
with BuildSketch(Plane.XY.offset(52)) as s5:
Circle(20 / 2)
extrude(amount=-52, mode=Mode.SUBTRACT)
fillet(
p.part.edges()
.filter_by(GeomType.CIRCLE)
.sort_by(Axis.Z)[2:-2]
.sort_by(SortBy.RADIUS)[1:],
3,
)
pln = Plane(pln2)
pln.origin = pln.origin + Vector(20 / 2, 0, 0)
pln = pln.rotated((0, 45, 0))
pln = pln.offset(-25 + 3 + 0.10)
with BuildSketch(pln) as s6:
Rectangle((73 - 35) / 2 * 1.414 + 5, 3)
zz = extrude(amount=15, taper=-20 / 2, mode=Mode.PRIVATE)
zz2 = split(zz, bisect_by=Plane.XY.offset(25), mode=Mode.PRIVATE)
zz3 = split(zz2, bisect_by=Plane.YZ.offset(35 / 2 - 1), mode=Mode.PRIVATE)
with PolarLocations(0, 3):
add(zz3)
with Locations(Plane.XY.offset(8)):
with PolarLocations(107.95 / 2, 6):
CounterBoreHole(6 / 2, 13 / 2, 4)
show(p)
got_mass = p.part.volume*densb
want_mass = 372.99
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-08-tie-plate .section}
[]{#tttt.xhtml#ttt-ppp0108}
## Party Pack 01-08 Tie Plate
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
3387.06 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-08 Tie Plate
"""
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
with BuildPart() as p:
with BuildSketch() as s1:
Rectangle(188 / 2 - 33, 162, align=(Align.MIN, Align.CENTER))
with Locations((188 / 2 - 33, 0)):
SlotOverall(190, 33 * 2, rotation=90)
mirror(about=Plane.YZ)
with GridLocations(188 - 2 * 33, 190 - 2 * 33, 2, 2):
Circle(29 / 2, mode=Mode.SUBTRACT)
Circle(84 / 2, mode=Mode.SUBTRACT)
extrude(amount=16)
with BuildPart() as p2:
with BuildSketch(Plane.XZ) as s2:
with BuildLine() as l1:
l1 = Polyline(
(222 / 2 + 14 - 40 - 40, 0),
(222 / 2 + 14 - 40, -35 + 16),
(222 / 2 + 14, -35 + 16),
(222 / 2 + 14, -35 + 16 + 30),
(222 / 2 + 14 - 40 - 40, -35 + 16 + 30),
close=True,
)
make_face()
with Locations((222 / 2, -35 + 16 + 14)):
Circle(11 / 2, mode=Mode.SUBTRACT)
extrude(amount=20 / 2, both=True)
with BuildSketch() as s3:
with Locations(l1 @ 0):
Rectangle(40 + 40, 8, align=(Align.MIN, Align.CENTER))
with Locations((40, 0)):
Rectangle(40, 20, align=(Align.MIN, Align.CENTER))
extrude(amount=30, both=True, mode=Mode.INTERSECT)
mirror(about=Plane.YZ)
show(p)
got_mass = p.part.volume*densa
want_mass = 3387.06
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-09-corner-tie .section}
[]{#tttt.xhtml#ttt-ppp0109}
## Party Pack 01-09 Corner Tie
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
307.23 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-09 Corner Tie
"""
from math import sqrt
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
with BuildPart() as ppp109:
with BuildSketch() as one:
Rectangle(69, 75, align=(Align.MAX, Align.CENTER))
fillet(one.vertices().group_by(Axis.X)[0], 17)
extrude(amount=13)
centers = [
arc.arc_center
for arc in ppp109.edges().filter_by(GeomType.CIRCLE).group_by(Axis.Z)[-1]
]
with Locations(*centers):
CounterBoreHole(radius=8 / 2, counter_bore_radius=15 / 2, counter_bore_depth=4)
with BuildSketch(Plane.YZ) as two:
with Locations((0, 45)):
Circle(15)
with BuildLine() as bl:
c = Line((75 / 2, 0), (75 / 2, 60), mode=Mode.PRIVATE)
u = two.edge().find_tangent(75 / 2 + 90)[0] # where is the slope 75/2?
l1 = IntersectingLine(
two.edge().position_at(u), -two.edge().tangent_at(u), other=c
)
Line(l1 @ 0, (0, 45))
Polyline((0, 0), c @ 0, l1 @ 1)
mirror(about=Plane.YZ)
make_face()
with Locations((0, 45)):
Circle(12 / 2, mode=Mode.SUBTRACT)
extrude(amount=-13)
with BuildSketch(Plane((0, 0, 0), x_dir=(1, 0, 0), z_dir=(1, 0, 1))) as three:
Rectangle(45 * 2 / sqrt(2) - 37.5, 75, align=(Align.MIN, Align.CENTER))
with Locations(three.edges().sort_by(Axis.X)[-1].center()):
Circle(37.5)
Circle(33 / 2, mode=Mode.SUBTRACT)
split(bisect_by=Plane.YZ)
extrude(amount=6)
f = ppp109.faces().filter_by(Axis((0, 0, 0), (-1, 0, 1)))[0]
extrude(f, until=Until.NEXT)
fillet(ppp109.edges().filter_by(Axis.Y).sort_by(Axis.Z)[2], 16)
# extrude(f, amount=10)
# fillet(ppp109.edges(Select.NEW), 16)
show(ppp109)
got_mass = ppp109.part.volume * densb
want_mass = 307.23
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.2f} g")
assert delta < tolerance, f"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}"
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#party-pack-01-10-light-cap .section}
[]{#tttt.xhtml#ttt-ppp0110}
## Party Pack 01-10 Light Cap
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
211.30 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby Party Pack 01-10 Light Cap
"""
from math import sqrt, asin, pi
from build123d import *
from ocp_vscode import *
densa = 7800 / 1e6 # carbon steel density g/mm^3
densb = 2700 / 1e6 # aluminum alloy
densc = 1020 / 1e6 # ABS
# The smaller cross-section is defined as having R40, height 46,
# and base width 84, so clearly it's not entirely a half-circle or
# similar; the base's extreme points need to connect via tangents
# to the R40 arc centered 6mm above the baseline.
#
# Compute the angle of the tangent line (working with the
# left/negativeX side, given symmetry) by observing the tangent
# point (T), the circle's center (O), and the baseline's edge (P)
# form a right triangle, so:
OT=40
OP=sqrt((-84/2)**2+(-6)**2)
TP=sqrt(OP**2-40**2)
OPT_degrees = asin(OT/OP) * 180/pi
# Correct for the fact that OP isn't horizontal.
OP_to_X_axis_degrees = asin(6/OP) * 180/pi
left_tangent_degrees = OPT_degrees + OP_to_X_axis_degrees
left_tangent_length = TP
with BuildPart() as outer:
with BuildSketch(Plane.XZ) as sk:
with BuildLine():
l1 = PolarLine(start=(-84/2, 0), length=left_tangent_length, angle=left_tangent_degrees)
l2 = TangentArc(l1@1, (0, 46), tangent=l1%1)
l3 = offset(amount=-8, side=Side.RIGHT, closed=False, mode=Mode.ADD)
l4 = Line(l1@0, l3@1)
l5 = Line(l3@0, l2@1)
l6 = Line(l3@0, (0, 46-16))
l7 = IntersectingLine(start=l6@1, direction=(-1,0), other=l3)
make_face()
revolve(axis=Axis.Z)
sk = sk.sketch & Plane.XZ*Rectangle(1000, 1000, align=[Align.CENTER, Align.MIN])
positive_Z = Box(100, 100, 100, align=[Align.CENTER, Align.MIN, Align.MIN])
p = outer.part & positive_Z
cross_section = sk + mirror(sk, about=Plane.YZ)
p += extrude(cross_section, amount=50)
p += mirror(p, about=Plane.XZ.offset(50))
p += fillet(p.edges().filter_by(GeomType.LINE).filter_by(Axis.Y).group_by(Axis.Z)[-1], radius=8)
ppp0110 = p
got_mass = ppp0110.volume*densc
want_mass = 211.30
tolerance = 1
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.1f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
show(ppp0110)
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#sm-hanger .section}
[]{#tttt.xhtml#ttt-23-02-02-sm-hanger}
## 23-02-02 SM Hanger
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
1028g +/- 10g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Creation of a complex sheet metal part
name: ttt_sm_hanger.py
by: Gumyr
date: July 17, 2023
desc:
This example implements the sheet metal part described in Too Tall Toby's
sm_hanger CAD challenge.
Notably, a BuildLine/Curve object is filleted by providing all the vertices
and allowing the fillet operation filter out the end vertices. The
make_brake_formed operation is used both in Algebra and Builder mode to
create a sheet metal part from just an outline and some dimensions.
license:
Copyright 2023 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 *
sheet_thickness = 4 * MM
# Create the main body from a side profile
with BuildPart() as side:
d = Vector(1, 0, 0).rotate(Axis.Y, 60)
with BuildLine(Plane.XZ) as side_line:
l1 = Line((0, 65), (170 / 2, 65))
l2 = PolarLine(l1 @ 1, length=65, direction=d, length_mode=LengthMode.VERTICAL)
l3 = Line(l2 @ 1, (170 / 2, 0))
fillet(side_line.vertices(), 7)
make_brake_formed(
thickness=sheet_thickness,
station_widths=[40, 40, 40, 112.52 / 2, 112.52 / 2, 112.52 / 2],
side=Side.RIGHT,
)
fe = side.edges().filter_by(Axis.Z).group_by(Axis.Z)[0].sort_by(Axis.Y)[-1]
fillet(fe, radius=7)
# Create the "wings" at the top
with BuildPart() as wing:
with BuildLine(Plane.YZ) as wing_line:
l1 = Line((0, 65), (80 / 2 + 1.526 * sheet_thickness, 65))
PolarLine(l1 @ 1, 20.371288916, direction=Vector(0, 1, 0).rotate(Axis.X, -75))
fillet(wing_line.vertices(), 7)
make_brake_formed(
thickness=sheet_thickness,
station_widths=110 / 2,
side=Side.RIGHT,
)
bottom_edge = wing.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[0]
fillet(bottom_edge, radius=7)
# Create the tab at the top in Algebra mode
tab_line = Plane.XZ * Polyline(
(20, 65 - sheet_thickness), (56 / 2, 65 - sheet_thickness), (56 / 2, 88)
)
tab_line = fillet(tab_line.vertices(), 7)
tab = make_brake_formed(sheet_thickness, 8, tab_line, Side.RIGHT)
tab = fillet(tab.edges().filter_by(Axis.X).group_by(Axis.Z)[-1].sort_by(Axis.Y)[-1], 5)
tab -= Pos((0, 0, 80)) * Rot(0, 90, 0) * Hole(5, 100)
# Combine the parts together
with BuildPart() as sm_hanger:
add([side.part, wing.part])
mirror(about=Plane.XZ)
with BuildSketch(Plane.XY.offset(65)) as h1:
with Locations((20, 0)):
Rectangle(30, 30, align=(Align.MIN, Align.CENTER))
fillet(h1.vertices().group_by(Axis.X)[-1], 7)
SlotCenterPoint((154, 0), (154 / 2, 0), 20)
extrude(amount=-40, mode=Mode.SUBTRACT)
with BuildSketch() as h2:
SlotCenterPoint((206, 0), (206 / 2, 0), 20)
extrude(amount=40, mode=Mode.SUBTRACT)
add(tab)
mirror(about=Plane.YZ)
mirror(about=Plane.XZ)
got_mass = sm_hanger.part.volume*7800*1e-6
want_mass = 1028
tolerance = 10
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.1f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
assert abs(got_mass - 1028) < 10, f'{got_mass=}, want=1028, tolerance=10'
show(sm_hanger)
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#t-24-curved-support .section}
[]{#tttt.xhtml#ttt-23-t-24}
## 23-T-24 Curved Support
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
1294 g
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
"""
Too Tall Toby challenge 23-T-24 CURVED SUPPORT
"""
from math import sin, cos, tan, radians
from build123d import *
from ocp_vscode import *
import sympy
# This problem uses the sympy symbolic math solver
# Define the symbols for the unknowns
# - the center of the radius 30 arc (x30, y30)
# - the center of the radius 66 arc (x66, y66)
# - end of the 8° line (l8x, l8y)
# - the point with the radius 30 and 66 arc meet i30_66
# - the start of the horizontal line lh
y30, x66, xl8, yl8 = sympy.symbols("y30 x66 xl8 yl8")
x30 = 77 - 55 / 2
y66 = 66 + 32
# There are 4 unknowns so we need 4 equations
equations = [
(x66 - x30) ** 2 + (y66 - y30) ** 2 - (66 + 30) ** 2, # distance between centers
xl8 - (x30 + 30 * sin(radians(8))), # 8 degree slope
yl8 - (y30 + 30 * cos(radians(8))), # 8 degree slope
(yl8 - 50) / (55 / 2 - xl8) - tan(radians(8)), # 8 degree slope
]
# There are two solutions but we want the 2nd one
solution = {k: float(v) for k,v in sympy.solve(equations, dict=True)[1].items()}
# Create the critical points
c30 = Vector(x30, solution[y30])
c66 = Vector(solution[x66], y66)
l8 = Vector(solution[xl8], solution[yl8])
i30_66 = Line(c30, c66) @ (30 / (30 + 66))
lh = Vector(c66.X, 32)
with BuildLine() as profile:
l1 = Line((55 / 2, 50), l8)
l2 = RadiusArc(l1 @ 1, i30_66, 30)
l3 = RadiusArc(l2 @ 1, lh, -66)
l4 = Polyline(l3 @ 1, (125, 32), (125, 0), (0, 0), (0, (l1 @ 0).Y), l1 @ 0)
with BuildPart() as curved_support:
with BuildSketch() as base_plan:
c_8_degrees = Circle(55 / 2)
with Locations((0, 125)):
Circle(30 / 2)
base_hull = make_hull(mode=Mode.PRIVATE)
extrude(amount=32)
extrude(c_8_degrees, amount=60)
extrude(base_hull, amount=11)
with BuildSketch(Plane.YZ) as bridge:
make_face(profile.edges())
extrude(amount=11 / 2, both=True)
Hole(35 / 2)
with Locations((0, 125)):
Hole(20 / 2)
got_mass = curved_support.part.volume * 7800e-6
want_mass = 1294
delta = abs(got_mass - want_mass)
tolerance = 3
print(f"Mass: {got_mass:0.1f} g")
assert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'
show(curved_support)
:::
:::
:::
```{=html}
```
:::
::: {#tttt.xhtml#spo-06-buffer-stand .section}
[]{#tttt.xhtml#ttt-24-spo-06}
## 24-SPO-06 Buffer Stand
{.align-center}
```{=html}
```
```{=html}
```
[Object Mass]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
3.92 lbs
:::
```{=html}
```
```{=html}
```
```{=html}
```
[Reference Implementation]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
with BuildPart() as p:
with BuildSketch() as xy:
with BuildLine():
l1 = ThreePointArc((5 / 2, -1.25), (5.5 / 2, 0), (5 / 2, 1.25))
Polyline(l1 @ 0, (0, -1.25), (0, 1.25), l1 @ 1)
make_face()
extrude(amount=4)
with BuildSketch(Plane.YZ) as yz:
Trapezoid(2.5, 4, 90 - 6, align=(Align.CENTER, Align.MIN))
full_round(yz.edges().sort_by(SortBy.LENGTH)[0])
circle_edge = yz.edges().filter_by(GeomType.CIRCLE)[0]
arc_center = circle_edge.arc_center
arc_radius = circle_edge.radius
extrude(amount=10, mode=Mode.INTERSECT)
# To avoid OCCT problems, don't attempt to extend the top arc, remove instead
with BuildPart(mode=Mode.SUBTRACT) as internals:
y = p.edges().filter_by(Axis.X).sort_by(Axis.Z)[-1].center().Z
with BuildSketch(Plane.YZ.offset(4.25 / 2)) as yz:
Trapezoid(2.5, y, 90 - 6, align=(Align.CENTER, Align.MIN))
with Locations(arc_center):
Circle(arc_radius, mode=Mode.SUBTRACT)
extrude(amount=-(4.25 - 3.5) / 2)
with BuildSketch(Plane.YZ.offset(3.5 / 2)) as yz:
Trapezoid(2.5, 4, 90 - 6, align=(Align.CENTER, Align.MIN))
extrude(amount=-3.5 / 2)
with BuildSketch(Plane.XZ.offset(-2)) as xz:
with Locations((0, 4)):
RectangleRounded(4.25, 7.5, 0.5)
extrude(amount=4, mode=Mode.INTERSECT)
with Locations(p.faces(Select.LAST).filter_by(GeomType.PLANE).sort_by(Axis.Z)[-1]):
CounterBoreHole(0.625 / 2, 1.25 / 2, 0.5)
with BuildSketch(Plane.YZ) as rib:
with Locations((0, 0.25)):
Trapezoid(0.5, 1, 90 - 8, align=(Align.CENTER, Align.MIN))
full_round(rib.edges().sort_by(SortBy.LENGTH)[0])
extrude(amount=4.25 / 2)
mirror(about=Plane.YZ)
part = scale(p.part, IN)
got_mass = part.volume * 7800e-6 / LB
want_mass = 3.923
tolerance = 0.02
delta = abs(got_mass - want_mass)
print(f"Mass: {got_mass:0.1f} lbs")
assert delta < tolerance, f"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}"
show(p)
:::
:::
:::
```{=html}
```
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tutorial_surface_modeling.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tutorial_surface_modeling.xhtml#surface-modeling .section}
# Surface Modeling
Surface modeling refers to the direct creation and manipulation of the skin of a 3D object---its bounding faces---rather than starting from volumetric primitives or solid operations.
Instead of defining a shape by extruding or revolving a 2D profile to fill a volume, surface modeling focuses on building the individual curved or planar faces that together define the outer boundary of a part. This approach allows for precise control of complex freeform geometry such as aerodynamic surfaces, boat hulls, or organic transitions that cannot easily be expressed with simple parametric solids.
In build123d, as in other CAD kernels based on BREP (Boundary Representation) modeling, all solids are ultimately defined by their boundaries: a hierarchy of faces, edges, and vertices. Each face represents a finite patch of a geometric surface (plane, cylinder, Bézier patch, etc.) bounded by one or more edge loops or wires. When adjacent faces share edges consistently and close into a continuous boundary, they form a manifold [`Shell`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shell "topology.Shell"){.hxr-hoverxref .hxr-tooltip .reference .internal}---the watertight surface of a volume. If this shell is properly oriented and encloses a finite region of space, the model becomes a solid.
Surface modeling therefore operates at the most fundamental level of BREP construction. Rather than relying on higher-level modeling operations to implicitly generate faces, it allows you to construct and connect those faces explicitly. This provides a path to build geometry that blends analytical and freeform shapes seamlessly, with full control over continuity, tangency, and curvature across boundaries.
This section provides: - A concise overview of surface‑building tools in build123d - Hands‑on tutorials, from fundamentals to advanced techniques like Gordon surfaces
Available surface methods
Methods on [`Face`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} for creating non‑planar surfaces:
- [`make_bezier_surface()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_bezier_surface "topology.Face.make_bezier_surface"){.hxr-hoverxref .hxr-tooltip .reference .internal}
- [`make_gordon_surface()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_gordon_surface "topology.Face.make_gordon_surface"){.hxr-hoverxref .hxr-tooltip .reference .internal}
- [`make_surface()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_surface "topology.Face.make_surface"){.hxr-hoverxref .hxr-tooltip .reference .internal}
- [`make_surface_from_array_of_points()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_surface_from_array_of_points "topology.Face.make_surface_from_array_of_points"){.hxr-hoverxref .hxr-tooltip .reference .internal}
- [`make_surface_from_curves()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_surface_from_curves "topology.Face.make_surface_from_curves"){.hxr-hoverxref .hxr-tooltip .reference .internal}
- [`make_surface_patch()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_surface_patch "topology.Face.make_surface_patch"){.hxr-hoverxref .hxr-tooltip .reference .internal}
::: {.admonition .note}
Note
Surface modeling is an advanced technique. Robust results usually come from reusing the same [`Edge`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} objects across adjacent faces and ensuring the final [`Shell`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shell "topology.Shell"){.hxr-hoverxref .hxr-tooltip .reference .internal} is *water‑tight* or *manifold* (no gaps).
:::
::: {.toctree-wrapper .compound}
- [Tutorial: Heart Token (Basics)](#tutorial_surface_heart_token.xhtml){.reference .internal}
- [Tutorial: Spitfire Wing with Gordon Surface](#tutorial_spitfire_wing_gordon.xhtml){.reference .internal}
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tutorial_surface_heart_token.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tutorial_surface_heart_token.xhtml#tutorial-heart-token-basics .section}
# Tutorial: Heart Token (Basics)
This hands‑on tutorial introduces the fundamentals of surface modeling by building a heart‑shaped token from a small set of non‑planar faces. We'll create non‑planar surfaces, mirror them, add side faces, and assemble a closed shell into a solid.
As described in the ``{=html}topology\_``{=html} section, a BREP model consists of vertices, edges, faces, and other elements that define the boundary of an object. When creating objects with non-planar faces, it is often more convenient to explicitly create the boundary faces of the object. To illustrate this process, we will create the following game token:
```{=html}
```
``{=html}``{=html}
Useful [`Face`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} creation methods include [`make_surface()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_surface "topology.Face.make_surface"){.hxr-hoverxref .hxr-tooltip .reference .internal}, [`make_bezier_surface()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_bezier_surface "topology.Face.make_bezier_surface"){.hxr-hoverxref .hxr-tooltip .reference .internal}, and [`make_surface_from_array_of_points()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_surface_from_array_of_points "topology.Face.make_surface_from_array_of_points"){.hxr-hoverxref .hxr-tooltip .reference .internal}. See the [[Surface Modeling]{.doc}](#tutorial_surface_modeling.xhtml){.reference .internal} overview for the full list.
In this case, we'll use the `make_surface`{.docutils .literal .notranslate} method, providing it with the edges that define the perimeter of the surface and a central point on that surface.
To create the perimeter, we'll define the perimeter edges. Since the heart is symmetric, we'll only create half of its surface here:
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
# Create the edges of one half the heart surface
l1 = JernArc((0, 0), (1, 1.4), 40, -17)
l2 = JernArc(l1 @ 1, l1 % 1, 4.5, 175)
l3 = IntersectingLine(l2 @ 1, l2 % 1, other=Edge.make_line((0, 0), (0, 20)))
l4 = ThreePointArc(l3 @ 1, (0, 0, 1.5) + (l3 @ 1 + l1 @ 0) / 2, l1 @ 0)
heart_half = Wire([l1, l2, l3, l4])
:::
:::
Note that `l4`{.docutils .literal .notranslate} is not in the same plane as the other lines; it defines the center line of the heart and archs up off `Plane.XY`{.docutils .literal .notranslate}.
{.align-center}
In preparation for creating the surface, we'll define a point on the surface:
::: {.highlight-build123d .notranslate}
::: highlight
# Create a point elevated off the center
surface_pnt = l2.arc_center + (0, 0, 1.5)
:::
:::
We will then use this point to create a non-planar `Face`{.docutils .literal .notranslate}:
::: {.highlight-build123d .notranslate}
::: highlight
# Create the surface from the edges and point
top_right_surface = Pos(Z=0.5) * -Face.make_surface(heart_half, [surface_pnt])
:::
:::
{.align-center}
Note that the surface was raised up by 0.5 using an Algebra expression with Pos. Also, note that the `-`{.docutils .literal .notranslate} in front of `Face`{.docutils .literal .notranslate} simply flips the face normal so that the colored side is up, which isn't necessary but helps with viewing.
Now that one half of the top of the heart has been created, the remainder of the top and bottom can be created by mirroring:
::: {.highlight-build123d .notranslate}
::: highlight
# Use the mirror method to create the other top and bottom surfaces
top_left_surface = top_right_surface.mirror(Plane.YZ)
bottom_right_surface = top_right_surface.mirror(Plane.XY)
bottom_left_surface = -top_left_surface.mirror(Plane.XY)
:::
:::
The sides of the heart are going to be created by extruding the outside of the perimeter as follows:
::: {.highlight-build123d .notranslate}
::: highlight
# Create the left and right sides
left_wire = Wire([l3, l2, l1])
left_side = Pos(Z=-0.5) * Shell.extrude(left_wire, (0, 0, 1))
right_side = left_side.mirror(Plane.YZ)
:::
:::
{.align-center}
With the top, bottom, and sides, the complete boundary of the object is defined. We can now put them together, first into a [`Shell`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shell "topology.Shell"){.hxr-hoverxref .hxr-tooltip .reference .internal} and then into a [`Solid`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}:
::: {.highlight-build123d .notranslate}
::: highlight
# Put all of the faces together into a Shell/Solid
heart = Solid(
Shell(
[
top_right_surface,
top_left_surface,
bottom_right_surface,
bottom_left_surface,
left_side,
right_side,
]
)
)
:::
:::
{.align-center}
::: {.admonition .note}
Note
When creating a Solid from a Shell, the Shell must be "water-tight," meaning it should have no holes. For objects with complex Edges, it's best practice to reuse Edges in adjoining Faces whenever possible to avoid slight mismatches that can create openings.
:::
Finally, we'll create the frame around the heart as a simple extrusion of a planar shape defined by the perimeter of the heart and merge all of the components together:
::: {.highlight-build123d .notranslate}
::: highlight
# Build a frame around the heart
with BuildPart() as heart_token:
with BuildSketch() as outline:
with BuildLine():
add(l1)
add(l2)
add(l3)
Line(l3 @ 1, l1 @ 0)
make_face()
mirror(about=Plane.YZ)
center = outline.sketch
offset(amount=2, kind=Kind.INTERSECTION)
add(center, mode=Mode.SUBTRACT)
extrude(amount=2, both=True)
add(heart)
heart_token.part.color = "Red"
show(heart_token)
:::
:::
Note that an additional planar line is used to close `l1`{.docutils .literal .notranslate} and `l3`{.docutils .literal .notranslate} so a `Face`{.docutils .literal .notranslate} can be created. The [`offset()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.offset "operations_generic.offset"){.hxr-hoverxref .hxr-tooltip .reference .internal} function defines the outside of the frame as a constant distance from the heart itself.
::: {#tutorial_surface_heart_token.xhtml#summary .section}
## Summary
In this tutorial, we've explored surface modeling techniques to create a non-planar heart-shaped object using build123d. By utilizing methods from the [`Face`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} class, such as [`make_surface()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_surface "topology.Face.make_surface"){.hxr-hoverxref .hxr-tooltip .reference .internal}, we constructed the perimeter and central point of the surface. We then assembled the complete boundary of the object by creating the top, bottom, and sides, and combined them into a [`Shell`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shell "topology.Shell"){.hxr-hoverxref .hxr-tooltip .reference .internal} and eventually a [`Solid`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}. Finally, we added a frame around the heart using the [`offset()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.offset "operations_generic.offset"){.hxr-hoverxref .hxr-tooltip .reference .internal} function to maintain a constant distance from the heart.
:::
::: {#tutorial_surface_heart_token.xhtml#next-steps .section}
## Next steps
Continue to [[Tutorial: Spitfire Wing with Gordon Surface]{.doc}](#tutorial_spitfire_wing_gordon.xhtml){.reference .internal} for an advanced example using [`make_gordon_surface()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_gordon_surface "topology.Face.make_gordon_surface"){.hxr-hoverxref .hxr-tooltip .reference .internal} to create a Supermarine Spitfire wing.
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tutorial_spitfire_wing_gordon.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tutorial_spitfire_wing_gordon.xhtml#tutorial-spitfire-wing-with-gordon-surface .section}
# Tutorial: Spitfire Wing with Gordon Surface
In this advanced tutorial we construct a Supermarine Spitfire wing as a [`make_gordon_surface()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Face.make_gordon_surface "topology.Face.make_gordon_surface"){.hxr-hoverxref .hxr-tooltip .reference .internal}---a powerful technique for surfacing from intersecting *profiles* and *guides*. A Gordon surface blends a grid of curves into a smooth, coherent surface as long as the profiles and guides intersect consistently.
::: {.admonition .note}
Note
Gordon surfaces work best when *each profile intersects each guide exactly once*, producing a well‑formed curve network.
:::
::: {#tutorial_spitfire_wing_gordon.xhtml#overview .section}
## Overview
We will:
1. Define overall wing dimensions and elliptic leading/trailing edge guide curves
2. Sample the guides to size the root and tip airfoils (different NACA profiles)
3. Build the Gordon surface from the airfoil *profiles* and wing‑edge *guides*
4. Close the root with a planar face and build the final [`Solid`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
``{=html}``{=html}
:::
::: {#tutorial_spitfire_wing_gordon.xhtml#step-1-dimensions-and-guide-curves .section}
## Step 1 --- Dimensions and guide curves
We model a single wing (half‑span), with an elliptic leading and trailing edge. These two edges act as the *guides* for the Gordon surface.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
wing_span = 36 * FT + 10 * IN
wing_leading = 2.5 * FT
wing_trailing = wing_span / 4 - wing_leading
wing_leading_fraction = wing_leading / (wing_leading + wing_trailing)
wing_tip_section = wing_span / 2 - 1 * IN # distance from root to last section
# Create leading and trailing edges
leading_edge = EllipticalCenterArc(
(0, 0), wing_span / 2, wing_leading, start_angle=270, end_angle=360
)
trailing_edge = EllipticalCenterArc(
(0, 0), wing_span / 2, wing_trailing, start_angle=0, end_angle=90
)
:::
:::
:::
::: {#tutorial_spitfire_wing_gordon.xhtml#step-2-root-and-tip-airfoil-sizing .section}
## Step 2 --- Root and tip airfoil sizing
We intersect the guides with planes normal to the span to size the airfoil sections. The resulting chord lengths define uniform scales for each airfoil curve.
::: {.highlight-build123d .notranslate}
::: highlight
# Calculate the airfoil sizes from the leading/trailing edges
airfoil_sizes = []
for i in [0, 1]:
tip_axis = Axis(i * (wing_tip_section, 0, 0), (0, 1, 0))
leading_pnt = leading_edge.intersect(tip_axis)[0]
trailing_pnt = trailing_edge.intersect(tip_axis)[0]
airfoil_sizes.append(trailing_pnt.Y - leading_pnt.Y)
:::
:::
:::
::: {#tutorial_spitfire_wing_gordon.xhtml#step-3-build-airfoil-profiles-root-and-tip .section}
## Step 3 --- Build airfoil profiles (root and tip)
We place two different NACA airfoils on `Plane.YZ`{.xref .py .py-data .docutils .literal .notranslate}---with the airfoil origins shifted so the leading edge fraction is aligned---then scale to the chord lengths from Step 2.
::: {.highlight-build123d .notranslate}
::: highlight
# Create the root and tip airfoils - note that they are different NACA profiles
airfoil_root = Plane.YZ * scale(
Airfoil("2213").translate((-wing_leading_fraction, 0, 0)), airfoil_sizes[0]
)
airfoil_tip = (
Plane.YZ
* Pos(Z=wing_tip_section)
* scale(Airfoil("2205").translate((-wing_leading_fraction, 0, 0)), airfoil_sizes[1])
)
:::
:::
:::
::: {#tutorial_spitfire_wing_gordon.xhtml#step-4-gordon-surface-construction .section}
## Step 4 --- Gordon surface construction
A Gordon surface needs *profiles* and *guides*. Here the airfoil edges are the profiles; the elliptic edges are the guides. We also add the wing tip section so the profile grid closes at the tip.
::: {.highlight-build123d .notranslate}
::: highlight
# Create the Gordon surface profiles and guides
profiles = airfoil_root.edges() + airfoil_tip.edges()
profiles.append(leading_edge @ 1) # wing tip
guides = [leading_edge, trailing_edge]
# Create the wing surface as a Gordon Surface
wing_surface = -Face.make_gordon_surface(profiles, guides)
# Create the root of the wing
wing_root = -Face(Wire(wing_surface.edges().filter_by(Edge.is_closed)))
:::
:::
{.align-center}
:::
::: {#tutorial_spitfire_wing_gordon.xhtml#step-5-cap-the-root-and-create-the-solid .section}
## Step 5 --- Cap the root and create the solid
We extract the closed root edge loop, make a planar cap, and form a solid shell.
::: {.highlight-build123d .notranslate}
::: highlight
# Create the wing Solid
wing = Solid(Shell([wing_surface, wing_root]))
wing.color = 0x99A3B9 # Azure Blue
show(wing)
:::
:::
{.align-center}
::: {#tutorial_spitfire_wing_gordon.xhtml#tips-for-robust-gordon-surfaces .section}
### Tips for robust Gordon surfaces
- Ensure each profile intersects each guide once and only once
- Keep the curve network coherent (no duplicated or missing intersections)
- When possible, reuse the same [`Edge`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} objects across adjacent faces
:::
:::
::: {#tutorial_spitfire_wing_gordon.xhtml#complete-listing .section}
## Complete listing
For convenience, here is the full script in one block:
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
wing_span = 36 * FT + 10 * IN
wing_leading = 2.5 * FT
wing_trailing = wing_span / 4 - wing_leading
wing_leading_fraction = wing_leading / (wing_leading + wing_trailing)
wing_tip_section = wing_span / 2 - 1 * IN # distance from root to last section
# Create leading and trailing edges
leading_edge = EllipticalCenterArc(
(0, 0), wing_span / 2, wing_leading, start_angle=270, end_angle=360
)
trailing_edge = EllipticalCenterArc(
(0, 0), wing_span / 2, wing_trailing, start_angle=0, end_angle=90
)
# [AirfoilSizes]
# Calculate the airfoil sizes from the leading/trailing edges
airfoil_sizes = []
for i in [0, 1]:
tip_axis = Axis(i * (wing_tip_section, 0, 0), (0, 1, 0))
leading_pnt = leading_edge.intersect(tip_axis)[0]
trailing_pnt = trailing_edge.intersect(tip_axis)[0]
airfoil_sizes.append(trailing_pnt.Y - leading_pnt.Y)
# [Airfoils]
# Create the root and tip airfoils - note that they are different NACA profiles
airfoil_root = Plane.YZ * scale(
Airfoil("2213").translate((-wing_leading_fraction, 0, 0)), airfoil_sizes[0]
)
airfoil_tip = (
Plane.YZ
* Pos(Z=wing_tip_section)
* scale(Airfoil("2205").translate((-wing_leading_fraction, 0, 0)), airfoil_sizes[1])
)
# [Profiles]
# Create the Gordon surface profiles and guides
profiles = airfoil_root.edges() + airfoil_tip.edges()
profiles.append(leading_edge @ 1) # wing tip
guides = [leading_edge, trailing_edge]
# Create the wing surface as a Gordon Surface
wing_surface = -Face.make_gordon_surface(profiles, guides)
# Create the root of the wing
wing_root = -Face(Wire(wing_surface.edges().filter_by(Edge.is_closed)))
# [Solid]
# Create the wing Solid
wing = Solid(Shell([wing_surface, wing_root]))
wing.color = 0x99A3B9 # Azure Blue
show(wing)
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tech_drawing_tutorial.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tech_drawing_tutorial.xhtml#technical-drawing-tutorial .section}
[]{#tech_drawing_tutorial.xhtml#tech-drawing-tutorial}
# Technical Drawing Tutorial
This example demonstrates how to generate a standard technical drawing of a 3D part using ``{=html}build123d``{=html}. It creates orthographic and isometric views of a Nema 23 stepper motor and exports the result as an SVG file suitable for printing or inspection.
::: {#tech_drawing_tutorial.xhtml#overview .section}
## Overview
A technical drawing represents a 3D object in 2D using a series of standardized views. These include:
- **Plan (Top View)** -- as seen from directly above (Z-axis down)
- **Front Elevation** -- looking at the object head-on (Y-axis forward)
- **Side Elevation (Right Side)** -- viewed from the right (X-axis)
- **Isometric Projection** -- a 3D perspective view to help visualize depth
Each view is aligned to a position on the page and optionally scaled or annotated.
:::
::: {#tech_drawing_tutorial.xhtml#how-it-works .section}
## How It Works
The script uses the ``{=html}project_to_viewport``{=html} method to project the 3D part geometry into 2D. A helper function, ``{=html}project_to_2d``{=html}, sets up the viewport (camera origin and up direction) and places the result onto a virtual drawing sheet.
The steps involved are:
1. Load or construct a 3D part (in this case, a stepper motor).
2. Define a ``{=html}TechnicalDrawing``{=html} border and title block using A4 page size.
3. Generate each of the standard views and apply transformations to place them.
4. Add dimensions using ``{=html}ExtensionLine``{=html} and labels using ``{=html}Text``{=html}.
5. Export the drawing using ``{=html}ExportSVG``{=html}, separating visible and hidden edges by layer and style.
:::
::: {#tech_drawing_tutorial.xhtml#result .section}
## Result
{.align-center style="width: 80%;"}
:::
::: {#tech_drawing_tutorial.xhtml#try-it-yourself .section}
## Try It Yourself
You can modify the script to:
- Replace the part with your own ``{=html}Part``{=html} model
- Adjust camera angles and scale
- Add other views (bottom, rear)
- Enhance with more labels and dimensions
:::
::: {#tech_drawing_tutorial.xhtml#code .section}
## Code
::: {.highlight-build123d .notranslate}
::: highlight
from datetime import date
from bd_warehouse.open_builds import StepperMotor
from build123d import *
from ocp_vscode import show
def project_to_2d(
part: Part,
viewport_origin: VectorLike,
viewport_up: VectorLike,
page_origin: VectorLike,
scale_factor: float = 1.0,
) -> tuple[ShapeList[Edge], ShapeList[Edge]]:
"""project_to_2d
Helper function to generate 2d views translated on the 2d page.
Args:
part (Part): 3d object
viewport_origin (VectorLike): location of viewport
viewport_up (VectorLike): direction of the viewport Y axis
page_origin (VectorLike): center of 2d object on page
scale_factor (float, optional): part scalar. Defaults to 1.0.
Returns:
tuple[ShapeList[Edge], ShapeList[Edge]]: visible & hidden edges
"""
scaled_part = part if scale_factor == 1.0 else scale(part, scale_factor)
visible, hidden = scaled_part.project_to_viewport(
viewport_origin, viewport_up, look_at=(0, 0, 0)
)
visible = [Pos(*page_origin) * e for e in visible]
hidden = [Pos(*page_origin) * e for e in hidden]
return ShapeList(visible), ShapeList(hidden)
# The object that appearing in the drawing
stepper: Part = StepperMotor("Nema23")
# Create a standard technical drawing border on A4 paper
border = TechnicalDrawing(
designed_by="build123d",
design_date=date.fromisoformat("2025-05-23"),
page_size=PageSize.A4,
title="Nema 23 Stepper",
sub_title="Units: mm",
drawing_number="BD-1",
sheet_number=1,
drawing_scale=1,
)
page_size = border.bounding_box().size
# Specify the drafting options for extension lines
drafting_options = Draft(font_size=3.5, decimal_precision=1, display_units=False)
# Lists used to store the 2d visible and hidden lines
visible_lines, hidden_lines = [], []
# Isometric Projection - A 3D view where the part is rotated to reveal three
# dimensions equally.
iso_v, iso_h = project_to_2d(
stepper,
(100, 100, 100),
(0, 0, 1),
page_size * 0.3,
0.75,
)
visible_lines.extend(iso_v)
hidden_lines.extend(iso_h)
# Plan View (Top) - The view from directly above the part (looking down along
# the Z-axis).
vis, _ = project_to_2d(
stepper,
(0, 0, 100),
(0, 1, 0),
(page_size.X * -0.3, page_size.Y * 0.25),
)
visible_lines.extend(vis)
# Dimension the top of the stepper
top_bbox = Curve(vis).bounding_box()
perimeter = Pos(*top_bbox.center()) * Rectangle(top_bbox.size.X, top_bbox.size.Y)
d1 = ExtensionLine(
border=perimeter.edges().sort_by(Axis.X)[-1], offset=1 * CM, draft=drafting_options
)
d2 = ExtensionLine(
border=perimeter.edges().sort_by(Axis.Y)[0], offset=1 * CM, draft=drafting_options
)
# Add a label
l1 = Text("Plan View", 6)
l1.position = vis.sort_by(Axis.Y)[-1].center() + (0, 5 * MM)
# Front Elevation - The primary view, typically looking along the Y-axis,
# showing the height.
vis, _ = project_to_2d(
stepper,
(0, -100, 0),
(0, 0, 1),
(page_size.X * -0.3, page_size.Y * -0.125),
)
visible_lines.extend(vis)
d3 = ExtensionLine(
border=vis.sort_by(Axis.Y)[-1], offset=-5 * MM, draft=drafting_options
)
l2 = Text("Front Elevation", 6)
l2.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM)
# Side Elevation - Often refers to the Right Side View, looking along the X-axis.
vis, _ = project_to_2d(
stepper,
(100, 0, 0),
(0, 0, 1),
(0, page_size.Y * 0.15),
)
visible_lines.extend(vis)
side_bbox = Curve(vis).bounding_box()
shaft_top_corner = vis.edges().sort_by(Axis.Y)[-1].vertices().sort_by(Axis.X)[-1]
body_bottom_corner = (side_bbox.max.X, side_bbox.min.Y)
d4 = ExtensionLine(
border=(shaft_top_corner, body_bottom_corner),
offset=-(side_bbox.max.X - shaft_top_corner.X) - 1 * CM, # offset to outside view.
measurement_direction=(0, 1, 0),
draft=drafting_options,
)
l3 = Text("Side Elevation", 6)
l3.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM)
# Initialize the SVG exporter
exporter = ExportSVG(unit=Unit.MM)
# Define visible and hidden line layers
exporter.add_layer("Visible")
exporter.add_layer("Hidden", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)
# Add the objects to the appropriate layer
exporter.add_shape(visible_lines, layer="Visible")
exporter.add_shape(hidden_lines, layer="Hidden")
exporter.add_shape(border, layer="Visible")
exporter.add_shape([d1, d2, d3, d4], layer="Visible")
exporter.add_shape([l1, l2, l3], layer="Visible")
# Write the file
exporter.write(f"assets/stepper_drawing.svg")
show(border, visible_lines, d1, d2, d3, d4, l1, l2, l3)
:::
:::
:::
::: {#tech_drawing_tutorial.xhtml#dependencies .section}
## Dependencies
This example depends on the following packages:
- ``{=html}build123d``{=html}
- ``{=html}bd_warehouse``{=html} (for the ``{=html}StepperMotor``{=html} part)
- ``{=html}ocp_vscode``{=html} (for local preview)
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#objects.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#objects.xhtml#objects .section}
# Objects
Objects are Python classes that take parameters as inputs and create 1D, 2D or 3D Shapes. For example, a [`Torus`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Torus "objects_part.Torus"){.hxr-hoverxref .hxr-tooltip .reference .internal} is defined by a major and minor radii. In Builder mode, objects are positioned with `Locations`{.docutils .literal .notranslate} while in Algebra mode, objects are positioned with the `*`{.docutils .literal .notranslate} operator and shown in these examples:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as disk:
with BuildSketch():
Circle(a)
with Locations((b, 0.0)):
Rectangle(c, c, mode=Mode.SUBTRACT)
with Locations((0, b)):
Circle(d, mode=Mode.SUBTRACT)
extrude(amount=c)
:::
:::
::: {.highlight-build123d .notranslate}
::: highlight
sketch = Circle(a) - Pos(b, 0.0) * Rectangle(c, c) - Pos(0.0, b) * Circle(d)
disk = extrude(sketch, c)
:::
:::
The following sections describe the 1D, 2D and 3D objects:
::: {#objects.xhtml#align .section}
## Align
2D/Sketch and 3D/Part objects can be aligned relative to themselves, either centered, or justified right or left of each Axis. The following diagram shows how this alignment works in 2D:
{.align-center}
For example:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch():
Circle(1, align=(Align.MIN, Align.MIN))
:::
:::
creates a circle who's minimal X and Y values are on the X and Y axis and is located in the top right corner. The `Align`{.docutils .literal .notranslate} enum has values: `MIN`{.docutils .literal .notranslate}, `CENTER`{.docutils .literal .notranslate} and `MAX`{.docutils .literal .notranslate}.
In 3D the `align`{.docutils .literal .notranslate} parameter also contains a Z align value but otherwise works in the same way.
Note that the `align`{.docutils .literal .notranslate} will also accept a single `Align`{.docutils .literal .notranslate} value which will be used on all axes - as shown here:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch():
Circle(1, align=Align.MIN)
:::
:::
:::
::: {#objects.xhtml#mode .section}
## Mode
With the Builder API the `mode`{.docutils .literal .notranslate} parameter controls how objects are combined with lines, sketches, or parts under construction. The `Mode`{.docutils .literal .notranslate} enum has values:
- `ADD`{.docutils .literal .notranslate}: fuse this object to the object under construction
- `SUBTRACT`{.docutils .literal .notranslate}: cut this object from the object under construction
- `INTERSECT`{.docutils .literal .notranslate}: intersect this object with the object under construction
- `REPLACE`{.docutils .literal .notranslate}: replace the object under construction with this object
- `PRIVATE`{.docutils .literal .notranslate}: don't interact with the object under construction at all
The Algebra API doesn't use the `mode`{.docutils .literal .notranslate} parameter - users combine objects with operators.
:::
::: {#objects.xhtml#d-objects .section}
## 1D Objects
The following objects all can be used in BuildLine contexts. Note that 1D objects are not affected by `Locations`{.docutils .literal .notranslate} in Builder mode.
::: {.sd-container-fluid .sd-sphinx-override .sd-mb-4 .docutils}
::: {.sd-row .sd-row-cols-3 .sd-row-cols-xs-3 .sd-row-cols-sm-3 .sd-row-cols-md-3 .sd-row-cols-lg-3 .docutils}
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Airfoil`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Airfoil "objects_curve.Airfoil"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Airfoil described by 4 digit NACA profile
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Bezier`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Bezier "objects_curve.Bezier"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Curve defined by control points and weights
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`BlendCurve`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.BlendCurve "objects_curve.BlendCurve"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Curve blending curvature of two curves
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`CenterArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.CenterArc "objects_curve.CenterArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc defined by center, radius, & angles
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`DoubleTangentArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.DoubleTangentArc "objects_curve.DoubleTangentArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc defined by point/tangent pair & other curve
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`EllipticalCenterArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.EllipticalCenterArc "objects_curve.EllipticalCenterArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Elliptical arc defined by center, radii & angles
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`ParabolicCenterArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.ParabolicCenterArc "objects_curve.ParabolicCenterArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Parabolic arc defined by vertex, focal length & angles
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`HyperbolicCenterArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.HyperbolicCenterArc "objects_curve.HyperbolicCenterArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Hyperbolic arc defined by center, radii & angles
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`FilletPolyline`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.FilletPolyline "objects_curve.FilletPolyline"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Polyline with filleted corners defined by pts and radius
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Helix`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Helix "objects_curve.Helix"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Helix defined pitch, radius and height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`IntersectingLine`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.IntersectingLine "objects_curve.IntersectingLine"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Intersecting line defined by start, direction & other line
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`JernArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.JernArc "objects_curve.JernArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc define by start point, tangent, radius and angle
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Line`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Line "objects_curve.Line"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Line defined by end points
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`PolarLine`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.PolarLine "objects_curve.PolarLine"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Line defined by start, angle and length
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Polyline`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Polyline "objects_curve.Polyline"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Multiple line segments defined by points
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`RadiusArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.RadiusArc "objects_curve.RadiusArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc defined by two points and a radius
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`SagittaArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.SagittaArc "objects_curve.SagittaArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc defined by two points and a sagitta
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Spline`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.Spline "objects_curve.Spline"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Curve define by points
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`TangentArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.TangentArc "objects_curve.TangentArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc defined by two points and a tangent
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`ThreePointArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.ThreePointArc "objects_curve.ThreePointArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc defined by three points
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`ArcArcTangentLine`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.ArcArcTangentLine "objects_curve.ArcArcTangentLine"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Line tangent defined by two arcs
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`ArcArcTangentArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.ArcArcTangentArc "objects_curve.ArcArcTangentArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc tangent defined by two arcs
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`PointArcTangentLine`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.PointArcTangentLine "objects_curve.PointArcTangentLine"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Line tangent defined by a point and arc
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`PointArcTangentArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.PointArcTangentArc "objects_curve.PointArcTangentArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arc tangent defined by a point, direction, and arc
:::
:::
:::
:::
:::
::: {#objects.xhtml#module-objects_curve .section}
[]{#objects.xhtml#reference}
### Reference
*[class]{.pre}[ ]{.w}*[[BaseLineObject]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[curve:]{.pre} [\~build123d.topology.one_d.Wire]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: BaseLineObject specialized for Wire.
Parameters[:]{.colon}
: - **curve** ([*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- wire to create
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Airfoil]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[airfoil_code:]{.pre} [str]{.pre}]{.n}*, *[[n_points:]{.pre} [int]{.pre} [=]{.pre} [50]{.pre}]{.n}*, *[[finite_te:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Create an airfoil described by a 4-digit (or fractional) NACA airfoil (e.g. '2412' or '2213.323').
The NACA four-digit wing sections define the airfoil_code by: - First digit describing maximum camber as percentage of the chord. - Second digit describing the distance of maximum camber from the airfoil leading edge in tenths of the chord. - Last two digits describing maximum thickness of the airfoil as percent of the chord.
Parameters[:]{.colon}
: - **airfoil_code** -- str The NACA 4-digit (or fractional) airfoil code (e.g. '2213.323').
- **n_points** -- int Number of points per upper/lower surface.
- **finite_te** -- bool If True, enforces a finite trailing edge (default False).
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
*[property]{.pre}[ ]{.w}*[[camber_line]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Edge]{.pre}*
: Camber line of the airfoil as an Edge.
[[camber_pos]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[float]{.pre}*
: Chordwise position of max camber (0--1)
[[code]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[str]{.pre}*
: NACA code string (e.g. "2412")
[[finite_te]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[bool]{.pre}*
: If True, trailing edge is finite
[[max_camber]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[float]{.pre}*
: Maximum camber as fraction of chord
*[static]{.pre}[ ]{.w}*[[parse_naca4]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[value]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[float]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Parse NACA 4-digit (or fractional) airfoil code into parameters.
[[thickness]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[float]{.pre}*
: Maximum thickness as fraction of chord
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Bezier]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[\*cntl_pnts:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [weights:]{.pre} [list\[float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Bezier Curve
Create a non-rational bezier curve defined by a sequence of points and include optional weights to create a rational bezier curve. The number of weights must match the number of control points.
Parameters[:]{.colon}
: - **cntl_pnts** (*sequence\[VectorLike\]*) -- points defining the curve
- **weights** (*list\[float\],* *optional*) -- control point weights. Defaults to None
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[BlendCurve]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[curve0:]{.pre} [\~build123d.topology.one_d.Edge,]{.pre} [curve1:]{.pre} [\~build123d.topology.one_d.Edge,]{.pre} [continuity:]{.pre} [\~build123d.build_enums.ContinuityLevel]{.pre} [=]{.pre} [ContinuityLevel.C2,]{.pre} [end_points:]{.pre} [tuple\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [tangent_scalars:]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: BlendCurve
Create a smooth Bézier-based transition curve between two existing edges.
The blend is constructed as a cubic (C1) or quintic (C2) Bézier curve whose control points are determined from the position, first derivative, and (for C2) second derivative of the input curves at the chosen endpoints. Optional scalar multipliers can be applied to the endpoint tangents to control the "tension" of the blend.
Parameters[:]{.colon}
: - **curve0** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- First curve to blend from.
- **curve1** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- Second curve to blend to.
- **continuity** (*ContinuityLevel,* *optional*) -- Desired geometric continuity at the join: - ContinuityLevel.C0: position match only (straight line) - ContinuityLevel.C1: match position and tangent direction (cubic Bézier) - ContinuityLevel.C2: match position, tangent, and curvature (quintic Bézier) Defaults to ContinuityLevel.C2.
- **end_points** (*tuple\[VectorLike,* *VectorLike\]* *\|* *None,* *optional*) -- Pair of points specifying the connection points on ``{=html}curve0``{=html} and ``{=html}curve1``{=html}. Each must coincide (within TOLERANCE) with the start or end of the respective curve. If None, the closest pair of endpoints is chosen. Defaults to None.
- **tangent_scalars** (*tuple\[float,* *float\]* *\|* *None,* *optional*) -- Scalar multipliers applied to the first derivatives at the start of ``{=html}curve0``{=html} and the end of ``{=html}curve1``{=html} before computing control points. Useful for adjusting the pull/tension of the blend without altering the base curves. Defaults to (1.0, 1.0).
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Boolean operation mode when used in a BuildLine context. Defaults to Mode.ADD.
Raises[:]{.colon}
: - **ValueError** -- ``{=html}tangent_scalars``{=html} must be a pair of float values.
- **ValueError** -- If specified ``{=html}end_points``{=html} are not coincident with the start or end of their respective curves.
Example
::: {.doctest .highlight-default .notranslate}
::: highlight
>>> blend = BlendCurve(curve_a, curve_b, ContinuityLevel.C1, tangent_scalars=(1.2, 0.8))
>>> show(blend)
:::
:::
```{=html}
```
*[class]{.pre}[ ]{.w}*[[CenterArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[center:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [radius:]{.pre} [float,]{.pre} [start_angle:]{.pre} [float,]{.pre} [arc_size:]{.pre} [float,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Center Arc
Create a circular arc defined by a center point and radius.
Parameters[:]{.colon}
: - **center** (*VectorLike*) -- center point of arc
- **radius** (*float*) -- arc radius
- **start_angle** (*float*) -- arc starting angle from x-axis
- **arc_size** (*float*) -- angular size of arc
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[DoubleTangentArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[pnt:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [tangent:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [other:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire,]{.pre} [keep:]{.pre} [\~build123d.build_enums.Keep]{.pre} [=]{.pre} [\,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Double Tangent Arc
Create a circular arc defined by a point/tangent pair and another line find a tangent to.
The arc specified with TOP or BOTTOM depends on the geometry and isn't predictable.
Contains a solver.
Parameters[:]{.colon}
: - **pnt** (*VectorLike*) -- start point
- **tangent** (*VectorLike*) -- tangent at start point
- **other** ([*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- line object to tangent
- **keep** ([*Keep*](#builder_api_reference.xhtml#build_enums.Keep "build_enums.Keep"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- specify which arc if more than one, TOP or BOTTOM. Defaults to Keep.TOP
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **RunTimeError** -- no double tangent arcs found
```{=html}
```
*[class]{.pre}[ ]{.w}*[[EllipticalCenterArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[center:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [x_radius:]{.pre} [float,]{.pre} [y_radius:]{.pre} [float,]{.pre} [start_angle:]{.pre} [float]{.pre} [=]{.pre} [0.0,]{.pre} [end_angle:]{.pre} [float]{.pre} [=]{.pre} [90.0,]{.pre} [rotation:]{.pre} [float]{.pre} [=]{.pre} [0.0,]{.pre} [angular_direction:]{.pre} [\~build123d.build_enums.AngularDirection]{.pre} [=]{.pre} [\,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Elliptical Center Arc
Create an elliptical arc defined by a center point, x- and y- radii.
Parameters[:]{.colon}
: - **center** (*VectorLike*) -- ellipse center
- **x_radius** (*float*) -- x radius of the ellipse (along the x-axis of plane)
- **y_radius** (*float*) -- y radius of the ellipse (along the y-axis of plane)
- **start_angle** (*float,* *optional*) -- arc start angle from x-axis. Defaults to 0.0
- **end_angle** (*float,* *optional*) -- arc end angle from x-axis. Defaults to 90.0
- **rotation** (*float,* *optional*) -- angle to rotate arc. Defaults to 0.0
- **angular_direction** (*AngularDirection,* *optional*) -- arc direction. Defaults to AngularDirection.COUNTER_CLOCKWISE
- **plane** ([*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- base plane. Defaults to Plane.XY
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[ParabolicCenterArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[vertex:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [focal_length:]{.pre} [float,]{.pre} [start_angle:]{.pre} [float]{.pre} [=]{.pre} [0.0,]{.pre} [end_angle:]{.pre} [float]{.pre} [=]{.pre} [90.0,]{.pre} [rotation:]{.pre} [float]{.pre} [=]{.pre} [0.0,]{.pre} [angular_direction:]{.pre} [\~build123d.build_enums.AngularDirection]{.pre} [=]{.pre} [\,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Parabolic Center Arc
Create a parabolic arc defined by a vertex point and focal length (distance from focus to vertex).
Parameters[:]{.colon}
: - **vertex** (*VectorLike*) -- parabola vertex
- **focal_length** (*float*) -- focal length the parabola (distance from the vertex to focus along the x-axis of plane)
- **start_angle** (*float,* *optional*) -- arc start angle. Defaults to 0.0
- **end_angle** (*float,* *optional*) -- arc end angle. Defaults to 90.0
- **rotation** (*float,* *optional*) -- angle to rotate arc. Defaults to 0.0
- **angular_direction** (*AngularDirection,* *optional*) -- arc direction. Defaults to AngularDirection.COUNTER_CLOCKWISE
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[HyperbolicCenterArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[center:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [x_radius:]{.pre} [float,]{.pre} [y_radius:]{.pre} [float,]{.pre} [start_angle:]{.pre} [float]{.pre} [=]{.pre} [0.0,]{.pre} [end_angle:]{.pre} [float]{.pre} [=]{.pre} [90.0,]{.pre} [rotation:]{.pre} [float]{.pre} [=]{.pre} [0.0,]{.pre} [angular_direction:]{.pre} [\~build123d.build_enums.AngularDirection]{.pre} [=]{.pre} [\,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Hyperbolic Center Arc
Create a hyperbolic arc defined by a center point and focal length (distance from focus to vertex).
Parameters[:]{.colon}
: - **center** (*VectorLike*) -- hyperbola center
- **x_radius** (*float*) -- x radius of the ellipse (along the x-axis of plane)
- **y_radius** (*float*) -- y radius of the ellipse (along the y-axis of plane)
- **start_angle** (*float,* *optional*) -- arc start angle from x-axis. Defaults to 0.0
- **end_angle** (*float,* *optional*) -- arc end angle from x-axis. Defaults to 90.0
- **rotation** (*float,* *optional*) -- angle to rotate arc. Defaults to 0.0
- **angular_direction** (*AngularDirection,* *optional*) -- arc direction. Defaults to AngularDirection.COUNTER_CLOCKWISE
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[FilletPolyline]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[\*pts:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\],]{.pre} [radius:]{.pre} [float]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[float\],]{.pre} [close:]{.pre} [bool]{.pre} [=]{.pre} [False,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Fillet Polyline Create a sequence of straight lines defined by successive points that are filleted to a given radius.
Parameters[:]{.colon}
: - **pts** (*VectorLike* *\|* *Iterable\[VectorLike\]*) -- sequence of two or more points
- **radius** (*float* *\|* *Iterable\[float\]*) -- radius to fillet at each vertex or a single value for all vertices. A radius of 0 will create a sharp corner (vertex without fillet).
- **close** (*bool,* *optional*) -- close end points with extra Edge and corner fillets. Defaults to False
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: - **ValueError** -- Two or more points not provided
- **ValueError** -- radius must be non-negative
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Helix]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[pitch:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[radius:]{.pre} [float]{.pre}]{.n}*, *[[center:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[0)]{.pre}]{.n}*, *[[direction:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[1)]{.pre}]{.n}*, *[[cone_angle:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[lefthand:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Line Object: Helix
Create a helix defined by pitch, height, and radius. The helix may have a taper defined by cone_angle.
If cone_angle is not 0, radius is the initial helix radius at center. cone_angle \> 0 increases the final radius. cone_angle \< 0 decreases the final radius.
Parameters[:]{.colon}
: - **pitch** (*float*) -- distance between loops
- **height** (*float*) -- helix height
- **radius** (*float*) -- helix radius
- **center** (*VectorLike,* *optional*) -- center point. Defaults to (0, 0, 0)
- **direction** (*VectorLike,* *optional*) -- direction of central axis. Defaults to (0, 0, 1)
- **cone_angle** (*float,* *optional*) -- conical angle from direction. Defaults to 0
- **lefthand** (*bool,* *optional*) -- left handed helix. Defaults to False
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[IntersectingLine]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[start:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [direction:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [other:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Intersecting Line Object: Line
Create a straight line defined by a point/direction pair and another line to intersect.
Parameters[:]{.colon}
: - **start** (*VectorLike*) -- start point
- **direction** (*VectorLike*) -- direction to make line
- **other** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- line object to intersect
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[JernArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[start:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [tangent:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [radius:]{.pre} [float,]{.pre} [arc_size:]{.pre} [float,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Jern Arc
Create a circular arc defined by a start point/tangent pair, radius and arc size.
Parameters[:]{.colon}
: - **start** (*VectorLike*) -- start point
- **tangent** (*VectorLike*) -- tangent at start point
- **radius** (*float*) -- arc radius
- **arc_size** (*float*) -- angular size of arc (negative to change direction)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Variables[:]{.colon}
: - **start** ([*Vector*](#direct_api_reference.xhtml#geometry.Vector "geometry.Vector"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- start point
- **end_of_arc** ([*Vector*](#direct_api_reference.xhtml#geometry.Vector "geometry.Vector"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- end point of arc
- **center_point** ([*Vector*](#direct_api_reference.xhtml#geometry.Vector "geometry.Vector"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- center of arc
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Line]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[\*pts:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\],]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Line
Create a straight line defined by two points.
Parameters[:]{.colon}
: - **pts** (*VectorLike* *\|* *Iterable\[VectorLike\]*) -- sequence of two points
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **ValueError** -- Two point not provided
```{=html}
```
*[class]{.pre}[ ]{.w}*[[PolarLine]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[start:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [length:]{.pre} [float,]{.pre} [angle:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [direction:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [length_mode:]{.pre} [\~build123d.build_enums.LengthMode]{.pre} [=]{.pre} [\,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Polar Line
Create a straight line defined by a start point, length, and angle. The length can specify the DIAGONAL, HORIZONTAL, or VERTICAL component of the triangle defined by the angle.
Parameters[:]{.colon}
: - **start** (*VectorLike*) -- start point
- **length** (*float*) -- line length
- **angle** (*float,* *optional*) -- angle from the local x-axis
- **direction** (*VectorLike,* *optional*) -- vector direction to determine angle
- **length_mode** (*LengthMode,* *optional*) -- how length defines the line. Defaults to LengthMode.DIAGONAL
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **ValueError** -- Either angle or direction must be provided
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Polyline]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[\*pts:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\],]{.pre} [close:]{.pre} [bool]{.pre} [=]{.pre} [False,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Polyline
Create a sequence of straight lines defined by successive points.
Parameters[:]{.colon}
: - **pts** (*VectorLike* *\|* *Iterable\[VectorLike\]*) -- sequence of two or more points
- **close** (*bool,* *optional*) -- close by generating an extra Edge. Defaults to False
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **ValueError** -- Two or more points not provided
```{=html}
```
*[class]{.pre}[ ]{.w}*[[RadiusArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[start_point:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [end_point:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [radius:]{.pre} [float,]{.pre} [short_sagitta:]{.pre} [bool]{.pre} [=]{.pre} [True,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Radius Arc
Create a circular arc defined by two points and a radius.
Parameters[:]{.colon}
: - **start_point** (*VectorLike*) -- start point
- **end_point** (*VectorLike*) -- end point
- **radius** (*float*) -- arc radius
- **short_sagitta** (*bool*) -- If True selects the short sagitta (height of arc from chord), else the long sagitta crossing the center. Defaults to True
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **ValueError** -- Insufficient radius to connect end points
```{=html}
```
*[class]{.pre}[ ]{.w}*[[SagittaArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[start_point:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [end_point:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [sagitta:]{.pre} [float,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Sagitta Arc
Create a circular arc defined by two points and the sagitta (height of the arc from chord).
Parameters[:]{.colon}
: - **start_point** (*VectorLike*) -- start point
- **end_point** (*VectorLike*) -- end point
- **sagitta** (*float*) -- arc height from chord between points
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Spline]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[\*pts:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\],]{.pre} [tangents:]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [tangent_scalars:]{.pre} [\~collections.abc.Iterable\[float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [periodic:]{.pre} [bool]{.pre} [=]{.pre} [False,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Spline
Create a spline defined by a sequence of points, optionally constrained by tangents. Tangents and tangent scalars must have length of 2 for only the end points or a length of the number of points.
Parameters[:]{.colon}
: - **pts** (*VectorLike* *\|* *Iterable\[VectorLike\]*) -- sequence of two or more points
- **tangents** (*Iterable\[VectorLike\],* *optional*) -- tangent directions. Defaults to None
- **tangent_scalars** (*Iterable\[float\],* *optional*) -- tangent scales. Defaults to None
- **periodic** (*bool,* *optional*) -- make the spline periodic (closed). Defaults to False
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[TangentArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[\*pts:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\],]{.pre} [tangent:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [tangent_from_first:]{.pre} [bool]{.pre} [=]{.pre} [True,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Tangent Arc
Create a circular arc defined by two points and a tangent.
Parameters[:]{.colon}
: - **pts** (*VectorLike* *\|* *Iterable\[VectorLike\]*) -- sequence of two points
- **tangent** (*VectorLike*) -- tangent to constrain arc
- **tangent_from_first** (*bool,* *optional*) -- apply tangent to first point. Applying tangent to end point will flip the orientation of the arc. Defaults to True
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **ValueError** -- Two points are required
```{=html}
```
*[class]{.pre}[ ]{.w}*[[ThreePointArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[\*pts:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\],]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Three Point Arc
Create a circular arc defined by three points.
Parameters[:]{.colon}
: - **pts** (*VectorLike* *\|* *Iterable\[VectorLike\]*) -- sequence of three points
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **ValueError** -- Three points must be provided
```{=html}
```
*[class]{.pre}[ ]{.w}*[[ArcArcTangentLine]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[start_arc:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre}]{.n}*, *[[end_arc:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre}]{.n}*, *[[side:]{.pre} [\~build123d.build_enums.Side]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[keep:]{.pre} [\~build123d.build_enums.Keep]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Line Object: Arc Arc Tangent Line
Create a straight line tangent to two arcs.
Parameters[:]{.colon}
: - **start_arc** ([*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- starting arc, must be GeomType.CIRCLE
- **end_arc** ([*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- ending arc, must be GeomType.CIRCLE
- **side** (*Side*) -- side of arcs to place tangent arc center, LEFT or RIGHT. Defaults to Side.LEFT
- **keep** ([*Keep*](#builder_api_reference.xhtml#build_enums.Keep "build_enums.Keep"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- which tangent arc to keep, INSIDE or OUTSIDE. Defaults to Keep.INSIDE
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[ArcArcTangentArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[start_arc:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre}]{.n}*, *[[end_arc:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre}]{.n}*, *[[radius:]{.pre} [float]{.pre}]{.n}*, *[[side:]{.pre} [\~build123d.build_enums.Side]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[keep:]{.pre} [\~build123d.build_enums.Keep]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Keep]{.pre}]{.n}*, *[[\~build123d.build_enums.Keep\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[short_sagitta:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Line Object: Arc Arc Tangent Arc
Create an arc tangent to two arcs and a radius.
keep specifies tangent arc position with a Keep pair: (placement, type)
- placement: start_arc is tangent INSIDE or OUTSIDE the tangent arc. BOTH is a special case for overlapping arcs with type INSIDE
- type: tangent arc is INSIDE or OUTSIDE start_arc and end_arc
Parameters[:]{.colon}
: - **start_arc** ([*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- starting arc, must be GeomType.CIRCLE
- **end_arc** ([*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- ending arc, must be GeomType.CIRCLE
- **radius** (*float*) -- radius of tangent arc
- **side** (*Side*) -- side of arcs to place tangent arc center, LEFT or RIGHT. Defaults to Side.LEFT
- **keep** ([*Keep*](#builder_api_reference.xhtml#build_enums.Keep "build_enums.Keep"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Keep*](#builder_api_reference.xhtml#build_enums.Keep "build_enums.Keep"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Keep*](#builder_api_reference.xhtml#build_enums.Keep "build_enums.Keep"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]*) -- which tangent arc to keep, INSIDE or OUTSIDE. Defaults to (Keep.INSIDE, Keep.INSIDE)
- **short_sagitta** (*bool*) -- If True selects the short sagitta (height of arc from chord), else the long sagitta crossing the center. Defaults to True
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
{.align-center}
*[class]{.pre}[ ]{.w}*[[PointArcTangentLine]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[point:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [arc:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire,]{.pre} [side:]{.pre} [\~build123d.build_enums.Side]{.pre} [=]{.pre} [\,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Point Arc Tangent Line
Create a straight, tangent line from a point to a circular arc.
Parameters[:]{.colon}
: - **point** (*VectorLike*) -- intersection point for tangent
- **arc** ([*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- circular arc to tangent, must be GeomType.CIRCLE
- **side** (*Side,* *optional*) -- side of arcs to place tangent arc center, LEFT or RIGHT. Defaults to Side.LEFT
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[PointArcTangentArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[point:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [direction:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [arc:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire,]{.pre} [side:]{.pre} [\~build123d.build_enums.Side]{.pre} [=]{.pre} [\,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Line Object: Point Arc Tangent Arc
Create an arc defined by a point/tangent pair and another line which the other end is tangent to.
Parameters[:]{.colon}
: - **point** (*VectorLike*) -- starting point of tangent arc
- **direction** (*VectorLike*) -- direction at starting point of tangent arc
- **arc** (*Union\[*[*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]*) -- ending arc, must be GeomType.CIRCLE
- **side** (*Side,* *optional*) -- select which arc to keep Defaults to Side.LEFT
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: - **ValueError** -- Arc must have GeomType.CIRCLE
- **ValueError** -- Point is already tangent to arc
- **RuntimeError** -- No tangent arc found
:::
:::
::: {#objects.xhtml#id1 .section}
## 2D Objects
::: {.sd-container-fluid .sd-sphinx-override .sd-mb-4 .docutils}
::: {.sd-row .sd-row-cols-3 .sd-row-cols-xs-3 .sd-row-cols-sm-3 .sd-row-cols-md-3 .sd-row-cols-lg-3 .docutils}
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Arrow`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#drafting.Arrow "drafting.Arrow"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arrow with head and path for shaft
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`ArrowHead`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#drafting.ArrowHead "drafting.ArrowHead"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Arrow head with multiple types
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Circle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Circle "objects_sketch.Circle"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Circle defined by radius
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`DimensionLine`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#drafting.DimensionLine "drafting.DimensionLine"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Dimension line
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Ellipse`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Ellipse "objects_sketch.Ellipse"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Ellipse defined by major and minor radius
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`ExtensionLine`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#drafting.ExtensionLine "drafting.ExtensionLine"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Extension lines for distance or angles
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Polygon`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Polygon "objects_sketch.Polygon"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Polygon defined by points
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Rectangle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Rectangle "objects_sketch.Rectangle"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Rectangle defined by width and height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`RectangleRounded`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.RectangleRounded "objects_sketch.RectangleRounded"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Rectangle with rounded corners defined by width, height, and radius
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`RegularPolygon`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.RegularPolygon "objects_sketch.RegularPolygon"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
RegularPolygon defined by radius and number of sides
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`SlotArc`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.SlotArc "objects_sketch.SlotArc"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
SlotArc defined by arc and height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`SlotCenterPoint`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.SlotCenterPoint "objects_sketch.SlotCenterPoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
SlotCenterPoint defined by two points and a height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`SlotCenterToCenter`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.SlotCenterToCenter "objects_sketch.SlotCenterToCenter"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
SlotCenterToCenter defined by center separation and height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`SlotOverall`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.SlotOverall "objects_sketch.SlotOverall"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
SlotOverall defined by end-to-end length and height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`TechnicalDrawing`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#drafting.TechnicalDrawing "drafting.TechnicalDrawing"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
A technical drawing with descriptions
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Text`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Text "objects_sketch.Text"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Text defined by string and font parameters
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Trapezoid`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Trapezoid "objects_sketch.Trapezoid"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Trapezoid defined by width, height and interior angles
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Triangle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Triangle "objects_sketch.Triangle"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Triangle defined by one side & two other sides or interior angles
:::
:::
:::
:::
:::
::: {#objects.xhtml#id2 .section}
### Reference
*[class]{.pre}[ ]{.w}*[[BaseSketchObject]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[obj:]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Base class for all BuildSketch objects
Parameters[:]{.colon}
: - **face** ([*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- face to create
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to None
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Arrow]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[arrow_size:]{.pre} [float]{.pre}]{.n}*, *[[shaft_path:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre}]{.n}*, *[[shaft_width:]{.pre} [float]{.pre}]{.n}*, *[[head_at_start:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[head_type:]{.pre} [\~build123d.build_enums.HeadType]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Arrow with shaft
Parameters[:]{.colon}
: - **arrow_size** (*float*) -- arrow head tip to tail length
- **shaft_path** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- line describing the shaft shape
- **shaft_width** (*float*) -- line width of shaft
- **head_at_start** (*bool,* *optional*) -- Defaults to True.
- **head_type** (*HeadType,* *optional*) -- arrow head shape. Defaults to HeadType.CURVED.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- \_description\_. Defaults to Mode.ADD.
```{=html}
```
*[class]{.pre}[ ]{.w}*[[ArrowHead]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[size:]{.pre} [float]{.pre}]{.n}*, *[[head_type:]{.pre} [\~build123d.build_enums.HeadType]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: ArrowHead
Parameters[:]{.colon}
: - **size** (*float*) -- tip to tail length
- **head_type** (*HeadType,* *optional*) -- arrow head shape. Defaults to HeadType.CURVED.
- **rotation** (*float,* *optional*) -- rotation in degrees. Defaults to 0.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Circle]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[radius:]{.pre} [float]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Circle
Create a circle defined by radius.
Parameters[:]{.colon}
: - **radius** (*float*) -- circle radius
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[DimensionLine]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[path:]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [list\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [\~build123d.topology.zero_d.Vertex]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]\],]{.pre} [draft:]{.pre} [\~drafting.Draft,]{.pre} [sketch:]{.pre} [\~build123d.topology.composite.Sketch]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [label:]{.pre} [str]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [arrows:]{.pre} [tuple\[bool,]{.pre} [bool\]]{.pre} [=]{.pre} [(True,]{.pre} [True),]{.pre} [tolerance:]{.pre} [float]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [label_angle:]{.pre} [bool]{.pre} [=]{.pre} [False,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Sketch Object: DimensionLine
Create a dimension line typically for internal measurements. Typically used for (but not restricted to) inside dimensions, a dimension line often as arrows on either side of a dimension or label.
There are three options depending on the size of the text and length of the dimension line: Type 1) The label and arrows fit within the length of the path Type 2) The text fit within the path and the arrows go outside Type 3) Neither the text nor the arrows fit within the path
Parameters[:]{.colon}
: - **path** (*PathDescriptor*) -- a very general type of input used to describe the path the dimension line will follow.
- **draft** (*Draft*) -- instance of Draft dataclass
- **sketch** ([*Sketch*](#direct_api_reference.xhtml#topology.Sketch "topology.Sketch"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- the Sketch being created to check for possible overlaps. In builder mode the active Sketch will be used if None is provided.
- **label** (*str,* *optional*) -- a text string which will replace the length (or arc length) that would otherwise be extracted from the provided path. Providing a label is useful when illustrating a parameterized input where the name of an argument is desired not an actual measurement. Defaults to None.
- **arrows** (*tuple\[bool,* *bool\],* *optional*) -- a pair of boolean values controlling the placement of the start and end arrows. Defaults to (True, True).
- **tolerance** (*float* *\|* *tuple\[float,* *float\],* *optional*) -- an optional tolerance value to add to the extracted length value. If a single tolerance value is provided it is shown as ± the provided value while a pair of values are shown as separate + and - values. Defaults to None.
- **label_angle** (*bool,* *optional*) -- a flag indicating that instead of an extracted length value, the size of the circular arc extracted from the path should be displayed in degrees.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
Raises[:]{.colon}
: - **ValueError** -- Only 2 points allowed for dimension lines
- **ValueError** -- No output - no arrows selected
[[dimension]{.pre}]{.sig-name .descname}
: length of the dimension
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Ellipse]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[x_radius:]{.pre} [float]{.pre}]{.n}*, *[[y_radius:]{.pre} [float]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Ellipse
Create an ellipse defined by x- and y- radii.
Parameters[:]{.colon}
: - **x_radius** (*float*) -- x radius of the ellipse (along the x-axis of plane)
- **y_radius** (*float*) -- y radius of the ellipse (along the y-axis of plane)
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[ExtensionLine]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[border:]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [list\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [\~build123d.topology.zero_d.Vertex]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]\],]{.pre} [offset:]{.pre} [float,]{.pre} [draft:]{.pre} [\~drafting.Draft,]{.pre} [sketch:]{.pre} [\~build123d.topology.composite.Sketch]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [label:]{.pre} [str]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [arrows:]{.pre} [tuple\[bool,]{.pre} [bool\]]{.pre} [=]{.pre} [(True,]{.pre} [True),]{.pre} [tolerance:]{.pre} [float]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [label_angle:]{.pre} [bool]{.pre} [=]{.pre} [False,]{.pre} [measurement_direction:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Sketch Object: Extension Line
Create a dimension line with two lines extending outward from the part to dimension. Typically used for (but not restricted to) outside dimensions, with a pair of lines extending from the edge of a part to a dimension line.
Parameters[:]{.colon}
: - **border** (*PathDescriptor*) -- a very general type of input defining the object to be dimensioned. Typically this value would be extracted from the part but is not restricted to this use.
- **offset** (*float*) -- a distance to displace the dimension line from the edge of the object
- **draft** (*Draft*) -- instance of Draft dataclass
- **label** (*str,* *optional*) -- a text string which will replace the length (or arc length) that would otherwise be extracted from the provided path. Providing a label is useful when illustrating a parameterized input where the name of an argument is desired not an actual measurement. Defaults to None.
- **arrows** (*tuple\[bool,* *bool\],* *optional*) -- a pair of boolean values controlling the placement of the start and end arrows. Defaults to (True, True).
- **tolerance** (*float* *\|* *tuple\[float,* *float\],* *optional*) -- an optional tolerance value to add to the extracted length value. If a single tolerance value is provided it is shown as ± the provided value while a pair of values are shown as separate + and - values. Defaults to None.
- **label_angle** (*bool,* *optional*) -- a flag indicating that instead of an extracted length value, the size of the circular arc extracted from the path should be displayed in degrees. Defaults to False.
- **measurement_direction** (*VectorLike,* *optional*) -- Vector line which to project the dimension against. Offset start point is the position of the start of border. Defaults to None.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
[[dimension]{.pre}]{.sig-name .descname}
: length of the dimension
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Polygon]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[\*pts:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]\],]{.pre} [rotation:]{.pre} [float]{.pre} [=]{.pre} [0,]{.pre} [align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align,]{.pre} [\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [(\,]{.pre} [\),]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Sketch Object: Polygon
Create a polygon defined by given sequence of points.
Note: the order of the points defines the resulting normal of the Face in Algebra mode, where counter-clockwise order creates an upward normal while clockwise order a downward normal. In Builder mode, the Face is added with an upward normal.
Parameters[:]{.colon}
: - **pts** (*VectorLike* *\|* *Iterable\[VectorLike\]*) -- sequence of points defining the vertices of the polygon
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Rectangle]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[width:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Rectangle
Create a rectangle defined by width and height.
Parameters[:]{.colon}
: - **width** (*float*) -- rectangle width
- **height** (*float*) -- rectangle height
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[RectangleRounded]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[width:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[radius:]{.pre} [float]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Rectangle Rounded
Create a rectangle defined by width and height with filleted corners.
Parameters[:]{.colon}
: - **width** (*float*) -- rectangle width
- **height** (*float*) -- rectangle height
- **radius** (*float*) -- fillet radius
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[RegularPolygon]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[radius:]{.pre} [float]{.pre}]{.n}*, *[[side_count:]{.pre} [int]{.pre}]{.n}*, *[[major_radius:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[align:]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Regular Polygon
Create a regular polygon defined by radius and side count. Use major_radius to define whether the polygon circumscribes (along the vertices) or inscribes (along the sides) the radius circle.
Parameters[:]{.colon}
: - **radius** (*float*) -- construction radius
- **side_count** (*int*) -- number of sides
- **major_radius** (*bool*) -- If True the radius is the major radius (circumscribed circle), else the radius is the minor radius (inscribed circle). Defaults to True
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
[[apothem]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[float]{.pre}*
: radius of the inscribed circle or minor radius
[[radius]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[float]{.pre}*
: radius of the circumscribed circle or major radius
```{=html}
```
*[class]{.pre}[ ]{.w}*[[SlotArc]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[arc:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Slot Arc
Create a slot defined by a line and height. May be an arc, stright line, spline, etc.
Parameters[:]{.colon}
: - **arc** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- center line of slot
- **height** (*float*) -- diameter of end arcs
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[SlotCenterPoint]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[center:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [point:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float\]]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\],]{.pre} [height:]{.pre} [float,]{.pre} [rotation:]{.pre} [float]{.pre} [=]{.pre} [0,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren}
: Sketch Object: Slot Center Point
Create a slot defined by the center of the slot and the center of one end arc. The slot will be symmetric about the center point.
Parameters[:]{.colon}
: - **center** (*VectorLike*) -- center point
- **point** (*VectorLike*) -- center of arc point
- **height** (*float*) -- diameter of end arcs
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[SlotCenterToCenter]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[center_separation:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Slot Center To Center
Create a slot defined by the distance between the centers of the two end arcs.
Parameters[:]{.colon}
: - **center_separation** (*float*) -- distance between arc centers
- **height** (*float*) -- diameter of end arcs
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[SlotOverall]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[width:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Slot Overall
Create a slot defined by the overall width and height.
Parameters[:]{.colon}
: - **width** (*float*) -- overall width of slot
- **height** (*float*) -- diameter of end arcs
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[TechnicalDrawing]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[designed_by:]{.pre} [str]{.pre} [=]{.pre} [\'build123d\']{.pre}]{.n}*, *[[design_date:]{.pre} [\~datetime.date]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[page_size:]{.pre} [\~build123d.build_enums.PageSize]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[title:]{.pre} [str]{.pre} [=]{.pre} [\'Title\']{.pre}]{.n}*, *[[sub_title:]{.pre} [str]{.pre} [=]{.pre} [\'Sub]{.pre} [Title\']{.pre}]{.n}*, *[[drawing_number:]{.pre} [str]{.pre} [=]{.pre} [\'B3D-1\']{.pre}]{.n}*, *[[sheet_number:]{.pre} [int]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[drawing_scale:]{.pre} [float]{.pre} [=]{.pre} [1.0]{.pre}]{.n}*, *[[nominal_text_size:]{.pre} [float]{.pre} [=]{.pre} [10.0]{.pre}]{.n}*, *[[line_width:]{.pre} [float]{.pre} [=]{.pre} [0.5]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: TechnicalDrawing
The border of a technical drawing with external frame and text box.
Parameters[:]{.colon}
: - **designed_by** (*str,* *optional*) -- Defaults to "build123d".
- **design_date** (*date,* *optional*) -- Defaults to date.today().
- **page_size** (*PageSize,* *optional*) -- Defaults to PageSize.A4.
- **title** (*str,* *optional*) -- drawing title. Defaults to "Title".
- **sub_title** (*str,* *optional*) -- drawing sub title. Defaults to "Sub Title".
- **drawing_number** (*str,* *optional*) -- Defaults to "B3D-1".
- **sheet_number** (*int,* *optional*) -- Defaults to None.
- **drawing_scale** (*float,* *optional*) -- displays as 1:value. Defaults to 1.0.
- **nominal_text_size** (*float,* *optional*) -- size of title text. Defaults to 10.0.
- **line_width** (*float,* *optional*) -- Defaults to 0.5.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
[[margin]{.pre}]{.sig-name .descname}*[ ]{.w}[[=]{.pre}]{.p}[ ]{.w}[5]{.pre}*
:
[[page_sizes]{.pre}]{.sig-name .descname}*[ ]{.w}[[=]{.pre}]{.p}[ ]{.w}[{\:]{.pre} [(1189,]{.pre} [841),]{.pre} [\:]{.pre} [(37,]{.pre} [26),]{.pre} [\:]{.pre} [(841,]{.pre} [594),]{.pre} [\:]{.pre} [(594,]{.pre} [420),]{.pre} [\:]{.pre} [(420,]{.pre} [297),]{.pre} [\:]{.pre} [(297,]{.pre} [210),]{.pre} [\:]{.pre} [(210,]{.pre} [148.5),]{.pre} [\:]{.pre} [(148.5,]{.pre} [105),]{.pre} [\:]{.pre} [(105,]{.pre} [74),]{.pre} [\:]{.pre} [(74,]{.pre} [52),]{.pre} [\:]{.pre} [(52,]{.pre} [37),]{.pre} [\:]{.pre} [(431.79999999999995,]{.pre} [279.4),]{.pre} [\:]{.pre} [(355.59999999999997,]{.pre} [215.89999999999998),]{.pre} [\:]{.pre} [(279.4,]{.pre} [215.89999999999998)}]{.pre}*
:
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Text]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[txt:]{.pre} [str]{.pre}]{.n}*, *[[font_size:]{.pre} [float]{.pre}]{.n}*, *[[font:]{.pre} [str]{.pre} [=]{.pre} [\'Arial\']{.pre}]{.n}*, *[[font_path:]{.pre} [str]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[font_style:]{.pre} [\~build123d.build_enums.FontStyle]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[text_align:]{.pre} [tuple\[\~build123d.build_enums.TextAlign]{.pre}]{.n}*, *[[\~build123d.build_enums.TextAlign\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[path:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[position_on_path:]{.pre} [float]{.pre} [=]{.pre} [0.0]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0.0]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Text
Create text defined by text string and font size.
Fonts installed to the system can be specified by name and FontStyle. Fonts with subfamilies not in FontStyle should be specified with the subfamily name, e.g. "Arial Black". Alternatively, a specific font file can be specified with font_path.
Use ``{=html}available_fonts()``{=html} to list available font names for ``{=html}font``{=html} and FontStyles. Note: on Windows, fonts must be installed with "Install for all users" to be found by name.
Not all fonts have every FontStyle available, however ITALIC and BOLDITALIC will still italicize the font if the respective font file is not available.
text_align specifies alignment of text inside the bounding box, while align the aligns the bounding box itself.
Optionally, the Text can be positioned on a non-linear edge or wire with a path and position_on_path.
Parameters[:]{.colon}
: - **txt** (*str*) -- text to render
- **font_size** (*float*) -- size of the font in model units
- **font** (*str,* *optional*) -- font name. Defaults to "Arial"
- **font_path** (*str,* *optional*) -- system path to font file. Defaults to None
- **font_style** (*Font_Style,* *optional*) -- font style, REGULAR, BOLD, BOLDITALIC, or ITALIC. Defaults to Font_Style.REGULAR
- **text_align** (*tuple\[TextAlign,* *TextAlign\],* *optional*) -- horizontal text align LEFT, CENTER, or RIGHT. Vertical text align BOTTOM, CENTER, TOP, or TOPFIRSTLINE. Defaults to (TextAlign.CENTER, TextAlign.CENTER)
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to None
- **path** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- path for text to follow. Defaults to None
- **position_on_path** (*float,* *optional*) -- the relative location on path to position the text, values must be between 0.0 and 1.0. Defaults to 0.0
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Trapezoid]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[width:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[left_side_angle:]{.pre} [float]{.pre}]{.n}*, *[[right_side_angle:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Trapezoid
Create a trapezoid defined by major width, height, and interior angle(s).
Parameters[:]{.colon}
: - **width** (*float*) -- trapezoid major width
- **height** (*float*) -- trapezoid height
- **left_side_angle** (*float*) -- bottom left interior angle
- **right_side_angle** (*float,* *optional*) -- bottom right interior angle. If not provided, the trapezoid will be symmetric. Defaults to None
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **ValueError** -- Give angles result in an invalid trapezoid
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Triangle]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*]{.pre}]{.n}*, *[[a:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[b:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[c:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[A:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[B:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[C:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[rotation:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Sketch Object: Triangle
Create a triangle defined by one side length and any of two other side lengths or interior angles. The interior angles are opposite the side with the same designation (i.e. side 'a' is opposite angle 'A'). Side 'a' is the bottom side, followed by 'b' on the right, going counter-clockwise.
Parameters[:]{.colon}
: - **a** (*float,* *optional*) -- side 'a' length. Defaults to None
- **b** (*float,* *optional*) -- side 'b' length. Defaults to None
- **c** (*float,* *optional*) -- side 'c' length. Defaults to None
- **A** (*float,* *optional*) -- interior angle 'A'. Defaults to None
- **B** (*float,* *optional*) -- interior angle 'B'. Defaults to None
- **C** (*float,* *optional*) -- interior angle 'C'. Defaults to None
- **rotation** (*float,* *optional*) -- angle to rotate object. Defaults to 0
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to None
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
Raises[:]{.colon}
: **ValueError** -- One length and two other values were not provided
[[A]{.pre}]{.sig-name .descname}
: interior angle 'A' in degrees
[[B]{.pre}]{.sig-name .descname}
: interior angle 'B' in degrees
[[C]{.pre}]{.sig-name .descname}
: interior angle 'C' in degrees
[[a]{.pre}]{.sig-name .descname}
: length of side 'a'
[[b]{.pre}]{.sig-name .descname}
: length of side 'b'
[[c]{.pre}]{.sig-name .descname}
: length of side 'c'
[[edge_a]{.pre}]{.sig-name .descname}
: edge 'a'
[[edge_b]{.pre}]{.sig-name .descname}
: edge 'b'
[[edge_c]{.pre}]{.sig-name .descname}
: edge 'c'
[[vertex_A]{.pre}]{.sig-name .descname}
: vertex 'A'
[[vertex_B]{.pre}]{.sig-name .descname}
: vertex 'B'
[[vertex_C]{.pre}]{.sig-name .descname}
: vertex 'C'
:::
:::
::: {#objects.xhtml#id3 .section}
## 3D Objects
::: {.sd-container-fluid .sd-sphinx-override .sd-mb-4 .docutils}
::: {.sd-row .sd-row-cols-3 .sd-row-cols-xs-3 .sd-row-cols-sm-3 .sd-row-cols-md-3 .sd-row-cols-lg-3 .docutils}
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Box`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Box "objects_part.Box"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Box defined by length, width, height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Cone`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Cone "objects_part.Cone"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Cone defined by radii and height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`CounterBoreHole`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.CounterBoreHole "objects_part.CounterBoreHole"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Counter bore hole defined by radii and depths
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`CounterSinkHole`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.CounterSinkHole "objects_part.CounterSinkHole"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Counter sink hole defined by radii and depth and angle
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Cylinder`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Cylinder "objects_part.Cylinder"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Cylinder defined by radius and height
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Hole`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Hole "objects_part.Hole"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Hole defined by radius and depth
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Sphere`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Sphere "objects_part.Sphere"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Sphere defined by radius and arc angles
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Torus`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Torus "objects_part.Torus"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Torus defined major and minor radii
:::
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .docutils}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
[`Wedge`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.Wedge "objects_part.Wedge"){.hxr-hoverxref .hxr-tooltip .reference .internal}
:::

:::
::: {.sd-card-footer .docutils}
Wedge defined by lengths along multiple Axes
:::
:::
:::
:::
:::
::: {#objects.xhtml#id4 .section}
### Reference
*[class]{.pre}[ ]{.w}*[[BasePartObject]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[part:]{.pre} [\~build123d.topology.composite.Part]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid]{.pre}]{.n}*, *[[rotation:]{.pre} [\~build123d.geometry.Rotation]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[0)]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Base class for all BuildPart objects & operations
Parameters[:]{.colon}
: - **solid** ([*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- object to create
- **rotation** (*RotationLike,* *optional*) -- angles to rotate about axes. Defaults to (0, 0, 0)
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]* *\|* *None,* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to None
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Box]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[length:]{.pre} [float]{.pre}]{.n}*, *[[width:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[rotation:]{.pre} [\~build123d.geometry.Rotation]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[0)]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Object: Box
Create a box defined by length, width, and height.
Parameters[:]{.colon}
: - **length** (*float*) -- box length
- **width** (*float*) -- box width
- **height** (*float*) -- box height
- **rotation** (*RotationLike,* *optional*) -- angles to rotate about axes. Defaults to (0, 0, 0)
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]* *\|* *None,* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combine mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Cone]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[bottom_radius:]{.pre} [float]{.pre}]{.n}*, *[[top_radius:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[arc_size:]{.pre} [float]{.pre} [=]{.pre} [360]{.pre}]{.n}*, *[[rotation:]{.pre} [\~build123d.geometry.Rotation]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[0)]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Object: Cone
Create a cone defined by bottom radius, top radius, and height.
Parameters[:]{.colon}
: - **bottom_radius** (*float*) -- bottom radius
- **top_radius** (*float*) -- top radius, may be zero
- **height** (*float*) -- cone height
- **arc_size** (*float,* *optional*) -- angular size of cone. Defaults to 360
- **rotation** (*RotationLike,* *optional*) -- angles to rotate about axes. Defaults to (0, 0, 0)
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]* *\|* *None,* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combine mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[CounterBoreHole]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[radius:]{.pre} [float]{.pre}]{.n}*, *[[counter_bore_radius:]{.pre} [float]{.pre}]{.n}*, *[[counter_bore_depth:]{.pre} [float]{.pre}]{.n}*, *[[depth:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Operation: Counter Bore Hole
Create a counter bore hole defined by radius, counter bore radius, counter bore and depth.
Parameters[:]{.colon}
: - **radius** (*float*) -- hole radius
- **counter_bore_radius** (*float*) -- counter bore radius
- **counter_bore_depth** (*float*) -- counter bore depth
- **depth** (*float,* *optional*) -- hole depth, through part if None. Defaults to None
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.SUBTRACT
```{=html}
```
*[class]{.pre}[ ]{.w}*[[CounterSinkHole]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[radius:]{.pre} [float]{.pre}]{.n}*, *[[counter_sink_radius:]{.pre} [float]{.pre}]{.n}*, *[[depth:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[counter_sink_angle:]{.pre} [float]{.pre} [=]{.pre} [82]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Operation: Counter Sink Hole
Create a countersink hole defined by radius, countersink radius, countersink angle, and depth.
Parameters[:]{.colon}
: - **radius** (*float*) -- hole radius
- **counter_sink_radius** (*float*) -- countersink radius
- **depth** (*float,* *optional*) -- hole depth, through part if None. Defaults to None
- **counter_sink_angle** (*float,* *optional*) -- cone angle. Defaults to 82
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.SUBTRACT
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Cylinder]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[radius:]{.pre} [float]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre}]{.n}*, *[[arc_size:]{.pre} [float]{.pre} [=]{.pre} [360]{.pre}]{.n}*, *[[rotation:]{.pre} [\~build123d.geometry.Rotation]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[0)]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Object: Cylinder
Create a cylinder defined by radius and height.
Parameters[:]{.colon}
: - **radius** (*float*) -- cylinder radius
- **height** (*float*) -- cylinder height
- **arc_size** (*float,* *optional*) -- angular size of cone. Defaults to 360.
- **rotation** (*RotationLike,* *optional*) -- angles to rotate about axes. Defaults to (0, 0, 0)
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]* *\|* *None,* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combine mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Hole]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[radius:]{.pre} [float]{.pre}]{.n}*, *[[depth:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Operation: Hole
Create a hole defined by radius and depth.
Parameters[:]{.colon}
: - **radius** (*float*) -- hole radius
- **depth** (*float,* *optional*) -- hole depth, through part if None. Defaults to None
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.SUBTRACT
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Sphere]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[radius:]{.pre} [float]{.pre}]{.n}*, *[[arc_size1:]{.pre} [float]{.pre} [=]{.pre} [-90]{.pre}]{.n}*, *[[arc_size2:]{.pre} [float]{.pre} [=]{.pre} [90]{.pre}]{.n}*, *[[arc_size3:]{.pre} [float]{.pre} [=]{.pre} [360]{.pre}]{.n}*, *[[rotation:]{.pre} [\~build123d.geometry.Rotation]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[0)]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Object: Sphere
Create a sphere defined by a radius.
Parameters[:]{.colon}
: - **radius** (*float*) -- sphere radius
- **arc_size1** (*float,* *optional*) -- angular size of bottom hemisphere. Defaults to -90.
- **arc_size2** (*float,* *optional*) -- angular size of top hemisphere. Defaults to 90.
- **arc_size3** (*float,* *optional*) -- angular revolution about pole. Defaults to 360.
- **rotation** (*RotationLike,* *optional*) -- angles to rotate about axes. Defaults to (0, 0, 0)
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]* *\|* *None,* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combine mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Torus]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[major_radius:]{.pre} [float]{.pre}]{.n}*, *[[minor_radius:]{.pre} [float]{.pre}]{.n}*, *[[minor_start_angle:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[minor_end_angle:]{.pre} [float]{.pre} [=]{.pre} [360]{.pre}]{.n}*, *[[major_angle:]{.pre} [float]{.pre} [=]{.pre} [360]{.pre}]{.n}*, *[[rotation:]{.pre} [\~build123d.geometry.Rotation]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[0)]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Object: Torus
Create a torus defined by major and minor radii.
Parameters[:]{.colon}
: - **major_radius** (*float*) -- major torus radius
- **minor_radius** (*float*) -- minor torus radius
- **minor_start_angle** (*float,* *optional*) -- angle to start minor arc. Defaults to 0
- **minor_end_angle** (*float,* *optional*) -- angle to end minor arc. Defaults to 360
- **major_angle** (*float,* *optional*) -- angle to revolve minor arc. Defaults to 360
- **rotation** (*RotationLike,* *optional*) -- angles to rotate about axes. Defaults to (0, 0, 0)
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]* *\|* *None,* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combine mode. Defaults to Mode.ADD
```{=html}
```
*[class]{.pre}[ ]{.w}*[[Wedge]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[xsize:]{.pre} [float]{.pre}]{.n}*, *[[ysize:]{.pre} [float]{.pre}]{.n}*, *[[zsize:]{.pre} [float]{.pre}]{.n}*, *[[xmin:]{.pre} [float]{.pre}]{.n}*, *[[zmin:]{.pre} [float]{.pre}]{.n}*, *[[xmax:]{.pre} [float]{.pre}]{.n}*, *[[zmax:]{.pre} [float]{.pre}]{.n}*, *[[rotation:]{.pre} [\~build123d.geometry.Rotation]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [=]{.pre} [(0]{.pre}]{.n}*, *[[0]{.pre}]{.n}*, *[[0)]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [=]{.pre} [(\]{.pre}]{.n}*, *[[\]{.pre}]{.n}*, *[[\)]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Part Object: Wedge
Create a wedge with a near face defined by xsize and z size, a far face defined by xmin to xmax and zmin to zmax, and a depth of ysize.
Parameters[:]{.colon}
: - **xsize** (*float*) -- length of near face along x-axis
- **ysize** (*float*) -- length of part along y-axis
- **zsize** (*float*) -- length of near face z-axis
- **xmin** (*float*) -- minimum position far face along x-axis
- **zmin** (*float*) -- minimum position far face along z-axis
- **xmax** (*float*) -- maximum position far face along x-axis
- **zmax** (*float*) -- maximum position far face along z-axis
- **rotation** (*RotationLike,* *optional*) -- angles to rotate about axes. Defaults to (0, 0, 0)
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]* *\|* *None,* *optional*) -- align MIN, CENTER, or MAX of object. Defaults to (Align.CENTER, Align.CENTER, Align.CENTER)
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combine mode. Defaults to Mode.ADD
:::
:::
::: {#objects.xhtml#custom-objects .section}
## Custom Objects
All of the objects presented above were created using one of three base object classes: [`BaseLineObject`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_curve.BaseLineObject "objects_curve.BaseLineObject"){.hxr-hoverxref .hxr-tooltip .reference .internal} , [`BaseSketchObject`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.BaseSketchObject "objects_sketch.BaseSketchObject"){.hxr-hoverxref .hxr-tooltip .reference .internal} , and [`BasePartObject`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_part.BasePartObject "objects_part.BasePartObject"){.hxr-hoverxref .hxr-tooltip .reference .internal} . Users can use these base object classes to easily create custom objects that have all the functionality of the core objects.
{.align-center}
Here is an example of a custom sketch object specially created as part of the design of this playing card storage box (`see the playing_cards.py example`{.xref .download .docutils .literal .notranslate}):
::: {.highlight-build123d .notranslate}
::: highlight
class Club(BaseSketchObject):
def __init__(
self,
height: float,
rotation: float = 0,
align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
):
with BuildSketch() as club:
with BuildLine():
l0 = Line((0, -188), (76, -188))
b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))
b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))
b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))
b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))
mirror(about=Plane.YZ)
make_face()
scale(by=height / club.sketch.bounding_box().size.Y)
super().__init__(obj=club.sketch, rotation=rotation, align=align, mode=mode)
:::
:::
Here the new custom object class is called `Club`{.docutils .literal .notranslate} and it's a sub-class of [`BaseSketchObject`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.BaseSketchObject "objects_sketch.BaseSketchObject"){.hxr-hoverxref .hxr-tooltip .reference .internal} . The `__init__`{.docutils .literal .notranslate} method contains all of the parameters used to instantiate the custom object, specially a `height`{.docutils .literal .notranslate}, `rotation`{.docutils .literal .notranslate}, `align`{.docutils .literal .notranslate}, and `mode`{.docutils .literal .notranslate} - your objects may contain a sub or super set of these parameters but should always contain a `mode`{.docutils .literal .notranslate} parameter such that it can be combined with a builder's object.
Next is the creation of the object itself, in this case a sketch of the club suit.
The final line calls the `__init__`{.docutils .literal .notranslate} method of the super class - i.e. [`BaseSketchObject`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.BaseSketchObject "objects_sketch.BaseSketchObject"){.hxr-hoverxref .hxr-tooltip .reference .internal} with its parameters.
That's it, now the `Club`{.docutils .literal .notranslate} object can be used anywhere a [`Circle`{.xref .py .py-class .docutils .literal .notranslate}](#objects.xhtml#objects_sketch.Circle "objects_sketch.Circle"){.hxr-hoverxref .hxr-tooltip .reference .internal} would be used - with either the Algebra or Builder API.
{.align-center}
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#operations.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#operations.xhtml#operations .section}
# Operations
Operations are functions that take objects as inputs and transform them into new objects. For example, a 2D Sketch can be extruded to create a 3D Part. All operations are Python functions which can be applied using both the Algebra and Builder APIs. It's important to note that objects created by operations are not affected by `Locations`{.docutils .literal .notranslate}, meaning their position is determined solely by the input objects used in the operation.
Here are a couple ways to use [`extrude()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal}, in Builder and Algebra mode:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as cylinder:
with BuildSketch():
Circle(radius)
extrude(amount=height)
:::
:::
::: {.highlight-build123d .notranslate}
::: highlight
cylinder = extrude(Circle(radius), amount=height)
:::
:::
The following table summarizes all of the available operations. Operations marked as 1D are applicable to BuildLine and Algebra Curve, 2D to BuildSketch and Algebra Sketch, 3D to BuildPart and Algebra Part.
Operation Description 0D 1D 2D 3D Example
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------ ---- ---- ---- ---- ----------------------------------------------------------------------------------------------------------
[`add()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.add "operations_generic.add"){.hxr-hoverxref .hxr-tooltip .reference .internal} Add object to builder ✓ ✓ ✓ [[16]{.std .std-ref}](#introductory_examples.xhtml#ex-16){.reference .internal}
[`bounding_box()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.bounding_box "operations_generic.bounding_box"){.hxr-hoverxref .hxr-tooltip .reference .internal} Add bounding box as Shape ✓ ✓ ✓
[`chamfer()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.chamfer "operations_generic.chamfer"){.hxr-hoverxref .hxr-tooltip .reference .internal} Bevel Vertex or Edge ✓ ✓ [[9]{.std .std-ref}](#introductory_examples.xhtml#ex-9){.reference .internal}
[`draft()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.draft "operations_part.draft"){.hxr-hoverxref .hxr-tooltip .reference .internal} Add a draft taper to a part ✓ [[Cast Bearing Unit]{.std .std-ref}](#examples_1.xhtml#examples-cast-bearing-unit){.reference .internal}
[`extrude()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} Draw 2D Shape into 3D ✓ [[3]{.std .std-ref}](#introductory_examples.xhtml#ex-3){.reference .internal}
[`fillet()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.fillet "operations_generic.fillet"){.hxr-hoverxref .hxr-tooltip .reference .internal} Radius Vertex or Edge ✓ ✓ [[9]{.std .std-ref}](#introductory_examples.xhtml#ex-9){.reference .internal}
[`full_round()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_sketch.full_round "operations_sketch.full_round"){.hxr-hoverxref .hxr-tooltip .reference .internal} Round-off Face along given Edge ✓ [[24-SPO-06 Buffer Stand]{.std .std-ref}](#tttt.xhtml#ttt-24-spo-06){.reference .internal}
[`loft()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.loft "operations_part.loft"){.hxr-hoverxref .hxr-tooltip .reference .internal} Create 3D Shape from sections ✓ [[24]{.std .std-ref}](#introductory_examples.xhtml#ex-24){.reference .internal}
[`make_brake_formed()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.make_brake_formed "operations_part.make_brake_formed"){.hxr-hoverxref .hxr-tooltip .reference .internal} Create sheet metal parts ✓
[`make_face()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_sketch.make_face "operations_sketch.make_face"){.hxr-hoverxref .hxr-tooltip .reference .internal} Create a Face from Edges ✓ [[4]{.std .std-ref}](#introductory_examples.xhtml#ex-4){.reference .internal}
[`make_hull()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_sketch.make_hull "operations_sketch.make_hull"){.hxr-hoverxref .hxr-tooltip .reference .internal} Create Convex Hull from Edges ✓
[`mirror()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.mirror "operations_generic.mirror"){.hxr-hoverxref .hxr-tooltip .reference .internal} Mirror about Plane ✓ ✓ ✓ [[15]{.std .std-ref}](#introductory_examples.xhtml#ex-15){.reference .internal}
[`offset()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.offset "operations_generic.offset"){.hxr-hoverxref .hxr-tooltip .reference .internal} Inset or outset Shape ✓ ✓ ✓ [[25]{.std .std-ref}](#introductory_examples.xhtml#ex-25){.reference .internal}
[`project()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.project "operations_generic.project"){.hxr-hoverxref .hxr-tooltip .reference .internal} Project points, lines or Faces ✓ ✓ ✓
[`project_workplane()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.project_workplane "operations_part.project_workplane"){.hxr-hoverxref .hxr-tooltip .reference .internal} Create workplane for projection
[`revolve()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.revolve "operations_part.revolve"){.hxr-hoverxref .hxr-tooltip .reference .internal} Swing 2D Shape about Axis ✓ [[23]{.std .std-ref}](#introductory_examples.xhtml#ex-23){.reference .internal}
[`scale()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.scale "operations_generic.scale"){.hxr-hoverxref .hxr-tooltip .reference .internal} Change size of Shape ✓ ✓ ✓
[`section()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.section "operations_part.section"){.hxr-hoverxref .hxr-tooltip .reference .internal} Generate 2D slices from 3D Shape ✓
[`split()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.split "operations_generic.split"){.hxr-hoverxref .hxr-tooltip .reference .internal} Divide object by Plane ✓ ✓ ✓ [[27]{.std .std-ref}](#introductory_examples.xhtml#ex-27){.reference .internal}
[`sweep()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.sweep "operations_generic.sweep"){.hxr-hoverxref .hxr-tooltip .reference .internal} Extrude 1/2D section(s) along path ✓ ✓ [[14]{.std .std-ref}](#introductory_examples.xhtml#ex-14){.reference .internal}
[`thicken()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.thicken "operations_part.thicken"){.hxr-hoverxref .hxr-tooltip .reference .internal} Expand 2D section(s) ✓
[`trace()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_sketch.trace "operations_sketch.trace"){.hxr-hoverxref .hxr-tooltip .reference .internal} Convert lines to faces ✓
The following table summarizes all of the selectors that can be used within the scope of a Builder. Note that they will extract objects from the builder that is currently within scope without it being explicitly referenced.
Builder
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------- --------- -------- ------
Selector Description Line Sketch Part
[`edge()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.edge "build_common.edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select edge from current builder ✓ ✓ ✓
[`edges()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.edges "build_common.edges"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select edges from current builder ✓ ✓ ✓
[`face()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.face "build_common.face"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select face from current builder ✓ ✓
[`faces()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.faces "build_common.faces"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select faces from current builder ✓ ✓
[`solid()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.solid "build_common.solid"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select solid from current builder ✓
[`solids()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.solids "build_common.solids"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select solids from current builder ✓
[`vertex()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.vertex "build_common.vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select vertex from current builder ✓ ✓ ✓
[`vertices()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.vertices "build_common.vertices"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select vertices from current builder ✓ ✓ ✓
[`wire()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.wire "build_common.wire"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select wire from current builder ✓ ✓ ✓
[`wires()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#build_common.wires "build_common.wires"){.hxr-hoverxref .hxr-tooltip .reference .internal} Select wires from current builder ✓ ✓ ✓
::: {#operations.xhtml#reference .section}
## Reference
[[add]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[objects:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid]{.pre} [\|]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~build123d.build_common.Builder]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid]{.pre} [\|]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~build123d.build_common.Builder\],]{.pre} [rotation:]{.pre} [float]{.pre} [\|]{.pre} [\~build123d.geometry.Rotation]{.pre} [\|]{.pre} [tuple\[float,]{.pre} [float,]{.pre} [float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [clean:]{.pre} [bool]{.pre} [=]{.pre} [True,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Compound]{.pre}]{.sig-return-typehint}]{.sig-return}
: Generic Object: Add Object to Part or Sketch
Add an object to a builder.
BuildPart:
: Edges and Wires are added to pending_edges. Compounds of Face are added to pending_faces. Solids or Compounds of Solid are combined into the part.
BuildSketch:
: Edges and Wires are added to pending_edges. Compounds of Face are added to sketch.
BuildLine:
: Edges and Wires are added to line.
Parameters[:]{.colon}
: - **objects** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*) -- objects to add
- **rotation** (*float* *\|* *RotationLike,* *optional*) -- rotation angle for sketch, rotation about each axis for part. Defaults to None.
- **clean** -- Remove extraneous internal structure. Defaults to True.
```{=html}
```
[[bounding_box]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects:]{.pre} [\~build123d.topology.shape_core.Shape]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.shape_core.Shape\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Sketch]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Part]{.pre}]{.sig-return-typehint}]{.sig-return}
: Generic Operation: Add Bounding Box
Applies to: BuildSketch and BuildPart
Add the 2D or 3D bounding boxes of the object sequence
Parameters[:]{.colon}
: - **objects** ([*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*) -- objects to create bbox for
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
```{=html}
```
[[chamfer]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Edge]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Vertex]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Iterable]{.pre}[[\[]{.pre}]{.p}[Edge]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Vertex]{.pre}[[\]]{.pre}]{.p}]{.n}*, *[[length]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}]{.n}*, *[[length2]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[reference]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Edge]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Face]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Sketch]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Part]{.pre}]{.sig-return-typehint}]{.sig-return}
: Generic Operation: chamfer
Applies to 2 and 3 dimensional objects.
Chamfer the given sequence of edges or vertices.
Parameters[:]{.colon}
: - **objects** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Vertex*](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*) -- edges or vertices to chamfer
- **length** (*float*) -- chamfer size
- **length2** (*float,* *optional*) -- asymmetric chamfer size. Defaults to None.
- **angle** (*float,* *optional*) -- chamfer angle in degrees. Defaults to None.
- **reference** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- identifies the side where length is measured. Edge(s) must be part of the face. Vertex/Vertices must be part of edge
Raises[:]{.colon}
: - **ValueError** -- no objects provided
- **ValueError** -- objects must be Edges
- **ValueError** -- objects must be Vertices
- **ValueError** -- Only one of length2 or angle should be provided
- **ValueError** -- reference can only be used in conjunction with length2 or angle
```{=html}
```
[[draft]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[faces]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Face]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Iterable]{.pre}[[\[]{.pre}]{.p}[Face]{.pre}[[\]]{.pre}]{.p}]{.n}*, *[[neutral_plane]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Plane]{.pre}]{.n}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Part]{.pre}]{.sig-return-typehint}]{.sig-return}
: Part Operation: draft
Apply a draft angle to the given faces of the part
Parameters[:]{.colon}
: - **faces** -- Faces to which the draft should be applied.
- **neutral_plane** -- Plane defining the neutral direction and position.
- **angle** -- Draft angle in degrees.
```{=html}
```
[[extrude]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[to_extrude:]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.composite.Sketch]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[amount:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[dir:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[until:]{.pre} [\~build123d.build_enums.Until]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[target:]{.pre} [\~build123d.topology.three_d.Solid]{.pre} [\|]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[both:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[taper:]{.pre} [float]{.pre} [=]{.pre} [0.0]{.pre}]{.n}*, *[[clean:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Part]{.pre}]{.sig-return-typehint}]{.sig-return}
: Part Operation: extrude
Extrude a sketch or face by an amount or until another object.
Parameters[:]{.colon}
: - **to_extrude** (*Union\[*[*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Sketch*](#direct_api_reference.xhtml#topology.Sketch "topology.Sketch"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- object to extrude. Defaults to None.
- **amount** (*float,* *optional*) -- distance to extrude, sign controls direction. Defaults to None.
- **dir** (*VectorLike,* *optional*) -- direction. Defaults to None.
- **until** ([*Until*](#builder_api_reference.xhtml#build_enums.Until "build_enums.Until"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- extrude limit. Defaults to None.
- **target** ([*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- extrude until target. Defaults to None.
- **both** (*bool,* *optional*) -- extrude in both directions. Defaults to False.
- **taper** (*float,* *optional*) -- taper angle. Defaults to 0.0.
- **clean** (*bool,* *optional*) -- Remove extraneous internal structure. Defaults to True.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
Raises[:]{.colon}
: - **ValueError** -- No object to extrude
- **ValueError** -- No target object
Returns[:]{.colon}
: extruded object
Return type[:]{.colon}
: [*Part*](#direct_api_reference.xhtml#topology.Part "topology.Part"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[fillet]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Edge]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Vertex]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Iterable]{.pre}[[\[]{.pre}]{.p}[Edge]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Vertex]{.pre}[[\]]{.pre}]{.p}]{.n}*, *[[radius]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Sketch]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Part]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Curve]{.pre}]{.sig-return-typehint}]{.sig-return}
: Generic Operation: fillet
Applies to 2 and 3 dimensional objects.
Fillet the given sequence of edges or vertices. Note that vertices on either end of an open line will be automatically skipped.
Parameters[:]{.colon}
: - **objects** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Vertex*](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*) -- edges or vertices to fillet
- **radius** (*float*) -- fillet size - must be less than 1/2 local width
Raises[:]{.colon}
: - **ValueError** -- no objects provided
- **ValueError** -- objects must be Edges
- **ValueError** -- objects must be Vertices
- **ValueError** -- nothing to fillet
```{=html}
```
[[full_round]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[edge:]{.pre} [\~build123d.topology.one_d.Edge]{.pre}]{.n}*, *[[invert:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[voronoi_point_count:]{.pre} [int]{.pre} [=]{.pre} [100]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[tuple]{.pre}[[\[]{.pre}]{.p}[Sketch]{.pre}[[,]{.pre}]{.p}[ ]{.w}[Vector]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Sketch Operation: full_round
Given an edge from a Face/Sketch, modify the face by replacing the given edge with the arc of the Voronoi largest empty circle that will fit within the Face. This "rounds off" the end of the object.
Parameters[:]{.colon}
: - **edge** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- target Edge to remove
- **invert** (*bool,* *optional*) -- make the arc concave instead of convex. Defaults to False.
- **voronoi_point_count** (*int,* *optional*) -- number of points along each edge used to create the voronoi vertices as potential locations for the center of the largest empty circle. Defaults to 100.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.REPLACE.
Raises[:]{.colon}
: **ValueError** -- Invalid geometry
Returns[:]{.colon}
: the modified shape
Return type[:]{.colon}
: [*Sketch*](#direct_api_reference.xhtml#topology.Sketch "topology.Sketch"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[loft]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[sections:]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.composite.Sketch]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.zero_d.Vertex]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.composite.Sketch\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[ruled:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[clean:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Part]{.pre}]{.sig-return-typehint}]{.sig-return}
: Part Operation: loft
Loft the pending sketches/faces, across all workplanes, into a solid.
Parameters[:]{.colon}
: - **sections** ([*Vertex*](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Sketch*](#direct_api_reference.xhtml#topology.Sketch "topology.Sketch"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- slices to loft into object. If not provided, pending_faces will be used. If vertices are to be used, a vertex can be the first, last, or first and last elements.
- **ruled** (*bool,* *optional*) -- discontiguous layer tangents. Defaults to False.
- **clean** (*bool,* *optional*) -- Remove extraneous internal structure. Defaults to True.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
```{=html}
```
[[make_brake_formed]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[thickness:]{.pre} [float,]{.pre} [station_widths:]{.pre} [float]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[float\],]{.pre} [line:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [side:]{.pre} [\~build123d.build_enums.Side]{.pre} [=]{.pre} [\,]{.pre} [kind:]{.pre} [\~build123d.build_enums.Kind]{.pre} [=]{.pre} [\,]{.pre} [clean:]{.pre} [bool]{.pre} [=]{.pre} [True,]{.pre} [mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Part]{.pre}]{.sig-return-typehint}]{.sig-return}
: Create a part typically formed with a sheet metal brake from a single outline. The line parameter describes how the material is to be bent. Either a single width value or a width value at each vertex or station is provided to control the width of the end part. Note that if multiple values are provided there must be one for each vertex and that the resulting part is composed of linear segments.
Parameters[:]{.colon}
: - **thickness** (*float*) -- sheet metal thickness
- **station_widths** (*Union\[float,* *Iterable\[float\]\]*) -- width of part at each vertex or a single value. Note that this width is perpendicular to the provided line/plane.
- **line** (*Union\[*[*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- outline of part. Defaults to None.
- **side** (*Side,* *optional*) -- offset direction. Defaults to Side.LEFT.
- **kind** ([*Kind*](#builder_api_reference.xhtml#build_enums.Kind "build_enums.Kind"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- offset intersection type. Defaults to Kind.ARC.
- **clean** (*bool,* *optional*) -- clean the resulting solid. Defaults to True.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
Raises[:]{.colon}
: - **ValueError** -- invalid line type
- **ValueError** -- not line provided
- **ValueError** -- line not suitable
- **ValueError** -- incorrect \# of width values
Returns[:]{.colon}
: sheet metal part
Return type[:]{.colon}
: [*Part*](#direct_api_reference.xhtml#topology.Part "topology.Part"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[make_face]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[edges:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.one_d.Edge\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Sketch]{.pre}]{.sig-return-typehint}]{.sig-return}
: Sketch Operation: make_face
Create a face from the given perimeter edges.
Parameters[:]{.colon}
: - **edges** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- sequence of perimeter edges. Defaults to all sketch pending edges.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
```{=html}
```
[[make_hull]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[edges:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.one_d.Edge\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Sketch]{.pre}]{.sig-return-typehint}]{.sig-return}
: Sketch Operation: make_hull
Create a face from the convex hull of the given edges
Parameters[:]{.colon}
: - **edges** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- sequence of edges to hull. Defaults to all sketch pending edges.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
```{=html}
```
[[mirror]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.composite.Sketch]{.pre} [\|]{.pre} [\~build123d.topology.composite.Part]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.composite.Sketch]{.pre} [\|]{.pre} [\~build123d.topology.composite.Part\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[about:]{.pre} [\~build123d.geometry.Plane]{.pre} [=]{.pre} [Plane(o=(0.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[0.00)]{.pre}]{.n}*, *[[x=(1.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[0.00)]{.pre}]{.n}*, *[[z=(0.00]{.pre}]{.n}*, *[[-1.00]{.pre}]{.n}*, *[[0.00))]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Curve]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sketch]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Part]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}]{.sig-return-typehint}]{.sig-return}
: Generic Operation: mirror
Applies to 1, 2, and 3 dimensional objects.
Mirror a sequence of objects over the given plane.
Parameters[:]{.colon}
: - **objects** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*) -- objects to mirror
- **about** ([*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- reference plane. Defaults to "XZ".
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
Raises[:]{.colon}
: **ValueError** -- missing objects
```{=html}
```
[[offset]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid]{.pre} [\|]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid]{.pre} [\|]{.pre} [\~build123d.topology.composite.Compound\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[amount:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[openings:]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [list\[\~build123d.topology.two_d.Face\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[kind:]{.pre} [\~build123d.build_enums.Kind]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[side:]{.pre} [\~build123d.build_enums.Side]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[closed:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[min_edge_length:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Curve]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sketch]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Part]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}]{.sig-return-typehint}]{.sig-return}
: Generic Operation: offset
Applies to 1, 2, and 3 dimensional objects.
Offset the given sequence of Edges, Faces, Compound of Faces, or Solids. The kind parameter controls the shape of the transitions. For Solid objects, the openings parameter allows selected faces to be open, like a hollow box with no lid.
Parameters[:]{.colon}
: - **objects** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*) -- objects to offset
- **amount** (*float*) -- positive values external, negative internal
- **openings** (*list\[*[*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- Defaults to None.
- **kind** ([*Kind*](#builder_api_reference.xhtml#build_enums.Kind "build_enums.Kind"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- transition shape. Defaults to Kind.ARC.
- **side** (*Side,* *optional*) -- side to place offset. Defaults to Side.BOTH.
- **closed** (*bool,* *optional*) -- if Side!=BOTH, close the LEFT or RIGHT offset. Defaults to True.
- **min_edge_length** (*float,* *optional*) -- repair degenerate edges generated by offset by eliminating edges of minimum length in offset wire. Defaults to None.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.REPLACE.
Raises[:]{.colon}
: - **ValueError** -- missing objects
- **ValueError** -- Invalid object type
```{=html}
```
[[project]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [\~build123d.topology.zero_d.Vertex]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [\~build123d.topology.zero_d.Vertex\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[workplane:]{.pre} [\~build123d.geometry.Plane]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[target:]{.pre} [\~build123d.topology.three_d.Solid]{.pre} [\|]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~build123d.topology.composite.Part]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Curve]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sketch]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[ShapeList]{.pre}[[\[]{.pre}]{.p}[Vector]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Generic Operation: project
Applies to 0, 1, and 2 dimensional objects.
Project the given objects or points onto a BuildLine or BuildSketch workplane in the direction of the normal of that workplane. When projecting onto a sketch a Face(s) are generated while Edges are generated for BuildLine. Will only use the first if BuildSketch has multiple active workplanes. In algebra mode a workplane must be provided and the output is either a Face, Curve, Sketch, Compound, or ShapeList\[Vector\].
Note that only if mode is not Mode.PRIVATE only Faces can be projected into BuildSketch and Edge/Wires into BuildLine.
Parameters[:]{.colon}
: - **objects** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *VectorLike* *\|* [*Vertex*](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*) -- objects or points to project
- **workplane** ([*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- screen workplane
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
Raises[:]{.colon}
: - **ValueError** -- project doesn't accept group_by
- **ValueError** -- Either a workplane must be provided or a builder must be active
- **ValueError** -- Points and faces can only be projected in PRIVATE mode
- **ValueError** -- Edges, wires and points can only be projected in PRIVATE mode
- **RuntimeError** -- BuildPart doesn't have a project operation
```{=html}
```
[[project_workplane]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[origin]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Vector]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sequence]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Vertex]{.pre}]{.n}*, *[[x_dir]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Vector]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sequence]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Vertex]{.pre}]{.n}*, *[[projection_dir]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Vector]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sequence]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[\]]{.pre}]{.p}]{.n}*, *[[distance]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Plane]{.pre}]{.sig-return-typehint}]{.sig-return}
: Part Operation: project_workplane
Return a plane to be used as a BuildSketch or BuildLine workplane with a known origin and x direction. The plane's origin will be the projection of the provided origin (in 3D space). The plane's x direction will be the projection of the provided x_dir (in 3D space).
Parameters[:]{.colon}
: - **origin** (*Union\[VectorLike,* [*Vertex*](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]*) -- origin in 3D space
- **x_dir** (*Union\[VectorLike,* [*Vertex*](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]*) -- x direction in 3D space
- **projection_dir** (*VectorLike*) -- projection direction
- **distance** (*float*) -- distance from origin to workplane
Raises[:]{.colon}
: - **RuntimeError** -- Not suitable for BuildLine or BuildSketch
- **ValueError** -- x_dir perpendicular to projection_dir
Returns[:]{.colon}
: workplane aligned for projection
Return type[:]{.colon}
: [*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[revolve]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[profiles:]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.two_d.Face\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[axis:]{.pre} [\~build123d.geometry.Axis]{.pre} [=]{.pre} [((0.0]{.pre}]{.n}*, *[[0.0]{.pre}]{.n}*, *[[0.0)]{.pre}]{.n}*, *[[(0.0]{.pre}]{.n}*, *[[0.0]{.pre}]{.n}*, *[[1.0))]{.pre}]{.n}*, *[[revolution_arc:]{.pre} [float]{.pre} [=]{.pre} [360.0]{.pre}]{.n}*, *[[clean:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Part]{.pre}]{.sig-return-typehint}]{.sig-return}
: Part Operation: Revolve
Revolve the profile or pending sketches/face about the given axis. Note that the most common use case is when the axis is in the same plane as the face to be revolved but this isn't required.
Parameters[:]{.colon}
: - **profiles** ([*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- 2D profile(s) to revolve.
- **axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- axis of rotation. Defaults to Axis.Z.
- **revolution_arc** (*float,* *optional*) -- angular size of revolution. Defaults to 360.0.
- **clean** (*bool,* *optional*) -- Remove extraneous internal structure. Defaults to True.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
Raises[:]{.colon}
: **ValueError** -- Invalid axis of revolution
```{=html}
```
[[scale]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects:]{.pre} [\~build123d.topology.shape_core.Shape]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.shape_core.Shape\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[by:]{.pre} [float]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [=]{.pre} [1]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Curve]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sketch]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Part]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}]{.sig-return-typehint}]{.sig-return}
: Generic Operation: scale
Applies to 1, 2, and 3 dimensional objects.
Scale a sequence of objects. Note that when scaling non-uniformly across the three axes, the type of the underlying object may change to bspline from line, circle, etc.
Parameters[:]{.colon}
: - **objects** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*) -- objects to scale
- **by** (*float* *\|* *tuple\[float,* *float,* *float\]*) -- scale factor
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.REPLACE.
Raises[:]{.colon}
: **ValueError** -- missing objects
```{=html}
```
[[section]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[obj:]{.pre} [\~build123d.topology.composite.Part]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[section_by:]{.pre} [\~build123d.geometry.Plane]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.geometry.Plane\]]{.pre} [=]{.pre} [Plane(o=(0.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[0.00)]{.pre}]{.n}*, *[[x=(1.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[0.00)]{.pre}]{.n}*, *[[z=(0.00]{.pre}]{.n}*, *[[-1.00]{.pre}]{.n}*, *[[0.00))]{.pre}]{.n}*, *[[height:]{.pre} [float]{.pre} [=]{.pre} [0.0]{.pre}]{.n}*, *[[clean:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Sketch]{.pre}]{.sig-return-typehint}]{.sig-return}
: Part Operation: section
Slices current part at the given height by section_by or current workplane(s).
Parameters[:]{.colon}
: - **obj** ([*Part*](#direct_api_reference.xhtml#topology.Part "topology.Part"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- object to section. Defaults to None.
- **section_by** ([*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- plane(s) to section object. Defaults to None.
- **height** (*float,* *optional*) -- workplane offset. Defaults to 0.0.
- **clean** (*bool,* *optional*) -- Remove extraneous internal structure. Defaults to True.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.INTERSECT.
```{=html}
```
[[split]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[bisect_by:]{.pre} [\~build123d.geometry.Plane]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Shell]{.pre} [=]{.pre} [Plane(o=(0.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[0.00)]{.pre}]{.n}*, *[[x=(1.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[0.00)]{.pre}]{.n}*, *[[z=(0.00]{.pre}]{.n}*, *[[-1.00]{.pre}]{.n}*, *[[0.00))]{.pre}]{.n}*, *[[keep:]{.pre} [\~build123d.build_enums.Keep]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Generic Operation: split
Applies to 1, 2, and 3 dimensional objects.
Bisect object with plane and keep either top, bottom or both.
Parameters[:]{.colon}
: - **objects** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal} *or* *Iterable of*)
- **bisect_by** ([*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- plane to segment part. Defaults to Plane.XZ.
- **keep** ([*Keep*](#builder_api_reference.xhtml#build_enums.Keep "build_enums.Keep"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- selector for which segment to keep. Defaults to Keep.TOP.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.REPLACE.
Raises[:]{.colon}
: **ValueError** -- missing objects
```{=html}
```
[[sweep]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[sections:]{.pre} [\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.composite.Compound]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.three_d.Solid\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[path:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.one_d.Edge\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[multisection:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[is_frenet:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[transition:]{.pre} [\~build123d.build_enums.Transition]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[normal:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[binormal:]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[clean:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Part]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sketch]{.pre}]{.sig-return-typehint}]{.sig-return}
: Generic Operation: sweep
Sweep pending 1D or 2D objects along path.
Parameters[:]{.colon}
: - **sections** ([*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- cross sections to sweep into object
- **path** ([*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- path to follow. Defaults to context pending_edges.
- **multisection** (*bool,* *optional*) -- sweep multiple on path. Defaults to False.
- **is_frenet** (*bool,* *optional*) -- use frenet algorithm. Defaults to False.
- **transition** ([*Transition*](#builder_api_reference.xhtml#build_enums.Transition "build_enums.Transition"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- discontinuity handling option. Defaults to Transition.TRANSFORMED.
- **normal** (*VectorLike,* *optional*) -- fixed normal. Defaults to None.
- **binormal** ([*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- guide rotation along path. Defaults to None.
- **clean** (*bool,* *optional*) -- Remove extraneous internal structure. Defaults to True.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination. Defaults to Mode.ADD.
```{=html}
```
[[thicken]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[to_thicken:]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.topology.composite.Sketch]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[amount:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[normal_override:]{.pre} [\~build123d.geometry.Vector]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [tuple\[float]{.pre}]{.n}*, *[[float]{.pre}]{.n}*, *[[float\]]{.pre} [\|]{.pre} [\~collections.abc.Sequence\[float\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[both:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[clean:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Part]{.pre}]{.sig-return-typehint}]{.sig-return}
: Part Operation: thicken
Create a solid(s) from a potentially non planar face(s) by thickening along the normals.
Parameters[:]{.colon}
: - **to_thicken** (*Union\[*[*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Sketch*](#direct_api_reference.xhtml#topology.Sketch "topology.Sketch"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- object to thicken. Defaults to None.
- **amount** (*float*) -- distance to extrude, sign controls direction.
- **normal_override** ([*Vector*](#direct_api_reference.xhtml#geometry.Vector "geometry.Vector"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- The normal_override vector can be used to indicate which way is 'up', potentially flipping the face normal direction such that many faces with different normals all go in the same direction (direction need only be +/- 90 degrees from the face normal). Defaults to None.
- **both** (*bool,* *optional*) -- thicken in both directions. Defaults to False.
- **clean** (*bool,* *optional*) -- Remove extraneous internal structure. Defaults to True.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
Raises[:]{.colon}
: - **ValueError** -- No object to extrude
- **ValueError** -- No target object
Returns[:]{.colon}
: extruded object
Return type[:]{.colon}
: [*Part*](#direct_api_reference.xhtml#topology.Part "topology.Part"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[trace]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[lines:]{.pre} [\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.composite.Curve]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Edge]{.pre} [\|]{.pre} [\~build123d.topology.one_d.Wire\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[line_width:]{.pre} [float]{.pre} [=]{.pre} [1]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Sketch]{.pre}]{.sig-return-typehint}]{.sig-return}
: Sketch Operation: trace
Convert edges, wires or pending edges into faces by sweeping a perpendicular line along them.
Parameters[:]{.colon}
: - **lines** ([*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *Iterable\[*[*Curve*](#direct_api_reference.xhtml#topology.Curve "topology.Curve"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]\],* *optional*) -- lines to trace. Defaults to sketch pending edges.
- **line_width** (*float,* *optional*) -- Defaults to 1.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
Raises[:]{.colon}
: **ValueError** -- No objects to trace
Returns[:]{.colon}
: Traced lines
Return type[:]{.colon}
: [*Sketch*](#direct_api_reference.xhtml#topology.Sketch "topology.Sketch"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[edge]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Edge]{.pre}]{.sig-return-typehint}]{.sig-return}
: Return Edge
Return an edge.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Edge selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Edge extracted
Return type[:]{.colon}
: [*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[edges]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[ShapeList]{.pre}[[\[]{.pre}]{.p}[Edge]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Return Edges
Return either all or the edges created during the last operation.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Edge selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Edges extracted
Return type[:]{.colon}
: [*ShapeList*](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}\[[*Edge*](#direct_api_reference.xhtml#topology.Edge "topology.Edge"){.hxr-hoverxref .hxr-tooltip .reference .internal}\]
```{=html}
```
[[face]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Face]{.pre}]{.sig-return-typehint}]{.sig-return}
: Return Face
Return a face.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Face selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Face extracted
Return type[:]{.colon}
: [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[faces]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[ShapeList]{.pre}[[\[]{.pre}]{.p}[Face]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Return Faces
Return either all or the faces created during the last operation.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Face selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Faces extracted
Return type[:]{.colon}
: [*ShapeList*](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}\[[*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}\]
```{=html}
```
[[solid]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Solid]{.pre}]{.sig-return-typehint}]{.sig-return}
: Return Solid
Return a solid.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Solid selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Solid extracted
Return type[:]{.colon}
: [*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[solids]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[ShapeList]{.pre}[[\[]{.pre}]{.p}[Solid]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Return Solids
Return either all or the solids created during the last operation.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Solid selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Solids extracted
Return type[:]{.colon}
: [*ShapeList*](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}\[[*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}\]
```{=html}
```
[[vertex]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Vertex]{.pre}]{.sig-return-typehint}]{.sig-return}
: Return Vertex
Return a vertex.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Vertex selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Vertex extracted
Return type[:]{.colon}
: [*Vertex*](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[vertices]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[ShapeList]{.pre}[[\[]{.pre}]{.p}[Vertex]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Return Vertices
Return either all or the vertices created during the last operation.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Vertex selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Vertices extracted
Return type[:]{.colon}
: [*ShapeList*](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}\[[*Vertex*](#direct_api_reference.xhtml#topology.Vertex "topology.Vertex"){.hxr-hoverxref .hxr-tooltip .reference .internal}\]
```{=html}
```
[[wire]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Wire]{.pre}]{.sig-return-typehint}]{.sig-return}
: Return Wire
Return a wire.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Wire selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Wire extracted
Return type[:]{.colon}
: [*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[wires]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[self]{.pre}]{.n}*, *[[select:]{.pre} [\~build123d.build_enums.Select]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[ShapeList]{.pre}[[\[]{.pre}]{.p}[Wire]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Return Wires
Return either all or the wires created during the last operation.
Parameters[:]{.colon}
: **select** ([*Select*](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- Wire selector. Defaults to Select.ALL.
Returns[:]{.colon}
: Wires extracted
Return type[:]{.colon}
: [*ShapeList*](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}\[[*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}\]
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#topology_selection.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#topology_selection.xhtml#topology-selection-and-exploration .section}
# Topology Selection and Exploration
[[Topology]{.std .std-ref}](#key_concepts.xhtml#topology){.reference .internal} is the structure of build123d geometric features and traversing the topology of a part is often required to specify objects for an operation or to locate a CAD feature. [[Selectors]{.std .std-ref}](#topology_selection.xhtml#selectors){.reference .internal} allow selection of topology objects into a [`ShapeList`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}. [[Operators]{.std .std-ref}](#topology_selection.xhtml#operators){.reference .internal} are powerful methods further explore and refine a [`ShapeList`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal} for subsequent operations.
::: {#topology_selection.xhtml#selectors .section}
[]{#topology_selection.xhtml#id1}
## Selectors
Selectors provide methods to extract all or a subset of a feature type in the referenced object. These methods select Edges, Faces, Solids, Vertices, or Wires in Builder objects or from Shape objects themselves. All of these methods return a [`ShapeList`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}, which is a subclass of `list`{.docutils .literal .notranslate} and may be sorted, grouped, or filtered by [[Operators]{.std .std-ref}](#topology_selection.xhtml#operators){.reference .internal}.
::: {#topology_selection.xhtml#overview .section}
### Overview
Selector Criteria Applicability Description
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------- -------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------
[`vertices()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.vertices "topology.Shape.vertices"){.hxr-hoverxref .hxr-tooltip .reference .internal} ALL, LAST `BuildLine`{.docutils .literal .notranslate}, `BuildSketch`{.docutils .literal .notranslate}, `BuildPart`{.docutils .literal .notranslate} `Vertex`{.docutils .literal .notranslate} extraction
[`edges()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.edges "topology.Shape.edges"){.hxr-hoverxref .hxr-tooltip .reference .internal} ALL, LAST, NEW `BuildLine`{.docutils .literal .notranslate}, `BuildSketch`{.docutils .literal .notranslate}, `BuildPart`{.docutils .literal .notranslate} `Edge`{.docutils .literal .notranslate} extraction
[`wires()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.wires "topology.Shape.wires"){.hxr-hoverxref .hxr-tooltip .reference .internal} ALL, LAST `BuildLine`{.docutils .literal .notranslate}, `BuildSketch`{.docutils .literal .notranslate}, `BuildPart`{.docutils .literal .notranslate} `Wire`{.docutils .literal .notranslate} extraction
[`faces()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.faces "topology.Shape.faces"){.hxr-hoverxref .hxr-tooltip .reference .internal} ALL, LAST `BuildSketch`{.docutils .literal .notranslate}, `BuildPart`{.docutils .literal .notranslate} `Face`{.docutils .literal .notranslate} extraction
[`solids()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.solids "topology.Shape.solids"){.hxr-hoverxref .hxr-tooltip .reference .internal} ALL, LAST `BuildPart`{.docutils .literal .notranslate} `Solid`{.docutils .literal .notranslate} extraction
Both shape objects and builder objects have access to selector methods to select all of a feature as long as they can contain the feature being selected.
::: {.highlight-build123d .notranslate}
::: highlight
# In context
with BuildSketch() as context:
Rectangle(1, 1)
context.edges()
# Build context implicitly has access to the selector
edges()
# Taking the sketch out of context
context.sketch.edges()
# Create sketch out of context
Rectangle(1, 1).edges()
:::
:::
:::
::: {#topology_selection.xhtml#select-in-build-context .section}
### Select In Build Context
Build contexts track the last operation and their selector methods can take [`Select`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal} as criteria to specify a subset of features to extract. By default, a selector will select `ALL`{.docutils .literal .notranslate} of a feature, while `LAST`{.docutils .literal .notranslate} selects features created or altered by the most recent operation. [`edges()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.edges "topology.Shape.edges"){.hxr-hoverxref .hxr-tooltip .reference .internal} can uniquely specify `NEW`{.docutils .literal .notranslate} to only select edges created in the last operation which neither existed in the referenced object before the last operation, nor the modifying object.
::: {.admonition .important}
Important
[`Select`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.Select "build_enums.Select"){.hxr-hoverxref .hxr-tooltip .reference .internal} as selector criteria is only valid for builder objects!
::: {.highlight-build123d .notranslate}
::: highlight
# In context
with BuildPart() as context:
Box(2, 2, 1)
Cylinder(1, 2)
context.edges(Select.LAST)
# Does not work out of context!
context.part.edges(Select.LAST)
(Box(2, 2, 1) + Cylinder(1, 2)).edges(Select.LAST)
:::
:::
:::
Create a simple part to demonstrate selectors. Select using the default criteria `Select.ALL`{.docutils .literal .notranslate}. Specifying `Select.ALL`{.docutils .literal .notranslate} for the selector is not required.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as part:
Box(5, 5, 1)
Cylinder(1, 5)
part.vertices()
part.edges()
part.faces()
# Is the same as
part.vertices(Select.ALL)
part.edges(Select.ALL)
part.faces(Select.ALL)
:::
:::
![[The default `Select.ALL`{.docutils .literal .notranslate} features]{.caption-text}](_images/selectors_select_all.png)
Select features changed in the last operation with criteria `Select.LAST`{.docutils .literal .notranslate}.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as part:
Box(5, 5, 1)
Cylinder(1, 5)
part.vertices(Select.LAST)
part.edges(Select.LAST)
part.faces(Select.LAST)
:::
:::
![[`Select.LAST`{.docutils .literal .notranslate} features]{.caption-text}](_images/selectors_select_last.png)
Select only new edges from the last operation with `Select.NEW`{.docutils .literal .notranslate}. This option is only available for a `ShapeList`{.docutils .literal .notranslate} of edges!
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as part:
Box(5, 5, 1)
Cylinder(1, 5)
part.edges(Select.NEW)
:::
:::
![[`Select.NEW`{.docutils .literal .notranslate} edges where box and cylinder intersect]{.caption-text}](_images/selectors_select_new.png)
This only returns new edges which are not reused from Box or Cylinder, in this case where the objects ``{=html}intersect``{=html}. But what happens if the objects don't intersect and all the edges are reused?
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as part:
Box(5, 5, 1, align=(Align.CENTER, Align.CENTER, Align.MAX))
Cylinder(2, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))
part.edges(Select.NEW)
:::
:::
![[`Select.NEW`{.docutils .literal .notranslate} edges when box and cylinder don't intersect]{.caption-text}](_images/selectors_select_new_none.png)
No edges are selected! Unlike the previous example, the Edge between the Box and Cylinder objects is an edge reused from the Cylinder. Think of `Select.NEW`{.docutils .literal .notranslate} as a way to select only completely new edges created by the operation.
::: {.admonition .note}
Note
Chamfer and fillet modify the current object, but do not have new edges via `Select.NEW`{.docutils .literal .notranslate}.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as part:
Box(5, 5, 1)
Cylinder(1, 5)
edges = part.edges().filter_by(lambda a: a.length == 1)
fillet(edges, 1)
part.edges(Select.NEW)
:::
:::
![[Left, `Select.NEW`{.docutils .literal .notranslate} returns no edges after fillet. Right, `Select.LAST`{.docutils .literal .notranslate}]{.caption-text}](_images/selectors_select_new_fillet.png)
:::
:::
::: {#topology_selection.xhtml#select-new-edges-in-algebra-mode .section}
### Select New Edges In Algebra Mode
The utility method `new_edges`{.docutils .literal .notranslate} compares one or more shape objects to a another "combined" shape object and returns the edges new to the combined shape. `new_edges`{.docutils .literal .notranslate} is available both Algebra mode or Builder mode, but is necessary in Algebra Mode where `Select.NEW`{.docutils .literal .notranslate} is unavailable
::: {.highlight-build123d .notranslate}
::: highlight
box = Box(5, 5, 1)
circle = Cylinder(2, 5)
part = box + circle
edges = new_edges(box, circle, combined=part)
:::
:::

`new_edges`{.docutils .literal .notranslate} can also find edges created during a chamfer or fillet operation by comparing the object before the operation to the "combined" object.
::: {.highlight-build123d .notranslate}
::: highlight
box = Box(5, 5, 1)
circle = Cylinder(2, 5)
part_before = box + circle
edges = part_before.edges().filter_by(lambda a: a.length == 1)
part = fillet(edges, 1)
edges = new_edges(part_before, combined=part)
:::
:::

:::
:::
::: {#topology_selection.xhtml#operators .section}
[]{#topology_selection.xhtml#id2}
## Operators
Operators provide methods refine a `ShapeList`{.docutils .literal .notranslate} of features isolated by a *selector* to further specify feature(s). These methods can sort, group, or filter `ShapeList`{.docutils .literal .notranslate} objects and return a modified `ShapeList`{.docutils .literal .notranslate}, or in the case of [`group_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.group_by "topology.ShapeList.group_by"){.hxr-hoverxref .hxr-tooltip .reference .internal}, `GroupBy`{.docutils .literal .notranslate}, a list of `ShapeList`{.docutils .literal .notranslate} objects accessible by index or key.
::: {#topology_selection.xhtml#id3 .section}
### Overview
Method Criteria Description
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------
[`sort_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.sort_by "topology.ShapeList.sort_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} `Axis`{.docutils .literal .notranslate}, `Edge`{.docutils .literal .notranslate}, `Wire`{.docutils .literal .notranslate}, `SortBy`{.docutils .literal .notranslate}, callable, property Sort `ShapeList`{.docutils .literal .notranslate} by criteria
[`sort_by_distance()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.sort_by_distance "topology.ShapeList.sort_by_distance"){.hxr-hoverxref .hxr-tooltip .reference .internal} `Shape`{.docutils .literal .notranslate}, `VectorLike`{.docutils .literal .notranslate} Sort `ShapeList`{.docutils .literal .notranslate} by distance from criteria
[`group_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.group_by "topology.ShapeList.group_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} `Axis`{.docutils .literal .notranslate}, `Edge`{.docutils .literal .notranslate}, `Wire`{.docutils .literal .notranslate}, `SortBy`{.docutils .literal .notranslate}, callable, property Group `ShapeList`{.docutils .literal .notranslate} by criteria
[`filter_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.filter_by "topology.ShapeList.filter_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} `Axis`{.docutils .literal .notranslate}, `Plane`{.docutils .literal .notranslate}, `GeomType`{.docutils .literal .notranslate}, `ShapePredicate`{.docutils .literal .notranslate}, property Filter `ShapeList`{.docutils .literal .notranslate} by criteria
[`filter_by_position()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.filter_by_position "topology.ShapeList.filter_by_position"){.hxr-hoverxref .hxr-tooltip .reference .internal} `Axis`{.docutils .literal .notranslate} Filter `ShapeList`{.docutils .literal .notranslate} by `Axis`{.docutils .literal .notranslate} & mix / max values
Operator methods take criteria to refine `ShapeList`{.docutils .literal .notranslate}. Broadly speaking, the criteria fall into the following categories, though not all operators take all criteria:
- Geometric objects: `Axis`{.docutils .literal .notranslate}, `Plane`{.docutils .literal .notranslate}
- Topological objects: `Edge`{.docutils .literal .notranslate}, `Wire`{.docutils .literal .notranslate}
- Enums: [`SortBy`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.SortBy "build_enums.SortBy"){.hxr-hoverxref .hxr-tooltip .reference .internal}, [`GeomType`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_enums.GeomType "build_enums.GeomType"){.hxr-hoverxref .hxr-tooltip .reference .internal}
- Properties, eg: `Face.area`{.docutils .literal .notranslate}, `Edge.length`{.docutils .literal .notranslate}
- `ShapePredicate`{.docutils .literal .notranslate}, eg: `lambda e: e.is_interior == 1`{.docutils .literal .notranslate}, `lambda f: lf.edges() >= 3`{.docutils .literal .notranslate}
- Callable eg: `Vertex().distance`{.docutils .literal .notranslate}
:::
::: {#topology_selection.xhtml#sort .section}
### Sort
A `ShapeList`{.docutils .literal .notranslate} can be sorted with the [`sort_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.sort_by "topology.ShapeList.sort_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} and [`sort_by_distance()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.sort_by_distance "topology.ShapeList.sort_by_distance"){.hxr-hoverxref .hxr-tooltip .reference .internal} methods based on a sorting criteria. Sorting is a critical step when isolating individual features as a `ShapeList`{.docutils .literal .notranslate} from a selector is typically unordered.
Here we want to capture some vertices from the object furthest along `X`{.docutils .literal .notranslate}: All the vertices are first captured with the [`vertices()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.vertices "topology.Shape.vertices"){.hxr-hoverxref .hxr-tooltip .reference .internal} selector, then sort by `Axis.X`{.docutils .literal .notranslate}. Finally, the vertices can be captured with a list slice for the last 4 list items, as the items are sorted from least to greatest `X`{.docutils .literal .notranslate} position. Remember, `ShapeList`{.docutils .literal .notranslate} is a subclass of `list`{.docutils .literal .notranslate}, so any list slice can be used.
::: {.highlight-build123d .notranslate}
::: highlight
part.vertices().sort_by(Axis.X)[-4:]
:::
:::

::: line-block
::: line
\
:::
:::
::: {#topology_selection.xhtml#examples .section}
#### Examples
::: {.toctree-wrapper .compound}
:::
::: {.sd-container-fluid .sd-sphinx-override .sd-mb-4 .docutils}
::: {.sd-row .sd-row-cols-3 .sd-row-cols-xs-3 .sd-row-cols-sm-3 .sd-row-cols-md-3 .sd-row-cols-lg-3 .sd-g-3 .sd-g-xs-3 .sd-g-sm-3 .sd-g-md-3 .sd-g-lg-3 .docutils}
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
SortBy
:::
:::
[[SortBy]{.std .std-ref}](topology_selection/sort_examples.xhtml#sort-sortby){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Along Wire
:::
:::
[[Along Wire]{.std .std-ref}](topology_selection/sort_examples.xhtml#sort-along-wire){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Axis
:::
:::
[[Axis]{.std .std-ref}](topology_selection/sort_examples.xhtml#sort-axis){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Distance From
:::
:::
[[Distance From]{.std .std-ref}](topology_selection/sort_examples.xhtml#sort-distance-from){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
:::
:::
:::
:::
::: {#topology_selection.xhtml#group .section}
### Group
A ShapeList can be grouped and sorted with the [`group_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.group_by "topology.ShapeList.group_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} method based on a grouping criteria. Grouping can be a great way to organize features without knowing the values of specific feature properties. Rather than returning a `ShapeList`{.docutils .literal .notranslate}, [`group_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.group_by "topology.ShapeList.group_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} returns a `GroupBy`{.docutils .literal .notranslate}, a list of `ShapeList`{.docutils .literal .notranslate} objects sorted by the grouping criteria. `GroupBy`{.docutils .literal .notranslate} can be printed to view the members of each group, indexed like a list to retrieve a `ShapeList`{.docutils .literal .notranslate}, and be accessed using a key with the `group`{.docutils .literal .notranslate} method. If the group keys are unknown they can be discovered with `key_to_group_index`{.docutils .literal .notranslate}.
If we want only the edges from the smallest faces by area we can get the faces, then group by `SortBy.AREA`{.docutils .literal .notranslate}. The `ShapeList`{.docutils .literal .notranslate} of smallest faces is available from the first list index. Finally, a `ShapeList`{.docutils .literal .notranslate} has access to selectors, so calling [`edges()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.edges "topology.Shape.edges"){.hxr-hoverxref .hxr-tooltip .reference .internal} will return a new list of all edges in the previous list.
::: {.highlight-build123d .notranslate}
::: highlight
part.faces().group_by(SortBy.AREA)[0].edges())
:::
:::

::: line-block
::: line
\
:::
:::
::: {#topology_selection.xhtml#id4 .section}
#### Examples
::: {.toctree-wrapper .compound}
:::
::: {.sd-container-fluid .sd-sphinx-override .sd-mb-4 .docutils}
::: {.sd-row .sd-row-cols-3 .sd-row-cols-xs-3 .sd-row-cols-sm-3 .sd-row-cols-md-3 .sd-row-cols-lg-3 .sd-g-3 .sd-g-xs-3 .sd-g-sm-3 .sd-g-md-3 .sd-g-lg-3 .docutils}
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Axis and Length
:::
:::
[[Axis and Length]{.std .std-ref}](topology_selection/group_examples.xhtml#group-axis){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Hole Area
:::
:::
[[Hole Area]{.std .std-ref}](topology_selection/group_examples.xhtml#group-hole-area){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Properties with Keys
:::
:::
[[Properties with Keys]{.std .std-ref}](topology_selection/group_examples.xhtml#group-properties-with-keys){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
:::
:::
:::
:::
::: {#topology_selection.xhtml#filter .section}
### Filter
A `ShapeList`{.docutils .literal .notranslate} can be filtered with the [`filter_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.filter_by "topology.ShapeList.filter_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} and [`filter_by_position()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.filter_by_position "topology.ShapeList.filter_by_position"){.hxr-hoverxref .hxr-tooltip .reference .internal} methods based on a filtering criteria. Filters are flexible way to isolate (or exclude) features based on known criteria.
Lets say we need all the faces with a normal in the `+Z`{.docutils .literal .notranslate} direction. One way to do this might be with a list comprehension, however [`filter_by()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.ShapeList.filter_by "topology.ShapeList.filter_by"){.hxr-hoverxref .hxr-tooltip .reference .internal} has the capability to take a lambda function as a filter condition on the entire list. In this case, the normal of each face can be checked against a vector direction and filtered accordingly.
::: {.highlight-build123d .notranslate}
::: highlight
part.faces().filter_by(lambda f: f.normal_at() == Vector(0, 0, 1))
:::
:::

::: line-block
::: line
\
:::
:::
::: {#topology_selection.xhtml#id5 .section}
#### Examples
::: {.toctree-wrapper .compound}
:::
::: {.sd-container-fluid .sd-sphinx-override .sd-mb-4 .docutils}
::: {.sd-row .sd-row-cols-3 .sd-row-cols-xs-3 .sd-row-cols-sm-3 .sd-row-cols-md-3 .sd-row-cols-lg-3 .sd-g-3 .sd-g-xs-3 .sd-g-sm-3 .sd-g-md-3 .sd-g-lg-3 .docutils}
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
GeomType
:::
:::
[[GeomType]{.std .std-ref}](topology_selection/filter_examples.xhtml#filter-geomtype){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
All Edges Circle
:::
:::
[[All Edges Circle]{.std .std-ref}](topology_selection/filter_examples.xhtml#filter-all-edges-circle){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Axis and Plane
:::
:::
[[Axis and Plane]{.std .std-ref}](topology_selection/filter_examples.xhtml#filter-axis-plane){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Inner Wire Count
:::
:::
[[Inner Wire Count]{.std .std-ref}](topology_selection/filter_examples.xhtml#filter-inner-wire-count){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Nested Filters
:::
:::
[[Nested Filters]{.std .std-ref}](topology_selection/filter_examples.xhtml#filter-nested){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
::: {.sd-col .sd-d-flex-row .docutils}
::: {.sd-card .sd-sphinx-override .sd-w-100 .sd-shadow-sm .sd-card-hover .docutils}
{.sd-card-img-top}
::: {.sd-card-body .docutils}
::: {.sd-card-title .sd-font-weight-bold .docutils}
Shape Properties
:::
:::
[[Shape Properties]{.std .std-ref}](topology_selection/filter_examples.xhtml#filter-shape-properties){.sd-stretched-link .sd-hide-link-text .reference .internal}
:::
:::
:::
:::
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#sort_examples.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#sort_examples.xhtml#sort-examples .section}
# Sort Examples
::: {#sort_examples.xhtml#sortby .section}
[]{#sort_examples.xhtml#sort-sortby}
## SortBy
[`SortBy`{.xref .py .py-class .docutils .literal .notranslate}](../builder_api_reference.xhtml#build_enums.SortBy "build_enums.SortBy"){.hxr-hoverxref .hxr-tooltip .reference .internal} enums are shape property shorthands which work across `Shape`{.docutils .literal .notranslate} multiple object types. `SortBy`{.docutils .literal .notranslate} is a criteria for both `sort_by`{.docutils .literal .notranslate} and `group_by`{.docutils .literal .notranslate}.
- `SortBy.LENGTH`{.docutils .literal .notranslate} works with `Edge`{.docutils .literal .notranslate}, `Wire`{.docutils .literal .notranslate}
- `SortBy.AREA`{.docutils .literal .notranslate} works with `Face`{.docutils .literal .notranslate}, `Solid`{.docutils .literal .notranslate}
- `SortBy.VOLUME`{.docutils .literal .notranslate} works with `Solid`{.docutils .literal .notranslate}
- `SortBy.RADIUS`{.docutils .literal .notranslate} works with `Edge`{.docutils .literal .notranslate}, `Face`{.docutils .literal .notranslate} with [`GeomType`{.xref .py .py-class .docutils .literal .notranslate}](../builder_api_reference.xhtml#build_enums.GeomType "build_enums.GeomType"){.hxr-hoverxref .hxr-tooltip .reference .internal} `CIRCLE`{.docutils .literal .notranslate}, `CYLINDER`{.docutils .literal .notranslate}, `SPHERE`{.docutils .literal .notranslate}
- `SortBy.DISTANCE`{.docutils .literal .notranslate} works `Vertex`{.docutils .literal .notranslate}, `Edge`{.docutils .literal .notranslate}, `Wire`{.docutils .literal .notranslate}, `Face`{.docutils .literal .notranslate}, `Solid`{.docutils .literal .notranslate}
`SortBy`{.docutils .literal .notranslate} is often interchangeable with specific shape properties and can alternatively be used with\`\`group_by\`\`.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as part:
Box(5, 5, 1)
Cylinder(2, 5)
edges = part.edges().filter_by(lambda a: a.length == 1)
fillet(edges, 1)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
part.wires().sort_by(SortBy.LENGTH)[:4]
part.wires().sort_by(Wire.length)[:4]
part.wires().group_by(SortBy.LENGTH)[0]
:::
:::

::: line-block
::: line
\
:::
:::
::: {.highlight-build123d .notranslate}
::: highlight
part.vertices().sort_by(SortBy.DISTANCE)[-2:]
part.vertices().sort_by_distance(Vertex())[-2:]
part.vertices().group_by(Vertex().distance)[-1]
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#sort_examples.xhtml#along-wire .section}
[]{#sort_examples.xhtml#sort-along-wire}
## Along Wire
Vertices selected from an edge or wire might have a useful ordering when created from a single object, but when created from multiple objects, the ordering not useful. For example, when applying incrementing fillet radii to a list of vertices from the face, the order is random.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildSketch() as along_wire:
Rectangle(48, 16, align=Align.MIN)
Rectangle(16, 48, align=Align.MIN)
Rectangle(32, 32, align=Align.MIN)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
for i, v in enumerate(along_wire.vertices()):
fillet(v, i + 1)
:::
:::

::: line-block
::: line
\
:::
:::
Vertices may be sorted along the wire they fall on to create order. Notice the fillet radii now increase in order.
::: {.highlight-build123d .notranslate}
::: highlight
sorted_verts = along_wire.vertices().sort_by(along_wire.wire())
for i, v in enumerate(sorted_verts):
fillet(v, i + 1)
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#sort_examples.xhtml#axis .section}
[]{#sort_examples.xhtml#sort-axis}
## Axis
Sorting by axis is often the most straightforward way to optimize selections. In this part we want to revolve the face at the end around an inside edge of the completed extrusion. First, the face to extrude can be found by sorting along x-axis and the revolution edge can be found sorting along y-axis.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as part:
with BuildSketch(Plane.YZ) as profile:
with BuildLine():
l1 = FilletPolyline((16, 0), (32, 0), (32, 25), radius=12)
l2 = FilletPolyline((16, 4), (28, 4), (28, 15), radius=8)
Line(l1 @ 0, l2 @ 0)
Polyline(l1 @ 1, l1 @ 1 - Vector(2, 0), l2 @ 1 + Vector(2, 0), l2 @ 1)
make_face()
extrude(amount=34)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
face = part.faces().sort_by(Axis.X)[-1]
edge = face.edges().sort_by(Axis.Y)[0]
revolve(face, -Axis(edge), 90)
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#sort_examples.xhtml#distance-from .section}
[]{#sort_examples.xhtml#sort-distance-from}
## Distance From
A `sort_by_distance`{.docutils .literal .notranslate} can be used to sort objects by their distance from another object. Here we are sorting the boxes by distance from the origin, using an empty `Vertex`{.docutils .literal .notranslate} (at the origin) as the reference shape to find distance to.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from itertools import product
from build123d import *
from ocp_vscode import *
boxes = ShapeList(
Box(1, 1, 1).scale(0.75 if (i, j) == (1, 2) else 0.25).translate((i, j, 0))
for i, j in product(range(-3, 4), repeat=2)
)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
boxes = boxes.sort_by_distance(Vertex())
show(*boxes, colors=ColorMap.listed(len(boxes)))
:::
:::

::: line-block
::: line
\
:::
:::
The example can be extended by first sorting the boxes by volume using the `Solid`{.docutils .literal .notranslate} property `volume`{.docutils .literal .notranslate}, and getting the last (largest) box. Then, the boxes sorted by their distance from the largest box.
::: {.highlight-build123d .notranslate}
::: highlight
boxes = boxes.sort_by_distance(boxes.sort_by(Solid.volume).last)
show(*boxes, colors=ColorMap.listed(len(boxes)))
:::
:::

::: line-block
::: line
\
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#group_examples.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#group_examples.xhtml#group-examples .section}
# Group Examples
::: {#group_examples.xhtml#axis-and-length .section}
[]{#group_examples.xhtml#group-axis}
## Axis and Length
This heatsink component could use fillets on the ends of the fins on the long ends. One way to accomplish this is to filter by length, sort by axis, and slice the result knowing how many edges to expect.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as fins:
with GridLocations(4, 6, 4, 4):
Box(2, 3, 10, align=(Align.CENTER, Align.CENTER, Align.MIN))
with BuildPart() as part:
Box(34, 48, 5, align=(Align.CENTER, Align.CENTER, Align.MAX))
with GridLocations(20, 27, 2, 2):
add(fins)
:::
:::
:::
```{=html}
```

::: line-block
::: line
\
:::
:::
However, `group_by`{.docutils .literal .notranslate} can be used to first group all the edges by z-axis position and then group again by length. In both cases, you can select the desired edges from the last group.
::: {.highlight-build123d .notranslate}
::: highlight
target = part.edges().group_by(Axis.Z)[-1].group_by(Edge.length)[-1]
fillet(target, .75)
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#group_examples.xhtml#hole-area .section}
[]{#group_examples.xhtml#group-hole-area}
## Hole Area
Callables are available to `group_by`{.docutils .literal .notranslate}, like `sort_by`{.docutils .literal .notranslate}. Here, the first inner wire is converted to a face and then that area is the grouping criteria to find the faces with the largest hole.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as part:
Cylinder(10, 30, rotation=(90, 0, 0))
Cylinder(8, 40, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MAX))
Cylinder(8, 23, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MIN))
Cylinder(5, 40, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MIN))
with BuildSketch(Plane.XY.offset(8)) as s:
SlotCenterPoint((0, 38), (0, 48), 5)
extrude(amount=2.5, both=True, mode=Mode.SUBTRACT)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
faces = part.faces().group_by(
lambda f: Face(f.inner_wires()[0]).area if f.inner_wires() else 0
)
chamfer([f.outer_wire().edges() for f in faces[-1]], 0.5)
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#group_examples.xhtml#properties-with-keys .section}
[]{#group_examples.xhtml#group-properties-with-keys}
## Properties with Keys
Groups are usually selected by list slice, often smallest `[0]`{.docutils .literal .notranslate} or largest `[-1]`{.docutils .literal .notranslate}, but they can also be selected by key with the `group`{.docutils .literal .notranslate} method if the keys are known. Starting with an incomplete bearing block we are looking to add fillets to the ribs and corners. We know the edge lengths so the edges can be grouped by `Edge.Length`{.docutils .literal .notranslate} and then the desired groups are selected with the `group`{.docutils .literal .notranslate} method using the lengths as keys.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as part:
with BuildSketch(Plane.XZ) as sketch:
with BuildLine():
CenterArc((-6, 12), 10, 0, 360)
Line((-16, 0), (16, 0))
make_hull()
Rectangle(50, 5, align=(Align.CENTER, Align.MAX))
extrude(amount=12)
Box(38, 6, 22, align=(Align.CENTER, Align.MAX, Align.MIN), mode=Mode.SUBTRACT)
circle = part.edges().filter_by(GeomType.CIRCLE).sort_by(Axis.Y)[0]
with Locations(Plane(circle.arc_center, z_dir=circle.normal())):
CounterBoreHole(13 / 2, 16 / 2, 4)
mirror(about=Plane.XZ)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
length_groups = part.edges().group_by(Edge.length)
fillet(length_groups.group(6) + length_groups.group(5), 4)
:::
:::

::: line-block
::: line
\
:::
:::
Next, we add alignment pin and counterbore holes after the fillets to make sure screw heads sit flush where they overlap the fillet. Once that is done, it's time to finalize the tight-tolerance bearing and pin holes with chamfers to make installation easier. We can filter by `GeomType.CIRCLE`{.docutils .literal .notranslate} and group by `Edge.radius`{.docutils .literal .notranslate} to group the circular edges. Again, the radii are known, so we can retrieve those groups directly and then further specify only the edges the bearings and pins are installed from.
```{=html}
```
```{=html}
```
[Adding holes]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch() as pins:
with Locations((-21, 0)):
Circle(3 / 2)
with Locations((21, 0)):
SlotCenterToCenter(1, 3)
extrude(amount=-12, mode=Mode.SUBTRACT)
with GridLocations(42, 16, 2, 2):
CounterBoreHole(3.5 / 2, 3.5, 0)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
radius_groups = part.edges().filter_by(GeomType.CIRCLE).group_by(Edge.radius)
bearing_edges = radius_groups.group(8).group_by(SortBy.DISTANCE)[-1]
pin_edges = radius_groups.group(1.5).filter_by_position(Axis.Z, -5, -5)
chamfer([pin_edges, bearing_edges], .5)
:::
:::

::: line-block
::: line
\
:::
:::
Note that `group_by`{.docutils .literal .notranslate} is not the only way to capture edges with a known property value! `filter_by`{.docutils .literal .notranslate} with a lambda expression can be used as well:
::: {.highlight-build123d .notranslate}
::: highlight
radius_groups = part.edges().filter_by(GeomType.CIRCLE)
bearing_edges = radius_groups.filter_by(lambda e: e.radius == 8)
pin_edges = radius_groups.filter_by(lambda e: e.radius == 1.5)
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#filter_examples.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#filter_examples.xhtml#filter-examples .section}
# Filter Examples
::: {#filter_examples.xhtml#geomtype .section}
[]{#filter_examples.xhtml#filter-geomtype}
## GeomType
[`GeomType`{.xref .py .py-class .docutils .literal .notranslate}](../builder_api_reference.xhtml#build_enums.GeomType "build_enums.GeomType"){.hxr-hoverxref .hxr-tooltip .reference .internal} enums are shape type shorthands for `Edge`{.docutils .literal .notranslate} and `Face`{.docutils .literal .notranslate} objects. They are most helpful for filtering objects of that specific type for further operations, and are sometimes necessary e.g. before sorting or filtering by radius. `Edge`{.docutils .literal .notranslate} and `Face`{.docutils .literal .notranslate} each support a subset of `GeomType`{.docutils .literal .notranslate}:
- `Edge`{.docutils .literal .notranslate} can be type `LINE`{.docutils .literal .notranslate}, `CIRCLE`{.docutils .literal .notranslate}, `ELLIPSE`{.docutils .literal .notranslate}, `HYPERBOLA`{.docutils .literal .notranslate}, `PARABOLA`{.docutils .literal .notranslate}, `BEZIER`{.docutils .literal .notranslate}, `BSPLINE`{.docutils .literal .notranslate}, `OFFSET`{.docutils .literal .notranslate}, `OTHER`{.docutils .literal .notranslate}
- `Face`{.docutils .literal .notranslate} can be type `PLANE`{.docutils .literal .notranslate}, `CYLINDER`{.docutils .literal .notranslate}, `CONE`{.docutils .literal .notranslate}, `SPHERE`{.docutils .literal .notranslate}, `TORUS`{.docutils .literal .notranslate}, `BEZIER`{.docutils .literal .notranslate}, `BSPLINE`{.docutils .literal .notranslate}, `REVOLUTION`{.docutils .literal .notranslate}, `EXTRUSION`{.docutils .literal .notranslate}, `OFFSET`{.docutils .literal .notranslate}, `OTHER`{.docutils .literal .notranslate}
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as part:
Box(5, 5, 1)
Cylinder(2, 5)
edges = part.edges().filter_by(lambda a: a.length == 1)
fillet(edges, 1)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
part.edges().filter_by(GeomType.LINE)
:::
:::

::: line-block
::: line
\
:::
:::
::: {.highlight-build123d .notranslate}
::: highlight
part.faces().filter_by(GeomType.CYLINDER)
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#filter_examples.xhtml#all-edges-circle .section}
[]{#filter_examples.xhtml#filter-all-edges-circle}
## All Edges Circle
In this complete bearing block, we want to add joints for the bearings. These should be located in the counterbore recess. One way to locate the joints is by finding faces with centers located where the joints need to be located. Filtering for faces with only circular edges selects the counterbore faces that meet the joint criteria.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as part:
with BuildSketch() as s:
Rectangle(115, 50)
with Locations((5 / 2, 0)):
SlotOverall(90, 12, mode=Mode.SUBTRACT)
extrude(amount=15)
with BuildSketch(Plane.XZ.offset(50 / 2)) as s3:
with Locations((-115 / 2 + 26, 15)):
SlotOverall(42 + 2 * 26 + 12, 2 * 26, rotation=90)
zz = extrude(amount=-12)
split(bisect_by=Plane.XY)
edgs = part.part.edges().filter_by(Axis.Y).group_by(Axis.X)[-2]
fillet(edgs, 9)
with Locations(zz.faces().sort_by(Axis.Y)[0]):
with Locations((42 / 2 + 6, 0)):
CounterBoreHole(24 / 2, 34 / 2, 4)
mirror(about=Plane.XZ)
with BuildSketch() as s4:
RectangleRounded(115, 50, 6)
extrude(amount=80, mode=Mode.INTERSECT)
# fillet does not work right, mode intersect is safer
with BuildSketch(Plane.YZ) as s4:
with BuildLine() as bl:
l1 = Line((0, 0), (18 / 2, 0))
l2 = PolarLine(l1 @ 1, 8, 60, length_mode=LengthMode.VERTICAL)
l3 = Line(l2 @ 1, (0, 8))
mirror(about=Plane.YZ)
make_face()
extrude(amount=115 / 2, both=True, mode=Mode.SUBTRACT)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
faces = part.faces().filter_by(
lambda f: all(e.geom_type == GeomType.CIRCLE for e in f.edges())
)
for i, f in enumerate(faces):
RigidJoint(f"bearing_bore_{i}", joint_location=f.center_location)
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#filter_examples.xhtml#axis-and-plane .section}
[]{#filter_examples.xhtml#filter-axis-plane}
## Axis and Plane
Filtering by an Axis will select faces perpendicular to the axis. Likewise filtering by Plane will select faces parallel to the plane.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as part:
Box(1, 1, 1)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
part.faces().filter_by(Axis.Z)
part.faces().filter_by(Plane.XY)
:::
:::

::: line-block
::: line
\
:::
:::
It might be useful to filter by an Axis or Plane in other ways. A lambda can be used to accomplish this with feature properties or methods. Here, we are looking for faces where the dot product of face normal and either the axis direction or the plane normal is about to 0. The result is faces parallel to the axis or perpendicular to the plane.
::: {.highlight-build123d .notranslate}
::: highlight
part.faces().filter_by(lambda f: abs(f.normal_at().dot(Axis.Z.direction) < 1e-6)
part.faces().filter_by(lambda f: abs(f.normal_at().dot(Plane.XY.z_dir)) < 1e-6)
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#filter_examples.xhtml#inner-wire-count .section}
[]{#filter_examples.xhtml#filter-inner-wire-count}
## Inner Wire Count
This motor bracket imported from a step file needs joints for adding to an assembly. Joints for the M3 clearance holes were already found by using the cylindrical face's axis of rotation, but the motor bore and slots need specific placement. The motor bore can be found by filtering for faces with 5 inner wires, sorting for the desired face, and then filtering for the specific inner wire by radius.
- bracket STEP model: `nema-17-bracket.step`{.xref .download .docutils .literal .notranslate}
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
bracket = import_step(os.path.join(working_path, "nema-17-bracket.step"))
faces = bracket.faces()
motor_mounts = faces.filter_by(GeomType.CYLINDER).filter_by(lambda f: f.radius == 3.3/2)
for i, f in enumerate(motor_mounts):
location = f.axis_of_rotation.location
RigidJoint(f"motor_m3_{i}", bracket, joint_location=location)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
motor_face = faces.filter_by(lambda f: len(f.inner_wires()) == 5).sort_by(Axis.X)[-1]
motor_bore = motor_face.inner_wires().edges().filter_by(lambda e: e.radius == 16).edge()
location = Location(motor_bore.arc_center, motor_bore.normal() * 90, Intrinsic.YXZ)
RigidJoint(f"motor", bracket, joint_location=location)
:::
:::

::: line-block
::: line
\
:::
:::
Linear joints for the slots are appropriate for mating flexibility, but require more than a single location. The slot arc centers can be used for creating a linear joint axis and range. To do that we can filter for faces with 6 inner wires, sort for and select the top face, and then filter for the circular edges of the inner wires.
::: {.highlight-build123d .notranslate}
::: highlight
mount_face = faces.filter_by(lambda f: len(f.inner_wires()) == 6).sort_by(Axis.Z)[-1]
mount_slots = mount_face.inner_wires().edges().filter_by(GeomType.CIRCLE)
joint_edges = [
Line(mount_slots[i].arc_center, mount_slots[i + 1].arc_center)
for i in range(0, len(mount_slots), 2)
]
for i, e in enumerate(joint_edges):
LinearJoint(f"mount_m4_{i}", bracket, axis=Axis(e), linear_range=(0, e.length / 2))
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#filter_examples.xhtml#nested-filters .section}
[]{#filter_examples.xhtml#filter-nested}
## Nested Filters
Filters can be nested to specify features by characteristics other than their own, like child properties. Here we want to chamfer the mating edges of the D bore and square shaft. A way to do this is first looking for faces with only 2 line edges among the inner wires. The nested filter captures the straight edges, while the parent filter selects faces based on the count. Then, from those faces, we filter for the wires with any line edges.
```{=html}
```
```{=html}
```
[Setup]{.sd-summary-text}
```{=html}
```
```{=html}
```
``{=html}
```{=html}
```
::: {.sd-summary-content .sd-card-body .docutils}
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
with BuildPart() as part:
Cylinder(15, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))
with BuildSketch():
RectangleRounded(10, 10, 2.5)
extrude(amount=15)
with BuildSketch():
Circle(2.5)
Rectangle(4, 5, mode=Mode.INTERSECT)
extrude(amount=15, mode=Mode.SUBTRACT)
with GridLocations(20, 0, 2, 1):
Hole(3.5 / 2)
:::
:::
:::
```{=html}
```
::: {.highlight-build123d .notranslate}
::: highlight
faces = part.faces().filter_by(
lambda f: len(f.inner_wires().edges().filter_by(GeomType.LINE)) == 2
)
wires = faces.wires().filter_by(
lambda w: any(e.geom_type == GeomType.LINE for e in w.edges())
)
chamfer(wires.edges(), 0.5)
:::
:::

::: line-block
::: line
\
:::
:::
:::
::: {#filter_examples.xhtml#shape-properties .section}
[]{#filter_examples.xhtml#filter-shape-properties}
## Shape Properties
Selected features can be quickly filtered by feature properties. First, we filter by interior and exterior edges using the `Edge`{.docutils .literal .notranslate} `is interior`{.docutils .literal .notranslate} property to apply different fillets accordingly. Then the `Face`{.docutils .literal .notranslate} `is_circular_*`{.docutils .literal .notranslate} properties are used to highlight the resulting fillets.
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
with BuildPart() as open_box_builder:
Box(20, 20, 5)
offset(amount=-2, openings=open_box_builder.faces().sort_by(Axis.Z)[-1])
inside_edges = open_box_builder.edges().filter_by(Edge.is_interior)
fillet(inside_edges, 1.5)
outside_edges = open_box_builder.edges().filter_by(Edge.is_interior, reverse=True)
fillet(outside_edges, 0.5)
open_box = open_box_builder.part
open_box.color = Color(0xEDAE49)
outside_fillets = Compound(open_box.faces().filter_by(Face.is_circular_convex))
outside_fillets.color = Color(0xD1495B)
inside_fillets = Compound(open_box.faces().filter_by(Face.is_circular_concave))
inside_fillets.color = Color(0x00798C)
:::
:::

::: line-block
::: line
\
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#builders.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#builders.xhtml#builders .section}
# Builders
The following sections describe each of the build123d stateful context builders.
::: {.toctree-wrapper .compound}
- [BuildLine](#build_line.xhtml){.reference .internal}
- [Basic Functionality](#build_line.xhtml#basic-functionality){.reference .internal}
- [Constraints](#build_line.xhtml#constraints){.reference .internal}
- [BuildLine to BuildSketch](#build_line.xhtml#buildline-to-buildsketch){.reference .internal}
- [BuildLine to BuildPart](#build_line.xhtml#buildline-to-buildpart){.reference .internal}
- [Working on other Planes](#build_line.xhtml#working-on-other-planes){.reference .internal}
- [Reference](#build_line.xhtml#module-build_line){.reference .internal}
- [BuildSketch](#build_sketch.xhtml){.reference .internal}
- [Basic Functionality](#build_sketch.xhtml#basic-functionality){.reference .internal}
- [Sketching on other Planes](#build_sketch.xhtml#sketching-on-other-planes){.reference .internal}
- [Local vs. Global Sketches](#build_sketch.xhtml#local-vs-global-sketches){.reference .internal}
- [Locating Features](#build_sketch.xhtml#locating-features){.reference .internal}
- [Reference](#build_sketch.xhtml#module-build_sketch){.reference .internal}
- [BuildPart](#build_part.xhtml){.reference .internal}
- [Basic Functionality](#build_part.xhtml#basic-functionality){.reference .internal}
- [Implicit Parameters](#build_part.xhtml#implicit-parameters){.reference .internal}
- [Units](#build_part.xhtml#units){.reference .internal}
- [Reference](#build_part.xhtml#module-build_part){.reference .internal}
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#build_line.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#build_line.xhtml#buildline .section}
# BuildLine
BuildLine is a python context manager that is used to create one dimensional objects - objects with the property of length but not area - that are typically used as part of a BuildSketch sketch or a BuildPart path.
The complete API for BuildLine is located at the end of this section.
::: {#build_line.xhtml#basic-functionality .section}
## Basic Functionality
The following is a simple BuildLine example:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildLine() as example_1:
Line((0, 0), (2, 0))
ThreePointArc((0, 0), (1, 1), (2, 0))
:::
:::
The `with`{.docutils .literal .notranslate} statement creates the `BuildLine`{.docutils .literal .notranslate} context manager with the identifier `example_1`{.docutils .literal .notranslate}. The objects and operations that are within the scope (i.e. indented) of this context will contribute towards the object being created by the context manager. For `BuildLine`{.docutils .literal .notranslate}, this object is `line`{.docutils .literal .notranslate} and it's referenced as `example_1.line`{.docutils .literal .notranslate}.
The first object in this example is a `Line`{.docutils .literal .notranslate} object which is used to create a straight line from coordinates (0,0) to (2,0) on the default XY plane. The second object is a `ThreePointArc`{.docutils .literal .notranslate} that starts and ends at the two ends of the line.
{.align-center}
:::
::: {#build_line.xhtml#constraints .section}
## Constraints
Building with constraints enables the designer to capture design intent and add a high degree of robustness to their designs. The following sections describe creating positional and tangential constraints as well as using object attributes to enable this type of design.
::: {#build_line.xhtml#position-at-operator .section}
### @ `position_at`{.docutils .literal .notranslate} Operator
In the previous example, the `ThreePointArc`{.docutils .literal .notranslate} started and ended at the two ends of the `Line`{.docutils .literal .notranslate} but this was done by referring to the same point `(0,0)`{.docutils .literal .notranslate} and `(2,0)`{.docutils .literal .notranslate}. This can be improved upon by specifying constraints that lock the arc to those two end points, as follows:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildLine() as example_2:
l1 = Line((0, 0), (2, 0))
l2 = ThreePointArc(l1 @ 0, (1, 1), l1 @ 1)
:::
:::
Here instance variables `l1`{.docutils .literal .notranslate} and `l2`{.docutils .literal .notranslate} are assigned to the two BuildLine objects and the `ThreePointArc`{.docutils .literal .notranslate} references the beginning of the straight line with `l1 @ 0`{.docutils .literal .notranslate} and the end with `l1 @ 1`{.docutils .literal .notranslate}. The `@`{.docutils .literal .notranslate} operator takes a float (or integer) parameter between 0 and 1 and determines a position at this fractional position along the line's length.
This example can be improved on further by calculating the mid-point of the arc as follows:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildLine() as example_3:
l1 = Line((0, 0), (2, 0))
l2 = ThreePointArc(l1 @ 0, l1 @ 0.5 + (0, 1), l1 @ 1)
:::
:::
Here `l1 @ 0.5`{.docutils .literal .notranslate} finds the center of `l1`{.docutils .literal .notranslate} while `l1 @ 0.5 + (0, 1)`{.docutils .literal .notranslate} does a vector addition to generate the point `(1,1)`{.docutils .literal .notranslate}.
To make the design even more parametric, the height of the arc can be calculated from `l1`{.docutils .literal .notranslate} as follows:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildLine() as example_4:
l1 = Line((0, 0), (2, 0))
l2 = ThreePointArc(l1 @ 0, l1 @ 0.5 + (0, l1.length / 2), l1 @ 1)
:::
:::
The arc height is now calculated as `(0, l1.length / 2)`{.docutils .literal .notranslate} by using the `length`{.docutils .literal .notranslate} property of `Edge`{.docutils .literal .notranslate} and `Wire`{.docutils .literal .notranslate} shapes. At this point the `ThreePointArc`{.docutils .literal .notranslate} is fully parametric and able to generate the same shape for any horizontal line.
:::
::: {#build_line.xhtml#tangent-at-operator .section}
### % `tangent_at`{.docutils .literal .notranslate} Operator
The other operator that is commonly used within BuildLine is `%`{.docutils .literal .notranslate} the tangent at operator. Here is another example:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildLine() as example_5:
l1 = Line((0, 0), (5, 0))
l2 = Line(l1 @ 1, l1 @ 1 + (0, l1.length - 1))
l3 = JernArc(start=l2 @ 1, tangent=l2 % 1, radius=0.5, arc_size=90)
l4 = Line(l3 @ 1, (0, l2.length + l3.radius))
:::
:::
which generates (note that the circles show line junctions):
{.align-center}
The `JernArc`{.docutils .literal .notranslate} has the following parameters:
- `start=l2 @ 1`{.docutils .literal .notranslate} - start the arc at the end of line `l2`{.docutils .literal .notranslate},
- `tangent=l2 % 1`{.docutils .literal .notranslate} - the tangent of the arc at the start point is equal to the `l2`{.docutils .literal .notranslate}\'s, tangent at its end (shown as a dashed line)
- `radius=0.5`{.docutils .literal .notranslate} - the radius of the arc, and
- `arc_size=90`{.docutils .literal .notranslate} the angular size of the arc.
The final line starts at the end of `l3`{.docutils .literal .notranslate} and ends at a point calculated from the length of `l2`{.docutils .literal .notranslate} and the radius of arc `l3`{.docutils .literal .notranslate}.
Building with constraints as shown here will ensure that your designs both fully represent design intent and are robust to design changes.
:::
:::
::: {#build_line.xhtml#buildline-to-buildsketch .section}
## BuildLine to BuildSketch
As mentioned previously, one of the two primary reasons to create BuildLine objects is to use them in BuildSketch. When a BuildLine context manager exits and is within the scope of a BuildSketch context manager it will transfer the generated line to BuildSketch. The BuildSketch [`make_face()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_sketch.make_face "operations_sketch.make_face"){.hxr-hoverxref .hxr-tooltip .reference .internal} or [`make_hull()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_sketch.make_hull "operations_sketch.make_hull"){.hxr-hoverxref .hxr-tooltip .reference .internal} operations are then used to transform the line (specifically a list of Edges) into a Face - the native BuildSketch objects.
Here is an example of using BuildLine to create an object that otherwise might be difficult to create:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch() as example_6:
with BuildLine() as club_outline:
l0 = Line((0, -188), (76, -188))
b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))
b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))
b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))
b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))
mirror(about=Plane.YZ)
make_face()
:::
:::
which generates:
{.align-center}
::: {.admonition .note}
Note
SVG import to BuildLine
The BuildLine code used in this example was generated by translating a SVG file into BuildLine source code with the [`import_svg_as_buildline_code()`{.xref .py .py-func .docutils .literal .notranslate}](#import_export.xhtml#importers.import_svg_as_buildline_code "importers.import_svg_as_buildline_code"){.hxr-hoverxref .hxr-tooltip .reference .internal} function. For example:
::: {.highlight-default .notranslate}
::: highlight
svg_code, builder_name = import_svg_as_buildline_code("club.svg")
:::
:::
would translate the "club.svg" image file's paths into BuildLine code much like that shown above. From there it's easy for a user to add constraints or otherwise enhance the original image and use it in their design.
:::
:::
::: {#build_line.xhtml#buildline-to-buildpart .section}
## BuildLine to BuildPart
The other primary reasons to use BuildLine is to create paths for BuildPart [`sweep()`{.xref .py .py-meth .docutils .literal .notranslate}](#operations.xhtml#operations_generic.sweep "operations_generic.sweep"){.hxr-hoverxref .hxr-tooltip .reference .internal} operations. Here some curved and straight segments define a path:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as example_7:
with BuildLine() as example_7_path:
l1 = RadiusArc((0, 0), (1, 1), 2)
l2 = Spline(l1 @ 1, (2, 3), (3, 3), tangents=(l1 % 1, (0, -1)))
l3 = Line(l2 @ 1, (3, 0))
with BuildSketch(Plane(origin=l1 @ 0, z_dir=l1 % 0)) as example_7_section:
Circle(0.1)
sweep()
:::
:::
which generates:
{.align-center}
There are few things to note from this example:
- The @ and % operators are used to create a plane normal to the beginning of the path with which to create the circular section used by the sweep operation (this plane is not one of the ordinal planes).
- Both the path generated by BuildLine and the section generated by BuildSketch have been transferred to BuildPart when each of them exit.
- The BuildPart `Sweep`{.docutils .literal .notranslate} operation is using the path and section previously transferred to it (as "pending" objects) as parameters of the sweep. The `Sweep`{.docutils .literal .notranslate} operation "consumes" these pending objects as to not interfere with subsequence operations.
:::
::: {#build_line.xhtml#working-on-other-planes .section}
## Working on other Planes
So far all of the examples were created on `Plane.XY`{.docutils .literal .notranslate} - the default plane - which is equivalent to global coordinates. Sometimes it's convenient to work on another plane, especially when creating paths for BuildPart `Sweep`{.docutils .literal .notranslate} operations.
::: {.highlight-build123d .notranslate}
::: highlight
with BuildLine(Plane.YZ) as example_8:
l1 = Line((0, 0), (5, 0))
l2 = Line(l1 @ 1, l1 @ 1 + (0, l1.length - 1))
l3 = JernArc(start=l2 @ 1, tangent=l2 % 1, radius=0.5, arc_size=90)
l4 = Line(l3 @ 1, (0, l2.length + l3.radius))
:::
:::
which generates:
{.align-center}
Here the BuildLine object is created on `Plane.YZ`{.docutils .literal .notranslate} just by specifying the working plane during BuildLine initialization.
There are three rules to keep in mind when working with alternate planes in BuildLine:
1. `BuildLine`{.docutils .literal .notranslate} accepts a single `Plane`{.docutils .literal .notranslate} to work with as opposed to other Builders that accept more than one workplane.
2. Values entered as tuples such as `(1, 2)`{.docutils .literal .notranslate} or `(1, 2, 3)`{.docutils .literal .notranslate} will be localized to the current workplane. This rule applies to points and to the use of tuples to modify locations calculated with the `@`{.docutils .literal .notranslate} and `%`{.docutils .literal .notranslate} operators such as `l1 @ 1 + (1, 1)`{.docutils .literal .notranslate}. For example, if the workplane is `Plane.YZ`{.docutils .literal .notranslate} the local value of `(1, 2)`{.docutils .literal .notranslate} would be converted to `(0, 1, 2)`{.docutils .literal .notranslate} in global coordinates. Three tuples are converted as well - `(1, 2, 3)`{.docutils .literal .notranslate} on `Plane.YZ`{.docutils .literal .notranslate} would be `(3, 1, 2)`{.docutils .literal .notranslate} in global coordinates. Providing values in local coordinates allows the designer to automate such conversions.
3. Values entered using the `Vector`{.docutils .literal .notranslate} class or those generated by the `@`{.docutils .literal .notranslate} operator are considered global values and are not localized. For example: `Line(Vector(1, 2, 3), Vector(4, 5, 6))`{.docutils .literal .notranslate} will generate the same line independent of the current workplane. It's unlikely that users will need to use `Vector`{.docutils .literal .notranslate} values but the option is there.
Finally, BuildLine's workplane need not be one of the predefined ordinal planes, it could be one created from a surface of a BuildPart part that is currently under construction.
:::
::: {#build_line.xhtml#module-build_line .section}
[]{#build_line.xhtml#reference}
## Reference
*[class]{.pre}[ ]{.w}*[[BuildLine]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[workplane:]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.geometry.Plane]{.pre} [\|]{.pre} [\~build123d.geometry.Location]{.pre} [=]{.pre} [Plane(o=(0.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[0.00)]{.pre}]{.n}*, *[[x=(1.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[0.00)]{.pre}]{.n}*, *[[z=(0.00]{.pre}]{.n}*, *[[0.00]{.pre}]{.n}*, *[[1.00))]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: The BuildLine class is a subclass of Builder for building lines (objects with length but not area or volume). It has an \_obj property that returns the current line being built. The class overrides the faces and solids methods of Builder since they don't apply to lines.
BuildLine only works with a single workplane which is used to convert tuples as inputs to global coordinates. For example:
::: {.highlight-default .notranslate}
::: highlight
with BuildLine(Plane.YZ) as radius_arc:
RadiusArc((1, 2), (2, 1), 1)
:::
:::
creates an arc from global points (0, 1, 2) to (0, 2, 1). Note that points entered as Vector(x, y, z) are considered global and are not localized.
The workplane is also used to define planes parallel to the workplane that arcs are created on.
Parameters[:]{.colon}
: - **workplane** (*Union\[*[*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Location*](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- plane used when local coordinates are used and when creating arcs. Defaults to Plane.XY.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
[[face]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*]{.pre}]{.o}[[args]{.pre}]{.n}*[)]{.sig-paren}
: face() not implemented
[[faces]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*]{.pre}]{.o}[[args]{.pre}]{.n}*[)]{.sig-paren}
: faces() not implemented
*[property]{.pre}[ ]{.w}*[[line]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Curve]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}*
: Get the current line
[[solid]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*]{.pre}]{.o}[[args]{.pre}]{.n}*[)]{.sig-paren}
: solid() not implemented
[[solids]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*]{.pre}]{.o}[[args]{.pre}]{.n}*[)]{.sig-paren}
: solids() not implemented
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#build_sketch.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#build_sketch.xhtml#buildsketch .section}
# BuildSketch
BuildSketch is a python context manager that is used to create planar two dimensional objects - objects with the property of area but not volume - that are typically used as profiles for BuildPart operations like [`extrude()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} or [`revolve()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.revolve "operations_part.revolve"){.hxr-hoverxref .hxr-tooltip .reference .internal}.
The complete API for BuildSketch is located at the end of this section.
::: {#build_sketch.xhtml#basic-functionality .section}
## Basic Functionality
The following is a simple BuildSketch example:
::: {.highlight-build123d .notranslate}
::: highlight
length, radius = 40.0, 60.0
with BuildSketch() as circle_with_hole:
Circle(radius=radius)
Rectangle(width=length, height=length, mode=Mode.SUBTRACT)
:::
:::
The `with`{.docutils .literal .notranslate} statement creates the `BuildSketch`{.docutils .literal .notranslate} context manager with the identifier `circle_with_hole`{.docutils .literal .notranslate}. The objects and operations that are within the scope (i.e. indented) of this context will contribute towards the object being created by the context manager. For `BuildSketch`{.docutils .literal .notranslate}, this object is `sketch`{.docutils .literal .notranslate} and it's referenced as `circle_with_hole.sketch`{.docutils .literal .notranslate}.
The first object in this example is a `Circle`{.docutils .literal .notranslate} object which is used to create a filled circular shape on the default XY plane. The second object is a `Rectangle`{.docutils .literal .notranslate} that is subtracted from the circle as directed by the `mode=Mode.SUBTRACT`{.docutils .literal .notranslate} parameter. A key aspect of sketch objects is that they are all filled shapes and not just a shape perimeter which enables combining subsequent shapes with different modes (the valid values of Mode are `ADD`{.docutils .literal .notranslate}, `SUBTRACT`{.docutils .literal .notranslate}, `INTERSECT`{.docutils .literal .notranslate}, `REPLACE`{.docutils .literal .notranslate}, and `PRIVATE`{.docutils .literal .notranslate}).
{.align-center}
:::
::: {#build_sketch.xhtml#sketching-on-other-planes .section}
[]{#build_sketch.xhtml#id1}
## Sketching on other Planes
Often when designing parts one needs to build on top of other features. To facilitate doing this `BuildSketch`{.docutils .literal .notranslate} allows one to create sketches on any Plane while allowing the designer to work in a local X, Y coordinate system. It might be helpful to think of what is happening with this metaphor:
1. When instantiating `BuildSketch`{.docutils .literal .notranslate} one or more workplanes can be passed as parameters. These are the placement targets for the completed sketch.
2. The designer draws on a flat "drafting table" which is `Plane.XY`{.docutils .literal .notranslate}.
3. Once the sketch is complete, it's applied like a sticker to all of the workplanes passed in step 1.
As an example, let's build the following simple control box with a display on an angled plane:
{.align-center}
Here is the code:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as controller:
# Create the side view of the controller
with BuildSketch(Plane.YZ) as profile:
with BuildLine():
Polyline((0, 0), (0, 40), (20, 80), (40, 80), (40, 0), (0, 0))
# Create a filled face from the perimeter drawing
make_face()
# Extrude to create the basis controller shape
extrude(amount=30, both=True)
# Round off all the edges
fillet(controller.edges(), radius=3)
# Hollow out the controller
offset(amount=-1, mode=Mode.SUBTRACT)
# Extract the face that will house the display
display_face = (
controller.faces()
.filter_by(GeomType.PLANE)
.filter_by_position(Axis.Z, 50, 70)[0]
)
# Create a workplane from the face
display_workplane = Plane(
origin=display_face.center(), x_dir=(1, 0, 0), z_dir=display_face.normal_at()
)
# Place the sketch directly on the controller
with BuildSketch(display_workplane) as display:
RectangleRounded(40, 30, 2)
with GridLocations(45, 35, 2, 2):
Circle(1)
# Cut the display sketch through the controller
extrude(amount=-1, mode=Mode.SUBTRACT)
:::
:::
The highlighted part of the code shows how a face is extracted from the design, a workplane is constructed from this face and finally this workplane is passed to `BuildSketch`{.docutils .literal .notranslate} as the target for the complete sketch. Notice how the `display`{.docutils .literal .notranslate} sketch uses local coordinates for its features thus avoiding having the user to determine how to move and rotate the sketch to get it where it should go.
Note that `BuildSketch`{.docutils .literal .notranslate} accepts a sequence planes, faces and locations for workplanes so creation of an explicit workplane is often not required. Being able to work on multiple workplanes at once allows for features to be created on multiple side of an object - say both the top and bottom - which is convenient for symmetric parts.
:::
::: {#build_sketch.xhtml#local-vs-global-sketches .section}
## Local vs. Global Sketches
In the above example the target for the sketch was not `Plane.XY`{.docutils .literal .notranslate} but a workplane passed by the user. Internally `BuildSketch`{.docutils .literal .notranslate} is always creating the sketch on `Plane.XY`{.docutils .literal .notranslate} which one can see by looking at the `sketch_local`{.docutils .literal .notranslate} property of your sketch. For example, to display the local version of the `display`{.docutils .literal .notranslate} sketch from above, one would use:
::: {.highlight-build123d .notranslate}
::: highlight
show_object(display.sketch_local, name="sketch on Plane.XY")
:::
:::
while the sketches as applied to their target workplanes is accessible through the `sketch`{.docutils .literal .notranslate} property, as follows:
::: {.highlight-build123d .notranslate}
::: highlight
show_object(display.sketch, name="sketch on target workplane(s)")
:::
:::
When using the [`add()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.add "operations_generic.add"){.hxr-hoverxref .hxr-tooltip .reference .internal} operation to add an external Face to a sketch the face will automatically be reoriented to `Plane.XY`{.docutils .literal .notranslate} before being combined with the sketch. As Faces don't provide an x-direction it's possible that the new Face may not be oriented as expected. To reorient the Face manually to `Plane.XY`{.docutils .literal .notranslate} one can use the `to_local_coords()`{.xref .py .py-meth .docutils .literal .notranslate} method as follows:
::: {.highlight-build123d .notranslate}
::: highlight
reoriented_face = plane.to_local_coords(face)
:::
:::
where `plane`{.docutils .literal .notranslate} is the plane that `face`{.docutils .literal .notranslate} was constructed on.
:::
::: {#build_sketch.xhtml#locating-features .section}
## Locating Features
Within a sketch features are positioned with `Locations`{.docutils .literal .notranslate} contexts (see [[Location Context]{.std .std-ref}](#key_concepts_builder.xhtml#location-context-link){.reference .internal}) on the current workplane(s). The following location contexts are available within a sketch:
- [`GridLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.GridLocations "build_common.GridLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} : a X/Y grid of locations
- [`HexLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.HexLocations "build_common.HexLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} : a hex grid of locations ideal for nesting circles
- [`Locations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.Locations "build_common.Locations"){.hxr-hoverxref .hxr-tooltip .reference .internal} : a sequence of arbitrary locations
- [`PolarLocations`{.xref .py .py-class .docutils .literal .notranslate}](#builder_api_reference.xhtml#build_common.PolarLocations "build_common.PolarLocations"){.hxr-hoverxref .hxr-tooltip .reference .internal} : locations defined by radius and angle
Generally one would specify tuples of (X, Y) values when defining locations but there are many options available to the user.
:::
::: {#build_sketch.xhtml#module-build_sketch .section}
[]{#build_sketch.xhtml#reference}
## Reference
*[class]{.pre}[ ]{.w}*[[BuildSketch]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*workplanes:]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.geometry.Plane]{.pre} [\|]{.pre} [\~build123d.geometry.Location]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: The BuildSketch class is a subclass of Builder for building planar 2D sketches (objects with area but not volume) from faces or lines. It has an \_obj property that returns the current sketch being built. The sketch property consists of the sketch(es) applied to the input workplanes while the sketch_local attribute is the sketch constructed on Plane.XY. The class overrides the solids method of Builder since they don't apply to lines.
Note that all sketch construction is done within sketch_local on Plane.XY. When objects are added to the sketch they must be coplanar to Plane.XY, usually handled automatically but may need user input for Edges and Wires since their construction plane isn't always able to be determined.
Parameters[:]{.colon}
: - **workplanes** (*Union\[*[*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Location*](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- objects converted to plane(s) to place the sketch on. Defaults to Plane.XY.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
[[consolidate_edges]{.pre}]{.sig-name .descname}[(]{.sig-paren}[)]{.sig-paren} [[→]{.sig-return-icon} [[Wire]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[list]{.pre}[[\[]{.pre}]{.p}[Wire]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Unify pending edges into one or more Wires
*[property]{.pre}[ ]{.w}*[[sketch]{.pre}]{.sig-name .descname}
: The global version of the sketch - may contain multiple sketches
*[property]{.pre}[ ]{.w}*[[sketch_local]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Sketch]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}*
: Get the builder's object
[[solid]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*]{.pre}]{.o}[[args]{.pre}]{.n}*[)]{.sig-paren}
: solid() not implemented
[[solids]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*]{.pre}]{.o}[[args]{.pre}]{.n}*[)]{.sig-paren}
: solids() not implemented
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#build_part.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#build_part.xhtml#buildpart .section}
# BuildPart
BuildPart is a python context manager that is used to create three dimensional objects - objects with the property of volume - that are typically finished parts.
The complete API for BuildPart is located at the end of this section.
::: {#build_part.xhtml#basic-functionality .section}
## Basic Functionality
The following is a simple BuildPart example:
::: {.highlight-build123d .notranslate}
::: highlight
length, width, thickness = 80.0, 60.0, 10.0
center_hole_dia = 22.0
with BuildPart() as ex2:
Box(length, width, thickness)
Cylinder(radius=center_hole_dia / 2, height=thickness, mode=Mode.SUBTRACT)
:::
:::
The `with`{.docutils .literal .notranslate} statement creates the `BuildPart`{.docutils .literal .notranslate} context manager with the identifier `ex2`{.docutils .literal .notranslate} (this code is the second of the introductory examples). The objects and operations that are within the scope (i.e. indented) of this context will contribute towards the object being created by the context manager. For `BuildPart`{.docutils .literal .notranslate}, this object is `part`{.docutils .literal .notranslate} and it's referenced as `ex2.part`{.docutils .literal .notranslate}.
The first object in this example is a `Box`{.docutils .literal .notranslate} object which is used to create a polyhedron with rectangular faces centered on the default `Plane.XY`{.docutils .literal .notranslate}. The second object is a `Cylinder`{.docutils .literal .notranslate} that is subtracted from the box as directed by the `mode=Mode.SUBTRACT`{.docutils .literal .notranslate} parameter thus creating a hole.
{.align-center}
:::
::: {#build_part.xhtml#implicit-parameters .section}
## Implicit Parameters
The BuildPart context keeps track of pending objects such that they can be used implicitly - there are a couple things to consider when deciding how to proceed:
- For sketches, the planes that they were constructed on is maintained in internal data structures such that operations like [`extrude()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} will have a good reference for the extrude direction. One can pass a Face to extrude but it will then be forced to use the normal direction at the center of the Face as the extrude direction which unfortunately can be reversed in some circumstances.
- Implicit parameters save some typing but hide some functionality - users have to decide what works best for them.
This tea cup example uses implicit parameters - note the [`sweep()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.sweep "operations_generic.sweep"){.hxr-hoverxref .hxr-tooltip .reference .internal} operation on the last line:
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import show
wall_thickness = 3 * MM
fillet_radius = wall_thickness * 0.49
with BuildPart() as tea_cup:
# Create the bowl of the cup as a revolved cross section
with BuildSketch(Plane.XZ) as bowl_section:
with BuildLine():
# Start & end points with control tangents
s = Spline(
(30 * MM, 10 * MM),
(69 * MM, 105 * MM),
tangents=((1, 0.5), (0.7, 1)),
tangent_scalars=(1.75, 1),
)
# Lines to finish creating ½ the bowl shape
Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)
make_face() # Create a filled 2D shape
revolve(axis=Axis.Z)
# Hollow out the bowl with openings on the top and bottom
offset(amount=-wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE))
# Add a bottom to the bowl
with Locations((0, 0, (s @ 0).Y)):
Cylinder(radius=(s @ 0).X, height=wall_thickness)
# Smooth out all the edges
fillet(tea_cup.edges(), radius=fillet_radius)
# Determine where the handle contacts the bowl
handle_intersections = [
tea_cup.part.find_intersection_points(
Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))
)[-1][0]
for vertical_offset in [35 * MM, 80 * MM]
]
# Create a path for handle creation
with BuildLine(Plane.XZ) as handle_path:
Spline(
handle_intersections[0] - (wall_thickness / 2, 0),
handle_intersections[0] + (35 * MM, 30 * MM),
handle_intersections[0] + (40 * MM, 60 * MM),
handle_intersections[1] - (wall_thickness / 2, 0),
tangents=((1, 1.25), (-0.2, -1)),
)
# Align the cross section to the beginning of the path
with BuildSketch(handle_path.line ^ 0) as handle_cross_section:
RectangleRounded(wall_thickness, 8 * MM, fillet_radius)
sweep() # Sweep handle cross section along path
assert abs(tea_cup.part.volume - 130326) < 1
show(tea_cup, names=["tea cup"])
:::
:::
[`sweep()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.sweep "operations_generic.sweep"){.hxr-hoverxref .hxr-tooltip .reference .internal} requires a 2D cross section - `handle_cross_section`{.docutils .literal .notranslate} - and a path - `handle_path`{.docutils .literal .notranslate} - which are both passed implicitly.
{.align-center}
:::
::: {#build_part.xhtml#units .section}
## Units
Parts created with build123d have no inherent units associated with them. However, when exporting parts to external formats like `STL`{.docutils .literal .notranslate} or `STEP`{.docutils .literal .notranslate} the units are assumed to be millimeters (mm). To be more explicit with units one can use the technique shown in the above tea cup example where linear dimensions are followed by `* MM`{.docutils .literal .notranslate} which multiplies the dimension by the `MM`{.docutils .literal .notranslate} scaling factor - in this case `1`{.docutils .literal .notranslate}.
The following dimensional constants are pre-defined:
::: {.highlight-python .notranslate}
::: highlight
MM = 1
CM = 10 * MM
M = 1000 * MM
IN = 25.4 * MM
FT = 12 * IN
THOU = IN / 1000
:::
:::
Some export formats like DXF have the ability to explicitly set the units used.
:::
::: {#build_part.xhtml#module-build_part .section}
[]{#build_part.xhtml#reference}
## Reference
*[class]{.pre}[ ]{.w}*[[BuildPart]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[\*workplanes:]{.pre} [\~build123d.topology.two_d.Face]{.pre} [\|]{.pre} [\~build123d.geometry.Plane]{.pre} [\|]{.pre} [\~build123d.geometry.Location]{.pre}]{.n}*, *[[mode:]{.pre} [\~build123d.build_enums.Mode]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: The BuildPart class is another subclass of Builder for building parts (objects with the property of volume) from sketches or 3D objects. It has an \_obj property that returns the current part being built, and several pending lists for storing faces, edges, and planes that will be integrated into the final part later. The class overrides the \_add_to_pending method of Builder.
Parameters[:]{.colon}
: - **workplanes** ([*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- initial plane to work on. Defaults to Plane.XY.
- **mode** ([*Mode*](#builder_api_reference.xhtml#build_enums.Mode "build_enums.Mode"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- combination mode. Defaults to Mode.ADD.
*[property]{.pre}[ ]{.w}*[[location]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Location]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}*
: Builder's location
*[property]{.pre}[ ]{.w}*[[part]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Part]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}*
: Get the current part
*[property]{.pre}[ ]{.w}*[[pending_edges_as_wire]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Wire]{.pre}*
: Return a wire representation of the pending edges
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#joints.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#joints.xhtml#joints .section}
[]{#joints.xhtml#id1}
# Joints
[`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}'s enable Solid and Compound objects to be arranged relative to each other in an intuitive manner - with the same degree of motion that is found with the equivalent physical joints. [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}'s always work in pairs - a [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal} can only be connected to another [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal} as follows:
[`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal} connect_to Example
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------
[`BallJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.BallJoint "joints.BallJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} [`RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} Gimbal
[`CylindricalJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.CylindricalJoint "joints.CylindricalJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} [`RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} Screw
[`LinearJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.LinearJoint "joints.LinearJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} [`RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}, [`RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RevoluteJoint "joints.RevoluteJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} Slider or Pin Slot
[`RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RevoluteJoint "joints.RevoluteJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} [`RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} Hinge
[`RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} [`RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} Fixed
Objects may have many joints bound to them each with an identifying label. All [`Joint`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal} objects have a `symbol`{.docutils .literal .notranslate} property that can be displayed to help visualize their position and orientation (the [ocp-vscode](https://github.com/bernhard-42/vscode-ocp-cad-viewer){.reference .external}[ \[https://github.com/bernhard-42/vscode-ocp-cad-viewer\]]{.link-target} viewer has built-in support for displaying joints).
::: {.admonition .note}
Note
If joints are created within the scope of a [`BuildPart`{.xref .py .py-class .docutils .literal .notranslate}](#build_part.xhtml#build_part.BuildPart "build_part.BuildPart"){.hxr-hoverxref .hxr-tooltip .reference .internal} builder, the `to_part`{.docutils .literal .notranslate} parameter need not be specified as the builder will, on exit, automatically transfer the joints created in its scope to the part created.
:::
The following sections provide more detail on the available joints and describes how they are used.
::: {#joints.xhtml#rigid-joint .section}
[]{#joints.xhtml#module-joints}
## Rigid Joint
A rigid joint positions two components relative to each another with no freedom of movement. When a [`RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} is instantiated it's assigned a `label`{.docutils .literal .notranslate}, a part to bind to (`to_part`{.docutils .literal .notranslate}), and a `joint_location`{.docutils .literal .notranslate} which defines both the position and orientation of the joint (see [`Location`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal}) - as follows:
::: {.highlight-build123d .notranslate}
::: highlight
RigidJoint(label="outlet", to_part=pipe, joint_location=path.location_at(1))
:::
:::
Once a joint is bound to a part this way, the [`connect_to()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint.connect_to "topology.Joint.connect_to"){.hxr-hoverxref .hxr-tooltip .reference .internal} method can be used to repositioning another part relative to `self`{.docutils .literal .notranslate} which stay fixed - as follows:
::: {.highlight-build123d .notranslate}
::: highlight
pipe.joints["outlet"].connect_to(flange_outlet.joints["pipe"])
:::
:::
::: {.admonition .note}
Note
Within a part all of the joint labels must be unique.
:::
The [`connect_to()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint.connect_to "topology.Joint.connect_to"){.hxr-hoverxref .hxr-tooltip .reference .internal} method only does a one time re-position of a part and does not bind them in any way; however, putting them into an [[Assemblies]{.std .std-ref}](#assemblies.xhtml#assembly){.reference .internal} will maintain there relative locations as will combining parts with boolean operations or within a [`BuildPart`{.xref .py .py-class .docutils .literal .notranslate}](#build_part.xhtml#build_part.BuildPart "build_part.BuildPart"){.hxr-hoverxref .hxr-tooltip .reference .internal} context.
As a example of creating parts with joints and connecting them together, consider the following code where flanges are attached to the ends of a curved pipe:

::: {.highlight-build123d .notranslate}
::: highlight
import copy
from build123d import *
from bd_warehouse.flange import WeldNeckFlange
from bd_warehouse.pipe import PipeSection
from ocp_vscode import *
flange_inlet = WeldNeckFlange(nps="10", flange_class=300)
flange_outlet = copy.copy(flange_inlet)
with BuildPart() as pipe_builder:
# Create the pipe
with BuildLine():
path = TangentArc((0, 0, 0), (2 * FT, 0, 1 * FT), tangent=(1, 0, 0))
with BuildSketch(Plane(origin=path @ 0, z_dir=path % 0)):
PipeSection("10", material="stainless", identifier="40S")
sweep()
# Add the joints
RigidJoint(label="inlet", joint_location=-path.location_at(0))
RigidJoint(label="outlet", joint_location=path.location_at(1))
# Place the flanges at the ends of the pipe
pipe_builder.part.joints["inlet"].connect_to(flange_inlet.joints["pipe"])
pipe_builder.part.joints["outlet"].connect_to(flange_outlet.joints["pipe"])
show(pipe_builder, flange_inlet, flange_outlet, render_joints=True)
:::
:::
Note how the locations of the joints are determined by the [`location_at()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Mixin1D.location_at "topology.Mixin1D.location_at"){.hxr-hoverxref .hxr-tooltip .reference .internal} method and how the `-`{.docutils .literal .notranslate} negate operator is used to reverse the direction of the location without changing its position. Also note that the `WeldNeckFlange`{.docutils .literal .notranslate} class predefines two joints, one at the pipe end and one at the face end - both of which are shown in the above image (generated by ocp-vscode with the `render_joints=True`{.docutils .literal .notranslate} flag set in the `show`{.docutils .literal .notranslate} function).
*[class]{.pre}[ ]{.w}*[[RigidJoint]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[label]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[to_part]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Solid]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[joint_location]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Location]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: A rigid joint fixes two components to one another.
Parameters[:]{.colon}
: - **label** (*str*) -- joint label
- **to_part** (*Union\[*[*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- object to attach joint to
- **joint_location** ([*Location*](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- global location of joint
Variables[:]{.colon}
: **relative_location** ([*Location*](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint location relative to bound object
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[BallJoint]{.pre}](#joints.xhtml#joints.BallJoint "joints.BallJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[angles]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Rotation]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[\*\*]{.pre}]{.o}[[kwargs]{.pre}]{.n}*[)]{.sig-paren}\
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[CylindricalJoint]{.pre}](#joints.xhtml#joints.CylindricalJoint "joints.CylindricalJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[LinearJoint]{.pre}](#joints.xhtml#joints.LinearJoint "joints.LinearJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RevoluteJoint]{.pre}](#joints.xhtml#joints.RevoluteJoint "joints.RevoluteJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*[)]{.sig-paren}
: Connect the RigidJoint to another Joint
Parameters[:]{.colon}
: - **other** ([*Joint*](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint to connect to
- **angle** (*float,* *optional*) -- angle in degrees. Defaults to range min.
- **angles** (*RotationLike,* *optional*) -- angles about axes in degrees. Defaults to range minimums.
- **position** (*float,* *optional*) -- linear position. Defaults to linear range min.
*[property]{.pre}[ ]{.w}*[[location]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Location]{.pre}*
: Location of joint
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[BallJoint]{.pre}](#joints.xhtml#joints.BallJoint "joints.BallJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[angles]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Rotation]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[CylindricalJoint]{.pre}](#joints.xhtml#joints.CylindricalJoint "joints.CylindricalJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[LinearJoint]{.pre}](#joints.xhtml#joints.LinearJoint "joints.LinearJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RevoluteJoint]{.pre}](#joints.xhtml#joints.RevoluteJoint "joints.RevoluteJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*[)]{.sig-paren}
: Relative location of RigidJoint to another Joint
Parameters[:]{.colon}
: - **other** ([*RigidJoint*](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- relative to joint
- **angle** (*float,* *optional*) -- angle in degrees. Defaults to range min.
- **angles** (*RotationLike,* *optional*) -- angles about axes in degrees. Defaults to range minimums.
- **position** (*float,* *optional*) -- linear position. Defaults to linear range min.
Raises[:]{.colon}
: **TypeError** -- other must be of a type in: BallJoint, CylindricalJoint, LinearJoint, RevoluteJoint, RigidJoint.
*[property]{.pre}[ ]{.w}*[[symbol]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Compound]{.pre}*
: A CAD symbol (XYZ indicator) as bound to part
:::
::: {#joints.xhtml#revolute-joint .section}
## Revolute Joint
Component rotates around axis like a hinge. The [[Joint Tutorial]{.std .std-ref}](#tutorial_joints.xhtml#joint-tutorial){.reference .internal} covers Revolute Joints in detail.
During instantiation of a [`RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RevoluteJoint "joints.RevoluteJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} there are three parameters not present with Rigid Joints: `axis`{.docutils .literal .notranslate}, `angle_reference`{.docutils .literal .notranslate}, and `range`{.docutils .literal .notranslate} that allow the circular motion to be fully defined.
When [`connect_to()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Joint.connect_to "topology.Joint.connect_to"){.hxr-hoverxref .hxr-tooltip .reference .internal} with a Revolute Joint, an extra `angle`{.docutils .literal .notranslate} parameter is present which allows one to change the relative position of joined parts by changing a single value.
*[class]{.pre}[ ]{.w}*[[RevoluteJoint]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[label]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[to_part]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Solid]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[axis]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Axis]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[((0.0,]{.pre} [0.0,]{.pre} [0.0),]{.pre} [(0.0,]{.pre} [0.0,]{.pre} [1.0))]{.pre}]{.default_value}*, *[[angle_reference]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Vector]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sequence]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angular_range]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[(0,]{.pre} [360)]{.pre}]{.default_value}*[)]{.sig-paren}
: Component rotates around axis like a hinge.
Parameters[:]{.colon}
: - **label** (*str*) -- joint label
- **to_part** (*Union\[*[*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- object to attach joint to
- **axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- axis of rotation
- **angle_reference** (*VectorLike,* *optional*) -- direction normal to axis defining where angles will be measured from. Defaults to None.
- **range** (*tuple\[float,* *float\],* *optional*) -- (min,max) angle of joint. Defaults to (0, 360).
Variables[:]{.colon}
: - **angle** (*float*) -- angle of joint
- **angle_reference** ([*Vector*](#direct_api_reference.xhtml#geometry.Vector "geometry.Vector"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- reference for angular positions
- **angular_range** (*tuple\[float,float\]*) -- min and max angular position of joint
- **relative_axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint axis relative to bound part
Raises[:]{.colon}
: **ValueError** -- angle_reference must be normal to axis
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: Connect RevoluteJoint and RigidJoint
Parameters[:]{.colon}
: - **other** ([*RigidJoint*](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- relative to joint
- **angle** (*float,* *optional*) -- angle in degrees. Defaults to range min.
Returns[:]{.colon}
: other must of type RigidJoint ValueError: angle out of range
Return type[:]{.colon}
: *TypeError*
*[property]{.pre}[ ]{.w}*[[location]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Location]{.pre}*
: Location of joint
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: Relative location of RevoluteJoint to RigidJoint
Parameters[:]{.colon}
: - **other** ([*RigidJoint*](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- relative to joint
- **angle** (*float,* *optional*) -- angle in degrees. Defaults to range min.
Raises[:]{.colon}
: - **TypeError** -- other must of type RigidJoint
- **ValueError** -- angle out of range
*[property]{.pre}[ ]{.w}*[[symbol]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Compound]{.pre}*
: A CAD symbol representing the axis of rotation as bound to part
:::
::: {#joints.xhtml#linear-joint .section}
## Linear Joint
Component moves along a single axis as with a sliding latch shown here:

The code to generate these components follows:
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from ocp_vscode import *
with BuildPart() as latch:
# Basic box shape to start with filleted corners
Box(70, 30, 14)
end = latch.faces().sort_by(Axis.X)[-1] # save the end with the hole
fillet(latch.edges().filter_by(Axis.Z), 2)
fillet(latch.edges().sort_by(Axis.Z)[-1], 1)
# Make screw tabs
with BuildSketch(latch.faces().sort_by(Axis.Z)[0]) as l4:
with Locations((-30, 0), (30, 0)):
SlotOverall(50, 10, rotation=90)
Rectangle(50, 30)
fillet(l4.vertices(Select.LAST), radius=2)
extrude(amount=-2)
with GridLocations(60, 40, 2, 2):
Hole(2)
# Create the hole from the end saved previously
with BuildSketch(end) as slide_hole:
add(end)
offset(amount=-2)
fillet(slide_hole.vertices(), 1)
extrude(amount=-68, mode=Mode.SUBTRACT)
# Slot for the handle to slide in
with BuildSketch(latch.faces().sort_by(Axis.Z)[-1]):
SlotOverall(32, 8)
extrude(amount=-2, mode=Mode.SUBTRACT)
# The slider will move align the x axis 12mm in each direction
LinearJoint("latch", axis=Axis.X, linear_range=(-12, 12))
with BuildPart() as slide:
# The slide will be a little smaller than the hole
with BuildSketch() as s1:
add(slide_hole.sketch)
offset(amount=-0.25)
# The extrusions aren't symmetric
extrude(amount=46)
extrude(slide.faces().sort_by(Axis.Z)[0], amount=20)
# Round off the ends
fillet(slide.edges().group_by(Axis.Z)[0], 1)
fillet(slide.edges().group_by(Axis.Z)[-1], 1)
# Create the knob
with BuildSketch() as s2:
with Locations((12, 0)):
SlotOverall(15, 4, rotation=90)
Rectangle(12, 7, align=(Align.MIN, Align.CENTER))
fillet(s2.vertices(Select.LAST), 1)
split(bisect_by=Plane.XZ)
revolve(axis=Axis.X)
# Align the joint to Plane.ZY flipped
RigidJoint("slide", joint_location=Location(-Plane.ZY))
# Position the slide in the latch: -12 >= position <= 12
latch.part.joints["latch"].connect_to(slide.part.joints["slide"], position=12)
# show(latch.part, render_joints=True)
# show(slide.part, render_joints=True)
show(latch.part, slide.part, render_joints=True)
:::
:::
{style="width: 65%;"} {style="width: 27.5%;"}
Note how the slide is constructed in a different orientation than the direction of motion. The three highlighted lines of code show how the joints are created and connected together:
- The [`LinearJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.LinearJoint "joints.LinearJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} has an axis and limits of movement
- The [`RigidJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} has a single location, orientated such that the knob will ultimately be "up"
- The `connect_to`{.docutils .literal .notranslate} specifies a position that must be within the predefined limits.
The slider can be moved back and forth by just changing the `position`{.docutils .literal .notranslate} value. Values outside of the limits will raise an exception.
*[class]{.pre}[ ]{.w}*[[LinearJoint]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[label]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[to_part]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Solid]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[axis]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Axis]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[((0.0,]{.pre} [0.0,]{.pre} [0.0),]{.pre} [(0.0,]{.pre} [0.0,]{.pre} [1.0))]{.pre}]{.default_value}*, *[[linear_range]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[(0,]{.pre} [inf)]{.pre}]{.default_value}*[)]{.sig-paren}
: Component moves along a single axis.
Parameters[:]{.colon}
: - **label** (*str*) -- joint label
- **to_part** (*Union\[*[*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- object to attach joint to
- **axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- axis of linear motion
- **range** (*tuple\[float,* *float\],* *optional*) -- (min,max) position of joint. Defaults to (0, inf).
Variables[:]{.colon}
: - **axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint axis
- **angle** (*float*) -- angle of joint
- **linear_range** (*tuple\[float,float\]*) -- min and max positional values
- **position** (*float*) -- joint position
- **relative_axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint axis relative to bound part
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RevoluteJoint]{.pre}](#joints.xhtml#joints.RevoluteJoint "joints.RevoluteJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: Connect LinearJoint to another Joint
Parameters[:]{.colon}
: - **other** ([*Joint*](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint to connect to
- **angle** (*float,* *optional*) -- angle in degrees. Defaults to range min.
- **position** (*float,* *optional*) -- linear position. Defaults to linear range min.
Raises[:]{.colon}
: - **TypeError** -- other must be of type RevoluteJoint or RigidJoint
- **ValueError** -- position out of range
- **ValueError** -- angle out of range
*[property]{.pre}[ ]{.w}*[[location]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Location]{.pre}*
: Location of joint
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}\
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RevoluteJoint]{.pre}](#joints.xhtml#joints.RevoluteJoint "joints.RevoluteJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: Relative location of LinearJoint to RevoluteJoint or RigidJoint
Parameters[:]{.colon}
: - **other** ([*Joint*](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint to connect to
- **angle** (*float,* *optional*) -- angle in degrees. Defaults to range min.
- **position** (*float,* *optional*) -- linear position. Defaults to linear range min.
Raises[:]{.colon}
: - **TypeError** -- other must be of type RevoluteJoint or RigidJoint
- **ValueError** -- position out of range
- **ValueError** -- angle out of range
*[property]{.pre}[ ]{.w}*[[symbol]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Compound]{.pre}*
: A CAD symbol of the linear axis positioned relative to_part
:::
::: {#joints.xhtml#cylindrical-joint .section}
## Cylindrical Joint
A [`CylindricalJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.CylindricalJoint "joints.CylindricalJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} allows a component to rotate around and moves along a single axis like a screw combining the functionality of a [`LinearJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.LinearJoint "joints.LinearJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} and a [`RevoluteJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.RevoluteJoint "joints.RevoluteJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} joint. The `connect_to`{.docutils .literal .notranslate} for these joints have both `position`{.docutils .literal .notranslate} and `angle`{.docutils .literal .notranslate} parameters as shown below extracted from the joint tutorial.
*[class]{.pre}[ ]{.w}*[[CylindricalJoint]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[label]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[to_part]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Solid]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[axis]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Axis]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[((0.0,]{.pre} [0.0,]{.pre} [0.0),]{.pre} [(0.0,]{.pre} [0.0,]{.pre} [1.0))]{.pre}]{.default_value}*, *[[angle_reference]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Vector]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Sequence]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[linear_range]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[(0,]{.pre} [inf)]{.pre}]{.default_value}*, *[[angular_range]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[(0,]{.pre} [360)]{.pre}]{.default_value}*[)]{.sig-paren}
: Component rotates around and moves along a single axis like a screw.
Parameters[:]{.colon}
: - **label** (*str*) -- joint label
- **to_part** (*Union\[*[*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- object to attach joint to
- **axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- axis of rotation and linear motion
- **angle_reference** (*VectorLike,* *optional*) -- direction normal to axis defining where angles will be measured from. Defaults to None.
- **linear_range** (*tuple\[float,* *float\],* *optional*) -- (min,max) position of joint. Defaults to (0, inf).
- **angular_range** (*tuple\[float,* *float\],* *optional*) -- (min,max) angle of joint. Defaults to (0, 360).
Variables[:]{.colon}
: - **axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint axis
- **linear_position** (*float*) -- linear joint position
- **rotational_position** (*float*) -- revolute joint angle in degrees
- **angle_reference** ([*Vector*](#direct_api_reference.xhtml#geometry.Vector "geometry.Vector"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- reference for angular positions
- **angular_range** (*tuple\[float,float\]*) -- min and max angular position of joint
- **linear_range** (*tuple\[float,float\]*) -- min and max positional values
- **relative_axis** ([*Axis*](#direct_api_reference.xhtml#geometry.Axis "geometry.Axis"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint axis relative to bound part
- **position** (*float*) -- joint position
- **angle** (*float*) -- angle of joint
Raises[:]{.colon}
: **ValueError** -- angle_reference must be normal to axis
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: Connect CylindricalJoint and RigidJoint"
Parameters[:]{.colon}
: - **other** ([*Joint*](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint to connect to
- **position** (*float,* *optional*) -- linear position. Defaults to linear range min.
- **angle** (*float,* *optional*) -- angle in degrees. Defaults to range min.
Raises[:]{.colon}
: - **TypeError** -- other must be of type RigidJoint
- **ValueError** -- position out of range
- **ValueError** -- angle out of range
*[property]{.pre}[ ]{.w}*[[location]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Location]{.pre}*
: Location of joint
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[position]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angle]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: Relative location of CylindricalJoint to RigidJoint
Parameters[:]{.colon}
: - **other** ([*Joint*](#direct_api_reference.xhtml#topology.Joint "topology.Joint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint to connect to
- **position** (*float,* *optional*) -- linear position. Defaults to linear range min.
- **angle** (*float,* *optional*) -- angle in degrees. Defaults to range min.
Raises[:]{.colon}
: - **TypeError** -- other must be of type RigidJoint
- **ValueError** -- position out of range
- **ValueError** -- angle out of range
*[property]{.pre}[ ]{.w}*[[symbol]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Compound]{.pre}*
: A CAD symbol representing the cylindrical axis as bound to part
:::
::: {#joints.xhtml#ball-joint .section}
## Ball Joint
A component rotates around all 3 axes using a gimbal system (3 nested rotations). A [`BallJoint`{.xref .py .py-class .docutils .literal .notranslate}](#joints.xhtml#joints.BallJoint "joints.BallJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal} is found within a rod end as shown here:

::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
from bd_warehouse.thread import IsoThread
from ocp_vscode import *
# Create the thread so the min radius is available below
thread = IsoThread(major_diameter=6, pitch=1, length=20, end_finishes=("fade", "raw"))
inner_radius = 15.89 / 2
inner_gap = 0.2
with BuildPart() as rod_end:
# Create the outer shape
with BuildSketch():
Circle(22.25 / 2)
with Locations((0, -12)):
Rectangle(8, 1)
make_hull()
split(bisect_by=Plane.YZ)
revolve(axis=Axis.Y)
# Refine the shape
with BuildSketch(Plane.YZ) as s2:
Rectangle(25, 8, align=(Align.MIN, Align.CENTER))
Rectangle(9, 10, align=(Align.MIN, Align.CENTER))
chamfer(s2.vertices(), 0.5)
revolve(axis=Axis.Z, mode=Mode.INTERSECT)
# Add the screw shaft
Cylinder(
thread.min_radius,
30,
rotation=(90, 0, 0),
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
# Cutout the ball socket
Sphere(inner_radius, mode=Mode.SUBTRACT)
# Add thread
with Locations((0, -30, 0)):
add(thread, rotation=(-90, 0, 0))
# Create the ball joint
BallJoint(
"socket",
joint_location=Location(),
angular_range=((-14, 14), (-14, 14), (0, 360)),
)
with BuildPart() as ball:
Sphere(inner_radius - inner_gap)
Box(50, 50, 13, mode=Mode.INTERSECT)
Hole(4)
ball.part.color = Color("aliceblue")
RigidJoint("ball", joint_location=Location())
rod_end.part.joints["socket"].connect_to(ball.part.joints["ball"], angles=(5, 10, 0))
show(rod_end.part, ball.part, s2)
:::
:::
Note how limits are defined during the instantiation of the ball joint when ensures that the pin or bolt within the rod end does not interfere with the rod end itself. The `connect_to`{.docutils .literal .notranslate} sets the three angles (only two are significant in this example).
*[class]{.pre}[ ]{.w}*[[BallJoint]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[label]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[to_part]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Solid]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Compound]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[joint_location]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Location]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[angular_range]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[tuple]{.pre}[[\[]{.pre}]{.p}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[[,]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[[,]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[[\]]{.pre}]{.p}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[((0,]{.pre} [360),]{.pre} [(0,]{.pre} [360),]{.pre} [(0,]{.pre} [360))]{.pre}]{.default_value}*, *[[angle_reference]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Plane]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[Plane(o=(0.00,]{.pre} [0.00,]{.pre} [0.00),]{.pre} [x=(1.00,]{.pre} [0.00,]{.pre} [0.00),]{.pre} [z=(0.00,]{.pre} [0.00,]{.pre} [1.00))]{.pre}]{.default_value}*[)]{.sig-paren}
: A component rotates around all 3 axes using a gimbal system (3 nested rotations).
Parameters[:]{.colon}
: - **label** (*str*) -- joint label
- **to_part** (*Union\[*[*Solid*](#direct_api_reference.xhtml#topology.Solid "topology.Solid"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\],* *optional*) -- object to attach joint to
- **joint_location** ([*Location*](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- global location of joint
- **angular_range** -- (tuple\[ tuple\[float, float\], tuple\[float, float\], tuple\[float, float\] \], optional): X, Y, Z angle (min, max) pairs. Defaults to ((0, 360), (0, 360), (0, 360)).
- **angle_reference** ([*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *optional*) -- plane relative to part defining zero degrees of rotation. Defaults to Plane.XY.
Variables[:]{.colon}
: - **relative_location** ([*Location*](#direct_api_reference.xhtml#geometry.Location "geometry.Location"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint location relative to bound part
- **angular_range** -- (tuple\[ tuple\[float, float\], tuple\[float, float\], tuple\[float, float\] \]): X, Y, Z angle (min, max) pairs.
- **angle_reference** ([*Plane*](#direct_api_reference.xhtml#geometry.Plane "geometry.Plane"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- plane relative to part defining zero degrees of
[[connect_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[angles]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Rotation]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: Connect BallJoint and RigidJoint
Parameters[:]{.colon}
: - **other** ([*RigidJoint*](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint to connect to
- **angles** (*RotationLike,* *optional*) -- angles about axes in degrees. Defaults to range minimums.
Raises[:]{.colon}
: - **TypeError** -- invalid other joint type
- **ValueError** -- angles out of range
*[property]{.pre}[ ]{.w}*[[location]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Location]{.pre}*
: Location of joint
[[relative_to]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[other]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[[RigidJoint]{.pre}](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}]{.n}*, *[[\*]{.pre}]{.o}*, *[[angles]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Rotation]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[tuple]{.pre}[[\[]{.pre}]{.p}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[,]{.pre}]{.p}[ ]{.w}[float]{.pre}[[\]]{.pre}]{.p}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren}
: relative_to - BallJoint
Return the relative location from this joint to the RigidJoint of another object
Parameters[:]{.colon}
: - **other** ([*RigidJoint*](#joints.xhtml#joints.RigidJoint "joints.RigidJoint"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- joint to connect to
- **angles** (*RotationLike,* *optional*) -- angles about axes in degrees. Defaults to range minimums.
Raises[:]{.colon}
: - **TypeError** -- invalid other joint type
- **ValueError** -- angles out of range
*[property]{.pre}[ ]{.w}*[[symbol]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Compound]{.pre}*
: A CAD symbol representing joint as bound to part
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#assemblies.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#assemblies.xhtml#assemblies .section}
[]{#assemblies.xhtml#assembly}
# Assemblies
Most CAD designs consist of more than one part which are naturally arranged in some type of assembly. Once parts have been assembled in a [`Compound`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} object they can be treated as a unit - i.e. [`moved()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.moved "topology.Shape.moved"){.hxr-hoverxref .hxr-tooltip .reference .internal} or exported.
To create an assembly in build123d, one needs to create a tree of parts by simply assigning either a [`Compound`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} object's `parent`{.docutils .literal .notranslate} or `children`{.docutils .literal .notranslate} attributes. To illustrate the process, we'll extend the [[Joint Tutorial]{.std .std-ref}](#tutorial_joints.xhtml#joint-tutorial){.reference .internal}.
::: {#assemblies.xhtml#assigning-labels .section}
## Assigning Labels
In order keep track of objects one can assign a `label`{.docutils .literal .notranslate} to all [`Shape`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal} objects. Here we'll assign labels to all of the components that will be part of the box assembly:
::: {.highlight-build123d .notranslate}
::: highlight
box.label = "box"
lid.label = "lid"
hinge_outer.label = "outer hinge"
hinge_inner.label = "inner hinge"
m6_screw.label = "M6 screw"
:::
:::
The labels are just strings with no further limitations (they don't have to be unique within the assembly).
:::
::: {#assemblies.xhtml#create-the-assembly-compound .section}
## Create the Assembly Compound
Creation of the assembly is done by simply creating a [`Compound`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} object and assigning appropriate `parent`{.docutils .literal .notranslate} and `children`{.docutils .literal .notranslate} attributes as shown here:
::: {.highlight-build123d .notranslate}
::: highlight
box_assembly = Compound(label="assembly", children=[box, lid, hinge_inner, hinge_outer])
:::
:::
To display the topology of an assembly [`Compound`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal}, the [`show_topology()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.show_topology "topology.Shape.show_topology"){.hxr-hoverxref .hxr-tooltip .reference .internal} method can be used as follows:
::: {.highlight-build123d .notranslate}
::: highlight
print(box_assembly.show_topology())
:::
:::
which results in:
::: {.highlight-default .notranslate}
::: highlight
assembly Compound at 0x7fc8ee235760, Location(p=(0, 0, 0), o=(-0, 0, -0))
├── box Compound at 0x7fc8ee2188b0, Location(p=(0, 0, 50), o=(-0, 0, -0))
├── lid Compound at 0x7fc8ee228460, Location(p=(-26, 0, 181), o=(-180, 30, -0))
├── inner hinge Hinge at 0x7fc9292c3f70, Location(p=(-119, 60, 122), o=(90, 0, -150))
└── outer hinge Hinge at 0x7fc9292c3f40, Location(p=(-150, 60, 50), o=(90, 0, 90))
:::
:::
To add to an assembly [`Compound`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} one can change either `children`{.docutils .literal .notranslate} or `parent`{.docutils .literal .notranslate} attributes.
::: {.highlight-build123d .notranslate}
::: highlight
m6_screw.parent = box_assembly
print(box_assembly.show_topology())
:::
:::
and now the screw is part of the assembly.
::: {.highlight-default .notranslate}
::: highlight
assembly Compound at 0x7fc8ee235760, Location(p=(0, 0, 0), o=(-0, 0, -0))
├── box Compound at 0x7fc8ee2188b0, Location(p=(0, 0, 50), o=(-0, 0, -0))
├── lid Compound at 0x7fc8ee228460, Location(p=(-26, 0, 181), o=(-180, 30, -0))
├── inner hinge Hinge at 0x7fc9292c3f70, Location(p=(-119, 60, 122), o=(90, 0, -150))
├── outer hinge Hinge at 0x7fc9292c3f40, Location(p=(-150, 60, 50), o=(90, 0, 90))
└── M6 screw Compound at 0x7fc8ee235310, Location(p=(-157, -40, 70), o=(-0, -90, -60))
:::
:::
:::
::: {#assemblies.xhtml#shallow-vs-deep-copies-of-shapes .section}
[]{#assemblies.xhtml#shallow-copy}
## Shallow vs. Deep Copies of Shapes
Build123d supports the standard python `copy`{.docutils .literal .notranslate} module which provides two different types of copy operations `copy.copy()`{.docutils .literal .notranslate} and `copy.deepcopy()`{.docutils .literal .notranslate}.
Build123d's implementation of `deepcopy()`{.docutils .literal .notranslate} for the [`Shape`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal} class (e.g. `Solid`{.docutils .literal .notranslate}, `Face`{.docutils .literal .notranslate}, etc.) does just that, creates a complete copy of the original all the way down to the CAD object. `deepcopy`{.docutils .literal .notranslate} is therefore suited to the case where the copy will be subsequently modified to become its own unique item.
However, when building an assembly a common use case is to include many instances of an object, each one identical but in a different location. This is where `copy.copy()`{.docutils .literal .notranslate} is very useful as it copies all of the [`Shape`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal} except for the actual CAD object which instead is a reference to the original (OpenCascade refers this as a `TShape`{.docutils .literal .notranslate}). As it's a reference any changes to the original will be seen in all of the shallow copies.
Consider this example where 100 screws are added to an assembly:
{.align-center}
::: {.highlight-default .notranslate}
::: highlight
screw = import_step("M6-1x12-countersunk-screw.step")
locs = HexLocations(6, 10, 10).local_locations
screw_copies = [copy.deepcopy(screw).locate(loc) for loc in locs]
copy_assembly = Compound(children=screw_copies)
export_step(copy_assembly, "copy_assembly.step")
:::
:::
which takes about 5 seconds to run (on an older computer) and produces a file of size 51938 KB. However, if a shallow copy is used instead:
::: {.highlight-default .notranslate}
::: highlight
screw = import_step("M6-1x12-countersunk-screw.step")
locs = HexLocations(6, 10, 10).local_locations
screw_references = [copy.copy(screw).locate(loc) for loc in locs]
reference_assembly = Compound(children=screw_references)
export_step(reference_assembly, "reference_assembly.step")
:::
:::
this takes about ¼ second and produces a file of size 550 KB - just over 1% of the size of the `deepcopy()`{.docutils .literal .notranslate} version and only 12% larger than the screw's step file.
Using `copy.copy()`{.docutils .literal .notranslate} to create references to the original CAD object for assemblies can substantially reduce the time and resources used to create and store that assembly.
:::
::: {#assemblies.xhtml#shapes-are-anytree-nodes .section}
## Shapes are Anytree Nodes
The build123d assembly constructs are built using the python [anytree](https://anytree.readthedocs.io/en/latest/){.reference .external}[ \[https://anytree.readthedocs.io/en/latest/\]]{.link-target} package by making the build123d [`Shape`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal} class a sub-class of anytree's `NodeMixin`{.docutils .literal .notranslate} class. Doing so adds the following attributes to [`Shape`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}:
- `parent`{.docutils .literal .notranslate} - Parent Node. On set, the node is detached from any previous parent node and attached to the new node.
- `children`{.docutils .literal .notranslate} - Tuple of all child nodes.
- `path`{.docutils .literal .notranslate} - Path of this `Node`{.docutils .literal .notranslate}.
- `iter_path_reverse`{.docutils .literal .notranslate} - Iterate up the tree from the current node.
- `ancestors`{.docutils .literal .notranslate} - All parent nodes and their parent nodes.
- `descendants`{.docutils .literal .notranslate} - All child nodes and all their child nodes.
- `root`{.docutils .literal .notranslate} - Tree Root Node.
- `siblings`{.docutils .literal .notranslate} - Tuple of nodes with the same parent.
- `leaves`{.docutils .literal .notranslate} - Tuple of all leaf nodes.
- `is_leaf`{.docutils .literal .notranslate} - `Node`{.docutils .literal .notranslate} has no children (External Node).
- `is_root`{.docutils .literal .notranslate} - `Node`{.docutils .literal .notranslate} is tree root.
- `height`{.docutils .literal .notranslate} - Number of edges on the longest path to a leaf `Node`{.docutils .literal .notranslate}.
- `depth`{.docutils .literal .notranslate} - Number of edges to the root `Node`{.docutils .literal .notranslate}.
::: {.admonition .note}
Note
Changing the `children`{.docutils .literal .notranslate} attribute
Any iterator can be assigned to the `children`{.docutils .literal .notranslate} attribute but subsequently the children are stored as immutable `tuple`{.docutils .literal .notranslate} objects. To add a child to an existing [`Compound`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal} object, the `children`{.docutils .literal .notranslate} attribute will have to be reassigned.
:::
:::
::: {#assemblies.xhtml#iterating-over-compounds .section}
[]{#assemblies.xhtml#pack}
## Iterating Over Compounds
As Compounds are containers for shapes, build123d can iterate over these as required. Complex nested assemblies (compounds within compounds) do not need to be looped over with recursive functions. In the example below, the variable total_volume holds the sum of all the volumes in each solid in an assembly. Compare this to assembly3_volume which only results in the volume of the top level part.
::: {.highlight-python .notranslate}
::: highlight
# [import]
from build123d import *
from ocp_vscode import *
# Each assembly has a box and the previous assembly.
assembly1 = Compound(label='Assembly1', children=[Box(1, 1, 1),])
assembly2 = Compound(label='Assembly2', children=[assembly1, Box(1, 1, 1)])
assembly3 = Compound(label='Assembly3', children=[assembly2, Box(1, 1, 1)])
total_volume = sum(part.volume for part in assembly3.solids()) # 3
assembly3_volume = assembly3.volume # 1
:::
:::
:::
::: {#assemblies.xhtml#id1 .section}
## pack
The [`pack.pack()`{.xref .py .py-meth .docutils .literal .notranslate}](#assemblies.xhtml#pack.pack "pack.pack"){.hxr-hoverxref .hxr-tooltip .reference .internal} function arranges objects in a compact, non-overlapping layout within a square(ish) 2D area. It is designed to minimize the space between objects while ensuring that no two objects overlap.
[[pack]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[objects]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Collection]{.pre}[[\[]{.pre}]{.p}[Shape]{.pre}[[\]]{.pre}]{.p}]{.n}*, *[[padding]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}]{.n}*, *[[align_z]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[bool]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[False]{.pre}]{.default_value}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Collection]{.pre}[[\[]{.pre}]{.p}[Shape]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Pack objects in a squarish area in Plane.XY.
Parameters[:]{.colon}
: - **objects** (*Collection\[*[*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]*) -- objects to arrange
- **padding** (*float*) -- space between objects
- **align_z** (*bool,* *optional*) -- align shape bottoms to Plane.XY. Defaults to False.
Returns[:]{.colon}
: rearranged objects
Return type[:]{.colon}
: *Collection*\[[*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}\]
::: {#assemblies.xhtml#detailed-description .section}
### Detailed Description
The `pack`{.docutils .literal .notranslate} function uses a bin-packing algorithm to efficiently place objects within a 2D plane, ensuring that there is no overlap and that the space between objects is minimized. This is particularly useful in scenarios where spatial efficiency is crucial, such as layout design and object arrangement in constrained spaces.
The function begins by calculating the bounding boxes for each object, including the specified padding. It then uses a helper function `_pack2d`{.docutils .literal .notranslate} to determine the optimal positions for each object within the 2D plane. The positions are then translated back to the original objects, ensuring that they are arranged without overlapping.
:::
::: {#assemblies.xhtml#usage-note .section}
### Usage Note
The `align_z`{.docutils .literal .notranslate} parameter is especially useful when creating print-plates for 3D printing. By aligning the bottoms of the shapes to the same XY plane, you ensure that the objects are perfectly positioned for slicing software, which will no longer need to perform this alignment for you. This can streamline the process and improve the accuracy of the print setup.
:::
::: {#assemblies.xhtml#example-usage .section}
### Example Usage
::: {.highlight-python .notranslate}
::: highlight
# [import]
from build123d import *
from ocp_vscode import *
# [initial space]
b1 = Box(100, 100, 100, align=(Align.CENTER, Align.CENTER, Align.MIN))
b2 = Box(54, 54, 54, align=(Align.CENTER, Align.CENTER, Align.MAX), mode=Mode.SUBTRACT)
b3 = Box(34, 34, 34, align=(Align.MIN, Align.MIN, Align.CENTER), mode=Mode.SUBTRACT)
b4 = Box(24, 24, 24, align=(Align.MAX, Align.MAX, Align.CENTER), mode=Mode.SUBTRACT)
:::
:::
{.align-center}
::: {.highlight-python .notranslate}
::: highlight
# [pack 2D]
xy_pack = pack(
[b1, b2, b3, b4],
padding=5,
align_z=False
)
:::
:::
{.align-center}
::: {.highlight-python .notranslate}
::: highlight
# [Pack and align_z]
z_pack = pack(
[b1, b2, b3, b4],
padding=5,
align_z=True
)
:::
:::
{.align-center}
:::
::: {#assemblies.xhtml#tip .section}
### Tip
If you place the arranged objects into a `Compound`{.docutils .literal .notranslate}, you can easily determine their bounding box and check whether the objects fit on your print bed.
::: {.highlight-python .notranslate}
::: highlight
# [bounding box]
print(Compound(xy_pack).bounding_box())
# bbox: 0.0 <= x <= 159.0, 0.0 <= y <= 129.0, -54.0 <= z <= 100.0
print(Compound(z_pack).bounding_box())
# bbox: 0.0 <= x <= 159.0, 0.0 <= y <= 129.0, 0.0 <= z <= 100.0
:::
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#tips.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#tips.xhtml#tips-best-practices-and-faq .section}
# Tips, Best Practices and FAQ
Although there are countless ways to create objects with build123d, experience has proven that certain techniques can assist designers in achieving their goals with the greatest efficiency. The following is a description of these techniques.
::: {#tips.xhtml#can-t-get-there-from-here .section}
## Can't Get There from Here
Unfortunately, it's a reality that not all parts described using build123d can be successfully constructed by the underlying CAD core. Designers may have to explore different design approaches to get the OpenCascade CAD core to successfully build the target object. For instance, if a multi-section [`sweep()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.sweep "operations_generic.sweep"){.hxr-hoverxref .hxr-tooltip .reference .internal} operation fails, a [`loft()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.loft "operations_part.loft"){.hxr-hoverxref .hxr-tooltip .reference .internal} operation may be a viable alternative in certain situations. It's crucial to remember that CAD is a complex field and patience may be required to achieve the desired results.
:::
::: {#tips.xhtml#d-before-3d .section}
## 2D before 3D
When creating complex 3D objects, it is generally best to start with 2D work before moving on to 3D. This is because 3D structures are much more intricate, and 3D operations can be slower and more prone to failure. For designers who come from a Constructive Solid Geometry (CSG) background, such as OpenSCAD, this approach may seem counterintuitive. On the other hand, designers from a GUI BREP CAD background, like Fusion 360 or SolidWorks, may find this approach more natural.
In practice, this means that 3D objects are often created by applying operations like [`extrude()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.extrude "operations_part.extrude"){.hxr-hoverxref .hxr-tooltip .reference .internal} or [`revolve()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.revolve "operations_part.revolve"){.hxr-hoverxref .hxr-tooltip .reference .internal} to 2D sketches, as shown below:
::: {.highlight-python .notranslate}
::: highlight
with BuildPart() as my_part:
with BuildSketch() as part_profile:
...
extrude(amount=some_distance)
...
:::
:::
With this structure `part_profile`{.docutils .literal .notranslate} may have many objects that are combined and modified by operations like [`fillet()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_generic.fillet "operations_generic.fillet"){.hxr-hoverxref .hxr-tooltip .reference .internal} before being extruded to a 3D shape.
:::
::: {#tips.xhtml#delay-chamfers-and-fillets .section}
## Delay Chamfers and Fillets
Chamfers and fillets can add complexity to a design by transforming simple vertices or edges into arcs or non-planar faces. This can significantly increase the complexity of the design. To avoid unnecessary processing costs and potential errors caused by a needlessly complicated design, it's recommended to perform these operations towards the end of the object's design. This is especially true for 3D shapes, as it is sometimes necessary to fillet or chamfer in the 2D design phase. Luckily, these 2D fillets and chamfers are less likely to fail than their 3D counterparts.
:::
::: {#tips.xhtml#parameterize .section}
## Parameterize
One of the most powerful features of build123d is the ability to design fully parameterized parts. While it may be faster to use a GUI CAD package for the initial iteration of a part, subsequent iterations can prove frustratingly difficult. By using variables for critical dimensions and deriving other dimensions from these key variables, not only can a single part be created, but a whole set of parts can be readily available. When inevitable change requests arise, a simple parameter adjustment may be all that's required to make necessary modifications.
:::
::: {#tips.xhtml#use-shallow-copies .section}
## Use Shallow Copies
As discussed in the Assembly section, a [[shallow copy]{.std .std-ref}](#assemblies.xhtml#shallow-copy){.reference .internal} of parts that are repeated in your design can make a huge difference in performance and usability of your end design. Objects like fasteners, bearings, chain links, etc. could be duplicated tens or even hundreds of times otherwise. Use shallow copies where possible but keep in mind that if one instance of the object changes all will change.
:::
::: {#tips.xhtml#object-selection .section}
## Object Selection
When selecting features in a design it's sometimes easier to select an object from higher up in the topology first, then select the object from there. For example let's consider a plate with four chamfered holes like this:
{.align-center}
When selecting edges to be chamfered one might first select the face that these edges belong to then select the edges as shown here:
::: {.highlight-build123d .notranslate}
::: highlight
from build123d import *
svg_opts = {"pixel_scale": 5, "show_axes": False, "show_hidden": True}
length, width, thickness = 80.0, 60.0, 10.0
hole_dia = 6.0
with BuildPart() as plate:
Box(length, width, thickness)
with GridLocations(length - 20, width - 20, 2, 2):
Hole(radius=hole_dia / 2)
top_face: Face = plate.faces().sort_by(Axis.Z)[-1]
hole_edges = top_face.edges().filter_by(GeomType.CIRCLE)
chamfer(hole_edges, length=1)
:::
:::
:::
::: {#tips.xhtml#build123d-cadquery-integration .section}
## Build123d - CadQuery Integration
As both [CadQuery](https://cadquery.readthedocs.io/en/latest/index.html){.reference .external}[ \[https://cadquery.readthedocs.io/en/latest/index.html\]]{.link-target} and **build123d** use a common OpenCascade Python wrapper ([OCP](https://github.com/CadQuery/OCP){.reference .external}[ \[https://github.com/CadQuery/OCP\]]{.link-target}) it's possible to interchange objects both from CadQuery to build123d and vice-versa by transferring the `wrapped`{.docutils .literal .notranslate} objects as follows (first from CadQuery to build123d):
::: {.highlight-build123d .notranslate}
::: highlight
import build123d as b3d
b3d_solid = b3d.Solid.make_box(1,1,1)
... some cadquery stuff ...
b3d_solid.wrapped = cq_solid.wrapped
:::
:::
Secondly, from build123d to CadQuery as follows:
::: {.highlight-build123d .notranslate}
::: highlight
import build123d as b3d
import cadquery as cq
with b3d.BuildPart() as b123d_box:
b3d.Box(1,2,3)
cq_solid = cq.Solid.makeBox(1,1,1)
cq_solid.wrapped = b123d_box.part.solid().wrapped
:::
:::
:::
::: {#tips.xhtml#self-intersection .section}
## Self Intersection
Avoid creating objects that intersect themselves - even if at a single vertex - as these topologies will almost certainly be invalid (even if [`is_valid()`{.xref .py .py-meth .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape.is_valid "topology.Shape.is_valid"){.hxr-hoverxref .hxr-tooltip .reference .internal} reports a `True`{.docutils .literal .notranslate} value). An example of where this may arise is with the thread of a screw (or any helical shape) where after one complete revolution the part may contact itself. One is likely be more successful if the part is split into multiple sections - say 180° of a helix - which are then stored in an assembly.
:::
::: {#tips.xhtml#packing-objects-on-a-plane .section .clearfix}
## Packing Objects on a Plane
When designing independent shapes it's common to place each at or near the global origin, which can make it tricky to visualize many shapes at once. [`pack.pack()`{.xref .py .py-meth .docutils .literal .notranslate}](#assemblies.xhtml#pack.pack "pack.pack"){.hxr-hoverxref .hxr-tooltip .reference .internal} will translate the [`Shape`{.xref .py .py-class .docutils .literal .notranslate}](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}'s passed to it so that they don't overlap, with an optional padding/spacing. Here's the result of packing a bunch of overlapping boxes (left) using some padding (right):
{.align-left style="width: 200px;"} {.align-right}
By default, the original Z value of all objects packed using the [`pack.pack()`{.xref .py .py-meth .docutils .literal .notranslate}](#assemblies.xhtml#pack.pack "pack.pack"){.hxr-hoverxref .hxr-tooltip .reference .internal} function is preserved. If you want to align all objects so that they are "placed" on the zero Z coordinate, the [`pack()`{.xref .py .py-meth .docutils .literal .notranslate}](#assemblies.xhtml#module-pack "pack"){.hxr-hoverxref .hxr-tooltip .reference .internal} function has an ``{=html}align_z``{=html} argument. When set to ``{=html}True``{=html}, this will align all objects.
This can be useful, for example, when preparing print setups for 3D printing, giving you full control over this alignment so you don't have to leave it to the slicer.
:::
::: {#tips.xhtml#isnt-from-build123d-import-bad-practice .section}
[]{#tips.xhtml#are-glob-imports-bad-practice}
## Isn't `from build123d import *`{.docutils .literal .notranslate} bad practice?
Glob imports like `from build123d import *`{.docutils .literal .notranslate} are generally frowned upon when writing software, and for good reason. They pollute the global namespace, cause confusing collisions, and are not future-proof, as future changes to the library being imported could collide with other names. It would be much safer to do something like `import build123d as bd`{.docutils .literal .notranslate} and then reference every item with, for example, `bd.BuildPart()`{.docutils .literal .notranslate}. If your goal is to integrate build123d into a larger piece of software, which many people work on, or where long-term maintainability is a priority, using this approach is definitely a good idea! Why then, are glob imports so often used in build123d code and official examples?
build123d is most commonly used not as a library within a larger application, but as a [Domain-Specific Language](https://en.wikipedia.org/wiki/Domain-specific_language){.reference .external}[ \[https://en.wikipedia.org/wiki/Domain-specific_language\]]{.link-target} which, together with something like the OCP CAD Viewer, acts as the user interface for a CAD application. Writing build123d often involves live coding in a REPL or typing in editors with limited space due to the rest of the CAD GUI taking up screen space. Scripts are usually centred around build123d usage, with usage of other libraries being limited enough that naming conflicts are easily avoided. In this context, it's entirely reasonable to prioritise developer ergonomics over "correctness" by making build123d's primitives available in the global namespace.
:::
::: {#tips.xhtml#why-doesn-t-buildsketch-plane-xz-work .section}
## Why doesn't BuildSketch(Plane.XZ) work?
When creating a sketch not on the default `Plane.XY`{.docutils .literal .notranslate} users may expect that they are drawing directly on the workplane / coordinate system provided. For example:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch(Plane.XZ) as vertical_sketch:
Rectangle(1, 1)
with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1]):
Circle(0.2)
:::
:::

In this case the circle is not positioned in the top right as one would expect; in-fact, the position of the circle randomly switches between the bottom and top corner.
This is because all sketches are created on a local `Plane.XY`{.docutils .literal .notranslate} independent of where they will be ultimately placed; therefore, the `sort_by(Axis.Z)`{.docutils .literal .notranslate} is sorting two points that have a Z value of zero as they are located on `Plane.XY`{.docutils .literal .notranslate} and effectively return a random point.
Why does `BuildSketch`{.docutils .literal .notranslate} work this way? Consider an example where the user wants to work on a plane not aligned with any Axis, as follows (this is often done when creating a sketch on a `Face`{.docutils .literal .notranslate} of a 3D part but is simulated here by rotating a `Plane`{.docutils .literal .notranslate}):
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch(Plane.YZ.rotated((123, 45, 6))) as custom_plane:
Rectangle(1, 1, align=Align.MIN)
with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Y)[-1]):
Circle(0.2)
:::
:::

Here one can see both `sketch_local`{.docutils .literal .notranslate} (with the light fill on `Plane.XY`{.docutils .literal .notranslate}) and the `sketch`{.docutils .literal .notranslate} (with the darker fill) placed on the user provided workplane. As the selectors work off global coordinates, selection of the "top right" of this sketch would be quite challenging and would likely change if the sketch was ever moved as could happen if the 3D part changed. For an example of sketching on a 3D part, see [[Sketching on other Planes]{.std .std-ref}](#build_sketch.xhtml#sketching-on-other-planes){.reference .internal}.
:::
::: {#tips.xhtml#why-is-buildline-not-working-as-expected-within-the-scope-of-buildsketch .section}
## Why is BuildLine not working as expected within the scope of BuildSketch?
As described above, all sketching is done on a local `Plane.XY`{.docutils .literal .notranslate}; however, the following is a common issue:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildSketch() as sketch:
with BuildLine(Plane.XZ):
Polyline(...)
make_face()
:::
:::
Here `BuildLine`{.docutils .literal .notranslate} is within the scope of `BuildSketch`{.docutils .literal .notranslate}; therefore, all of the drawing should be done on `Plane.XY`{.docutils .literal .notranslate}; however, the user has specified `Plane.XZ`{.docutils .literal .notranslate} when creating the `BuildLine`{.docutils .literal .notranslate} instance. Although this isn't absolutely incorrect it's almost certainly not what the user intended. Here the face created by `make_face`{.docutils .literal .notranslate} will be reoriented to `Plane.XY`{.docutils .literal .notranslate} as all sketching must be done on that plane. This reorienting of objects to `Plane.XY`{.docutils .literal .notranslate} allows a user to `add`{.docutils .literal .notranslate} content from other sources to the sketch without having to manually re-orient the object.
Unless there is a good reason and the user understands how the `BuildLine`{.docutils .literal .notranslate} object will be reoriented, all `BuildLine`{.docutils .literal .notranslate} instances within the scope of `BuildSketch`{.docutils .literal .notranslate} should be done on the default `Plane.XY`{.docutils .literal .notranslate}.
:::
::: {#tips.xhtml#don-t-builders-inherit-workplane-coordinate-systems-when-nested .section}
## Don't Builders inherit workplane/coordinate systems when nested
Some users expect that nested Builders will inherit the workplane or coordinate system from their parent Builder - this is not true. When a Builder is instantiated, a workplane is either provided by the user or it defaults to `Plane.XY`{.docutils .literal .notranslate}. Having Builders inherent coordinate systems from their parents could result in confusion when they are nested as well as change their behaviour depending on which scope they are in. Inheriting coordinate systems isn't necessarily incorrect, it was considered for build123d but ultimately the simple static approach was taken.
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#import_export.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#import_export.xhtml#import-export .section}
# Import/Export
Methods and functions specific to exporting and importing build123d objects are defined below.
For example:
::: {.highlight-build123d .notranslate}
::: highlight
with BuildPart() as box_builder:
Box(1, 1, 1)
export_step(box_builder.part, "box.step")
:::
:::
::: {#import_export.xhtml#file-formats .section}
## File Formats
::: {#import_export.xhtml#mf .section}
### 3MF
The 3MF (3D Manufacturing Format) file format is a versatile and modern standard for representing 3D models used in additive manufacturing, 3D printing, and other applications. Developed by the 3MF Consortium, it aims to overcome the limitations of traditional 3D file formats by providing a more efficient and feature-rich solution. The 3MF format supports various advanced features like color information, texture mapping, multi-material definitions, and precise geometry representation, enabling seamless communication between design software, 3D printers, and other manufacturing devices. Its open and extensible nature makes it an ideal choice for exchanging complex 3D data in a compact and interoperable manner.
:::
::: {#import_export.xhtml#brep .section}
### BREP
The BREP (Boundary Representation) file format is a widely used data format in computer-aided design (CAD) and computer-aided engineering (CAE) applications. BREP represents 3D geometry using topological entities like vertices, edges, and faces, along with their connectivity information. It provides a precise and comprehensive representation of complex 3D models, making it suitable for advanced modeling and analysis tasks. BREP files are widely supported by various CAD software, enabling seamless data exchange between different systems. Its ability to represent both geometric shapes and their topological relationships makes it a fundamental format for storing and sharing detailed 3D models.
:::
::: {#import_export.xhtml#dxf .section}
### DXF
The DXF (Drawing Exchange Format) file format is a widely used standard for representing 2D and 3D drawings, primarily used in computer-aided design (CAD) applications. Developed by Autodesk, DXF files store graphical and geometric data, such as lines, arcs, circles, and text, as well as information about layers, colors, and line weights. Due to its popularity, DXF files can be easily exchanged and shared between different CAD software. The format's simplicity and human-readable structure make it a versatile choice for sharing designs, drawings, and models across various CAD platforms, facilitating seamless collaboration in engineering and architectural projects.
:::
::: {#import_export.xhtml#gltf .section}
### glTF
The glTF (GL Transmission Format) is a royalty-free specification for the efficient transmission and loading of 3D models and scenes by applications. Developed by the Khronos Group, glTF is designed as a compact, interoperable format that enables the quick display of assets across various platforms and devices. glTF supports a rich feature set, including detailed meshes, materials, textures, skeletal animations, and more, facilitating complex 3D visualizations. It streamlines the process of sharing and deploying 3D content in web applications, game engines, and other visualization tools, making it the "JPEG of 3D." glTF's versatility and efficiency have led to its widespread adoption in the 3D content industry.
:::
::: {#import_export.xhtml#stl .section}
### STL
The STL (STereoLithography) file format is a widely used file format in 3D printing and computer-aided design (CAD) applications. It represents 3D geometry using triangular facets to approximate the surface of a 3D model. STL files are widely supported and can store both the geometry and color information of the model. They are used for rapid prototyping and 3D printing, as they provide a simple and efficient way to represent complex 3D objects. The format's popularity stems from its ease of use, platform independence, and ability to accurately describe the surface of intricate 3D models with a minimal file size.
:::
::: {#import_export.xhtml#step .section}
### STEP
The STEP (Standard for the Exchange of Product model data) file format is a widely used standard for representing 3D product and manufacturing data in computer-aided design (CAD) and computer-aided engineering (CAE) applications. It is an ISO standard (ISO 10303) and supports the representation of complex 3D geometry, product structure, and metadata. STEP files store information in a neutral and standardized format, making them highly interoperable across different CAD/CAM software systems. They enable seamless data exchange between various engineering disciplines, facilitating collaboration and data integration throughout the entire product development and manufacturing process.
:::
::: {#import_export.xhtml#svg .section}
### SVG
The SVG (Scalable Vector Graphics) file format is an XML-based standard used for describing 2D vector graphics. It is widely supported and can be displayed in modern web browsers, making it suitable for web-based graphics and interactive applications. SVG files define shapes, paths, text, and images using mathematical equations, allowing for smooth scalability without loss of quality. The format is ideal for logos, icons, illustrations, and other graphics that require resolution independence. SVG files are also easily editable in text editors or vector graphic software, making them a popular choice for designers and developers seeking flexible and versatile graphic representation.
:::
:::
::: {#import_export.xhtml#d-exporters .section}
## 2D Exporters
Exports to DXF (Drawing Exchange Format) and SVG (Scalable Vector Graphics) are provided by the 2D Exporters: ExportDXF and ExportSVG classes.
DXF is a widely used file format for exchanging CAD (Computer-Aided Design) data between different software applications. SVG is a widely used vector graphics format that is supported by web browsers and various graphic editors.
The core concept to these classes is the creation of a DXF/SVG document with specific properties followed by the addition of layers and shapes to the documents. Once all of the layers and shapes have been added, the document can be written to a file.
::: {#import_export.xhtml#d-to-2d-projection .section}
### 3D to 2D Projection
There are a couple ways to generate a 2D drawing of a 3D part:
- Generate a section: The [`section()`{.xref .py .py-func .docutils .literal .notranslate}](#operations.xhtml#operations_part.section "operations_part.section"){.hxr-hoverxref .hxr-tooltip .reference .internal} operation can be used to create a 2D cross section of a 3D part at a given plane.
- Generate a projection: The `project_to_viewport()`{.xref .py .py-meth .docutils .literal .notranslate} method can be used to create a 2D projection of a 3D scene. Similar to a camera, the `viewport_origin`{.docutils .literal .notranslate} defines the location of camera, the `viewport_up`{.docutils .literal .notranslate} defines the orientation of the camera, and the `look_at`{.docutils .literal .notranslate} parameter defined where the camera is pointed. By default, `viewport_up`{.docutils .literal .notranslate} is the positive z axis and `look_up`{.docutils .literal .notranslate} is the center of the shape. The return value is a tuple of lists of edges, the first the visible edges and the second the hidden edges.
Each of these Edges and Faces can be assigned different line color/types and fill colors as described below (as `project_to_viewport`{.docutils .literal .notranslate} only generates Edges, fill doesn't apply). The shapes generated from the above steps are to be added as shapes in one of the exporters described below and written as either a DXF or SVG file as shown in this example:
::: {.highlight-build123d .notranslate}
::: highlight
view_port_origin=(-100, -50, 30)
visible, hidden = part.project_to_viewport(view_port_origin)
max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)
exporter = ExportSVG(scale=100 / max_dimension)
exporter.add_layer("Visible")
exporter.add_layer("Hidden", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)
exporter.add_shape(visible, layer="Visible")
exporter.add_shape(hidden, layer="Hidden")
exporter.write("part_projection.svg")
:::
:::
:::
::: {#import_export.xhtml#linetype .section}
### LineType
ANSI (American National Standards Institute) and ISO (International Organization for Standardization) standards both define line types in drawings used in DXF and SVG exported drawings:
-
ANSI Standards:
: - ANSI/ASME Y14.2 - "Line Conventions and Lettering" is the standard that defines line types, line weights, and line usage in engineering drawings in the United States.
-
ISO Standards:
: - ISO 128 - "Technical drawings -- General principles of presentation" is the ISO standard that covers the general principles of technical drawing presentation, including line types and line conventions.
- ISO 13567 - "Technical product documentation (TPD) -- Organization and naming of layers for CAD" provides guidelines for the organization and naming of layers in Computer-Aided Design (CAD) systems, which may include line type information.
These standards help ensure consistency and clarity in technical drawings, making it easier for engineers, designers, and manufacturers to communicate and interpret the information presented in the drawings.
The line types used by the 2D Exporters are defined by the `LineType`{.xref .py .py-class .docutils .literal .notranslate} Enum and are shown in the following diagram:
{.align-center}
:::
::: {#import_export.xhtml#exportdxf .section}
### ExportDXF
*[class]{.pre}[ ]{.w}*[[ExportDXF]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[version:]{.pre} [str]{.pre} [=]{.pre} [\'AC1027\']{.pre}]{.n}*, *[[unit:]{.pre} [\~build123d.build_enums.Unit]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[color:]{.pre} [\~exporters.ColorIndex]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[line_weight:]{.pre} [float]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[line_type:]{.pre} [\~exporters.LineType]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*[)]{.sig-paren}
: The ExportDXF class provides functionality for exporting 2D shapes to DXF (Drawing Exchange Format) format. DXF is a widely used file format for exchanging CAD (Computer-Aided Design) data between different software applications.
Parameters[:]{.colon}
: - **version** (*str,* *optional*) -- The DXF version to use for the output file. Defaults to ezdxf.DXF2013.
- **unit** (*Unit,* *optional*) -- The unit used for the exported DXF. It should be one of the Unit enums: Unit.MC, Unit.MM, Unit.CM, Unit.M, Unit.IN, or Unit.FT. Defaults to Unit.MM.
- **color** (*Optional\[ColorIndex\],* *optional*) -- The default color index for shapes. It can be specified as a ColorIndex enum or None.. Defaults to None.
- **line_weight** (*Optional\[float\],* *optional*) -- The default line weight (stroke width) for shapes, in millimeters. . Defaults to None.
- **line_type** (*Optional\[LineType\],* *optional*) -- e default line type for shapes. It should be a LineType enum or None.. Defaults to None.
Example
::: {.highlight-python .notranslate}
::: highlight
exporter = ExportDXF(unit=Unit.MM, line_weight=0.5)
exporter.add_layer("Layer 1", color=ColorIndex.RED, line_type=LineType.DASHED)
exporter.add_shape(shape_object, layer="Layer 1")
exporter.write("output.dxf")
:::
:::
Raises[:]{.colon}
: **ValueError** -- unit not supported
[[METRIC_UNITS]{.pre}]{.sig-name .descname}*[ ]{.w}[[=]{.pre}]{.p}[ ]{.w}[{\,]{.pre} [\,]{.pre} [\}]{.pre}*
:
[[add_layer]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[\*]{.pre}]{.o}*, *[[color]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[ColorIndex]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[line_weight]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[line_type]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[LineType]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Self]{.pre}]{.sig-return-typehint}]{.sig-return}
: Adds a new layer to the DXF export with the given properties.
Parameters[:]{.colon}
: - **name** (*str*) -- The name of the layer definition. Must be unique among all layers.
- **color** (*Optional\[ColorIndex\],* *optional*) -- The color index for shapes on this layer. It can be specified as a ColorIndex enum or None. Defaults to None.
- **line_weight** (*Optional\[float\],* *optional*) -- The line weight (stroke width) for shapes on this layer, in millimeters. Defaults to None.
- **line_type** (*Optional\[LineType\],* *optional*) -- The line type for shapes on this layer. It should be a LineType enum or None. Defaults to None.
Returns[:]{.colon}
: DXF document with additional layer
Return type[:]{.colon}
: Self
[[add_shape]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[shape]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Shape]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Iterable]{.pre}[[\[]{.pre}]{.p}[Shape]{.pre}[[\]]{.pre}]{.p}]{.n}*, *[[layer]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[\'\']{.pre}]{.default_value}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Self]{.pre}]{.sig-return-typehint}]{.sig-return}
: Adds a shape to the specified layer.
Parameters[:]{.colon}
: - **shape** ([*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *Iterable\[*[*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]*) -- The shape or collection of shapes to be added. It can be a single Shape object or an iterable of Shape objects.
- **layer** (*str,* *optional*) -- The name of the layer where the shape will be added. If not specified, the default layer will be used. Defaults to "".
Returns[:]{.colon}
: Document with additional shape
Return type[:]{.colon}
: Self
[[write]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[file_name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[BytesIO]{.pre}]{.n}*[)]{.sig-paren}
: Writes the DXF data to the specified file name.
Parameters[:]{.colon}
: **file_name** (*PathLike* *\|* *str* *\|* *bytes* *\|* *BytesIO*) -- The file name (including path) where the DXF data will be written.
:::
::: {#import_export.xhtml#exportsvg .section}
### ExportSVG
*[class]{.pre}[ ]{.w}*[[ExportSVG]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[unit:]{.pre} [\~build123d.build_enums.Unit]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[scale:]{.pre} [float]{.pre} [=]{.pre} [1]{.pre}]{.n}*, *[[margin:]{.pre} [float]{.pre} [=]{.pre} [0]{.pre}]{.n}*, *[[fit_to_stroke:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[precision:]{.pre} [int]{.pre} [=]{.pre} [6]{.pre}]{.n}*, *[[fill_color:]{.pre} [\~exporters.ColorIndex]{.pre} [\|]{.pre} [\~ezdxf.colors.RGB]{.pre} [\|]{.pre} [\~build123d.geometry.Color]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*, *[[line_color:]{.pre} [\~exporters.ColorIndex]{.pre} [\|]{.pre} [\~ezdxf.colors.RGB]{.pre} [\|]{.pre} [\~build123d.geometry.Color]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [ColorIndex.BLACK]{.pre}]{.n}*, *[[line_weight:]{.pre} [float]{.pre} [=]{.pre} [0.09]{.pre}]{.n}*, *[[line_type:]{.pre} [\~exporters.LineType]{.pre} [=]{.pre} [LineType.CONTINUOUS]{.pre}]{.n}*, *[[dot_length:]{.pre} [\~exporters.DotLength]{.pre} [\|]{.pre} [float]{.pre} [=]{.pre} [DotLength.INKSCAPE_COMPAT]{.pre}]{.n}*[)]{.sig-paren}
: SVG file export functionality.
The ExportSVG class provides functionality for exporting 2D shapes to SVG (Scalable Vector Graphics) format. SVG is a widely used vector graphics format that is supported by web browsers and various graphic editors.
Parameters[:]{.colon}
: - **unit** (*Unit,* *optional*) -- The unit used for the exported SVG. It should be one of the Unit enums: Unit.MM, Unit.CM, or Unit.IN. Defaults to Unit.MM.
- **scale** (*float,* *optional*) -- The scaling factor applied to the exported SVG. Defaults to 1.
- **margin** (*float,* *optional*) -- The margin added around the exported shapes. Defaults to 0.
- **fit_to_stroke** (*bool,* *optional*) -- A boolean indicating whether the SVG view box should fit the strokes of the shapes. Defaults to True.
- **precision** (*int,* *optional*) -- The number of decimal places used for rounding coordinates in the SVG. Defaults to 6.
- **fill_color** (*ColorIndex* *\|* *RGB* *\|* *None,* *optional*) -- The default fill color for shapes. It can be specified as a ColorIndex, an RGB tuple, or None. Defaults to None.
- **line_color** (*ColorIndex* *\|* *RGB* *\|* *None,* *optional*) -- The default line color for shapes. It can be specified as a ColorIndex or an RGB tuple, or None. Defaults to Export2D.DEFAULT_COLOR_INDEX.
- **line_weight** (*float,* *optional*) -- The default line weight (stroke width) for shapes, in millimeters. Defaults to Export2D.DEFAULT_LINE_WEIGHT.
- **line_type** (*LineType,* *optional*) -- The default line type for shapes. It should be a LineType enum. Defaults to Export2D.DEFAULT_LINE_TYPE.
- **dot_length** (*DotLength* *\|* *float,* *optional*) -- The width of rendered dots in a Can be either a DotLength enum or a float value in tenths of an inch. Defaults to DotLength.INKSCAPE_COMPAT.
Example
::: {.highlight-python .notranslate}
::: highlight
exporter = ExportSVG(unit=Unit.MM, line_weight=0.5)
exporter.add_layer("Layer 1", fill_color=(255, 0, 0), line_color=(0, 0, 255))
exporter.add_shape(shape_object, layer="Layer 1")
exporter.write("output.svg")
:::
:::
Raises[:]{.colon}
: **ValueError** -- Invalid unit.
[[add_layer]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[\*]{.pre}]{.o}*, *[[fill_color]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[ColorIndex]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[RGB]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Color]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[None]{.pre}]{.default_value}*, *[[line_color]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[ColorIndex]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[RGB]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Color]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[None]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[ColorIndex.BLACK]{.pre}]{.default_value}*, *[[line_weight]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[0.09]{.pre}]{.default_value}*, *[[line_type]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[LineType]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[LineType.CONTINUOUS]{.pre}]{.default_value}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Self]{.pre}]{.sig-return-typehint}]{.sig-return}
: Adds a new layer to the SVG export with the given properties.
Parameters[:]{.colon}
: - **name** (*str*) -- The name of the layer. Must be unique among all layers.
- **fill_color** (*ColorIndex* *\|* *RGB* *\|* [*Color*](#direct_api_reference.xhtml#geometry.Color "geometry.Color"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *None,* *optional*) -- The fill color for shapes on this layer. It can be specified as a ColorIndex, an RGB tuple, a Color, or None. Defaults to None.
- **line_color** (*ColorIndex* *\|* *RGB* *\|* [*Color*](#direct_api_reference.xhtml#geometry.Color "geometry.Color"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *None,* *optional*) -- The line color for shapes on this layer. It can be specified as a ColorIndex or an RGB tuple, a Color, or None. Defaults to Export2D.DEFAULT_COLOR_INDEX.
- **line_weight** (*float,* *optional*) -- The line weight (stroke width) for shapes on this layer, in millimeters. Defaults to Export2D.DEFAULT_LINE_WEIGHT.
- **line_type** (*LineType,* *optional*) -- The line type for shapes on this layer. It should be a LineType enum. Defaults to Export2D.DEFAULT_LINE_TYPE.
Raises[:]{.colon}
: - **ValueError** -- Duplicate layer name
- **ValueError** -- Unknown linetype
Returns[:]{.colon}
: Drawing with an additional layer
Return type[:]{.colon}
: Self
[[add_shape]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[shape]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Shape]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Iterable]{.pre}[[\[]{.pre}]{.p}[Shape]{.pre}[[\]]{.pre}]{.p}]{.n}*, *[[layer]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[\'\']{.pre}]{.default_value}*, *[[reverse_wires]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[bool]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[False]{.pre}]{.default_value}*[)]{.sig-paren}
: Adds a shape or a collection of shapes to the specified layer.
Parameters[:]{.colon}
: - **shape** ([*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *Iterable\[*[*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]*) -- The shape or collection of shapes to be added. It can be a single Shape object or an iterable of Shape objects.
- **layer** (*str,* *optional*) -- The name of the layer where the shape(s) will be added. Defaults to "".
- **reverse_wires** (*bool,* *optional*) -- A boolean indicating whether the wires of the shape(s) should be in reversed direction. Defaults to False.
Raises[:]{.colon}
: **ValueError** -- Undefined layer
[[write]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[path]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[BytesIO]{.pre}]{.n}*[)]{.sig-paren}
: Writes the SVG data to the specified file path.
Parameters[:]{.colon}
: **path** (*PathLike* *\|* *str* *\|* *bytes* *\|* *BytesIO*) -- The file path where the SVG data will be written.
:::
:::
::: {#import_export.xhtml#module-exporters3d .section}
[]{#import_export.xhtml#id1}
## 3D Exporters
[[export_brep]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[to_export]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Shape]{.pre}]{.n}*, *[[file_path]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[BytesIO]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[bool]{.pre}]{.sig-return-typehint}]{.sig-return}
: Export this shape to a BREP file
Parameters[:]{.colon}
: - **to_export** ([*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- object or assembly
- **file_path** -- Union\[PathLike, str, bytes, BytesIO\]: brep file path or memory buffer
Returns[:]{.colon}
: write status
Return type[:]{.colon}
: *bool*
```{=html}
```
[[export_gltf]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[to_export:]{.pre} [\~build123d.topology.shape_core.Shape]{.pre}]{.n}*, *[[file_path:]{.pre} [\~os.PathLike]{.pre} [\|]{.pre} [str]{.pre} [\|]{.pre} [bytes]{.pre}]{.n}*, *[[unit:]{.pre} [\~build123d.build_enums.Unit]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[binary:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[linear_deflection:]{.pre} [float]{.pre} [=]{.pre} [0.001]{.pre}]{.n}*, *[[angular_deflection:]{.pre} [float]{.pre} [=]{.pre} [0.1]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[bool]{.pre}]{.sig-return-typehint}]{.sig-return}
: The glTF (GL Transmission Format) specification primarily focuses on the efficient transmission and loading of 3D models as a compact, binary format that is directly renderable by graphics APIs like WebGL, OpenGL, and Vulkan. It's designed to store detailed 3D model data, including meshes (vertices, normals, textures, etc.), animations, materials, and scene hierarchy, among other aspects.
Parameters[:]{.colon}
: - **to_export** ([*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- object or assembly
- **file_path** (*Union\[PathLike,* *str,* *bytes\]*) -- glTF file path
- **unit** (*Unit,* *optional*) -- shape units. Defaults to Unit.MM.
- **binary** (*bool,* *optional*) -- output format. Defaults to False.
- **linear_deflection** (*float,* *optional*) -- A linear deflection setting which limits the distance between a curve and its tessellation. Setting this value too low will result in large meshes that can consume computing resources. Setting the value too high can result in meshes with a level of detail that is too low. The default is a good starting point for a range of cases. Defaults to 1e-3.
- **angular_deflection** (*float,* *optional*) -- Angular deflection setting which limits the angle between subsequent segments in a polyline. Defaults to 0.1.
Raises[:]{.colon}
: **RuntimeError** -- Failed to write glTF file
Returns[:]{.colon}
: write status
Return type[:]{.colon}
: *bool*
```{=html}
```
[[export_step]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[to_export:]{.pre} [\~build123d.topology.shape_core.Shape]{.pre}]{.n}*, *[[file_path:]{.pre} [\~os.PathLike]{.pre} [\|]{.pre} [str]{.pre} [\|]{.pre} [bytes]{.pre} [\|]{.pre} [\~\_io.BytesIO]{.pre}]{.n}*, *[[unit:]{.pre} [\~build123d.build_enums.Unit]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[write_pcurves:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[precision_mode:]{.pre} [\~build123d.build_enums.PrecisionMode]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[\*]{.pre}]{.n}*, *[[timestamp:]{.pre} [str]{.pre} [\|]{.pre} [\~datetime.datetime]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[bool]{.pre}]{.sig-return-typehint}]{.sig-return}
: Export a build123d Shape or assembly with color and label attributes. Note that if the color of a node in an assembly isn't set, it will be assigned the color of its nearest ancestor.
Parameters[:]{.colon}
: - **to_export** ([*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- object or assembly
- **file_path** (*Union\[PathLike,* *str,* *bytes,* *BytesIO\]*) -- step file path
- **unit** (*Unit,* *optional*) -- shape units. Defaults to Unit.MM.
- **write_pcurves** (*bool,* *optional*) -- write parametric curves to the STEP file. Defaults to True.
- **precision_mode** (*PrecisionMode,* *optional*) -- geometric data precision. Defaults to PrecisionMode.AVERAGE.
Raises[:]{.colon}
: **RuntimeError** -- Unknown Compound type
Returns[:]{.colon}
: success
Return type[:]{.colon}
: *bool*
```{=html}
```
[[export_stl]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[to_export]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[Shape]{.pre}]{.n}*, *[[file_path]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}]{.n}*, *[[tolerance]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[0.001]{.pre}]{.default_value}*, *[[angular_tolerance]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[float]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[0.1]{.pre}]{.default_value}*, *[[ascii_format]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[bool]{.pre}]{.n}[ ]{.w}[[=]{.pre}]{.o}[ ]{.w}[[False]{.pre}]{.default_value}*[)]{.sig-paren} [[→]{.sig-return-icon} [[bool]{.pre}]{.sig-return-typehint}]{.sig-return}
: Export STL
Exports a shape to a specified STL file.
Parameters[:]{.colon}
: - **to_export** ([*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}) -- object or assembly
- **file_path** (*Union\[PathLike,* *str,* *bytes\]*) -- The path and file name to write the STL output to.
- **tolerance** (*float,* *optional*) -- A linear deflection setting which limits the distance between a curve and its tessellation. Setting this value too low will result in large meshes that can consume computing resources. Setting the value too high can result in meshes with a level of detail that is too low. The default is a good starting point for a range of cases. Defaults to 1e-3.
- **angular_tolerance** (*float,* *optional*) -- Angular deflection setting which limits the angle between subsequent segments in a polyline. Defaults to 0.1.
- **ascii_format** (*bool,* *optional*) -- Export the file as ASCII (True) or binary (False) STL format. Defaults to False (binary).
Returns[:]{.colon}
: Success
Return type[:]{.colon}
: *bool*
::: {#import_export.xhtml#d-mesh-export .section}
### 3D Mesh Export
Both 3MF and STL export (and import) are provided with the [`Mesher`{.xref .py .py-class .docutils .literal .notranslate}](#import_export.xhtml#mesher.Mesher "mesher.Mesher"){.hxr-hoverxref .hxr-tooltip .reference .internal} class. As mentioned above, the 3MF format it provides is feature-rich and therefore has a slightly more complex API than the simple Shape exporters.
For example:
::: {.highlight-build123d .notranslate}
::: highlight
# Create the shapes and assign attributes
blue_shape = Solid.make_cone(20, 0, 50)
blue_shape.color = Color("blue")
blue_shape.label = "blue"
blue_uuid = uuid.uuid1()
red_shape = Solid.make_cylinder(5, 50).move(Location((0, -30, 0)))
red_shape.color = Color("red")
red_shape.label = "red"
# Create a Mesher instance as an exporter, add shapes and write
exporter = Mesher()
exporter.add_shape(blue_shape, part_number="blue-1234-5", uuid_value=blue_uuid)
exporter.add_shape(red_shape)
exporter.add_meta_data(
name_space="custom",
name="test_meta_data",
value="hello world",
metadata_type="str",
must_preserve=False,
)
exporter.add_code_to_metadata()
exporter.write("example.3mf")
exporter.write("example.stl")
:::
:::
*[class]{.pre}[ ]{.w}*[[Mesher]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[unit:]{.pre} [\~build123d.build_enums.Unit]{.pre} [=]{.pre} [\]{.pre}]{.n}*[)]{.sig-paren}
: Tool for exporting and importing meshed objects stored in 3MF or STL files.
Parameters[:]{.colon}
: **unit** (*Unit,* *optional*) -- model units. Defaults to Unit.MM.
[[add_code_to_metadata]{.pre}]{.sig-name .descname}[(]{.sig-paren}[)]{.sig-paren}
: Add the code calling this method to the 3MF metadata with the custom name space ``{=html}build123d``{=html}, name equal to the base file name and the type as ``{=html}python``{=html}
[[add_meta_data]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[name_space]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[value]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[metadata_type]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[must_preserve]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[bool]{.pre}]{.n}*[)]{.sig-paren}
: Add meta data to the models
Parameters[:]{.colon}
: - **name_space** (*str*) -- categorizer of different metadata entries
- **name** (*str*) -- metadata label
- **value** (*str*) -- metadata content
- **metadata_type** (*str*) -- metadata type
- **must_preserve** (*bool*) -- metadata must not be removed if unused
[[add_shape]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[shape:]{.pre} [\~build123d.topology.shape_core.Shape]{.pre} [\|]{.pre} [\~collections.abc.Iterable\[\~build123d.topology.shape_core.Shape\],]{.pre} [linear_deflection:]{.pre} [float]{.pre} [=]{.pre} [0.001,]{.pre} [angular_deflection:]{.pre} [float]{.pre} [=]{.pre} [0.1,]{.pre} [mesh_type:]{.pre} [\~build123d.build_enums.MeshType]{.pre} [=]{.pre} [\,]{.pre} [part_number:]{.pre} [str]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None,]{.pre} [uuid_value:]{.pre} [\~uuid.UUID]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}*[)]{.sig-paren}
: Add a shape to the 3MF/STL file.
Parameters[:]{.colon}
: - **shape** (*Union\[*[*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* *Iterable\[*[*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]\]*) -- build123d object
- **linear_deflection** (*float,* *optional*) -- mesh control for edges. Defaults to 0.001.
- **angular_deflection** (*float,* *optional*) -- mesh control for non-planar surfaces. Defaults to 0.1.
- **mesh_type** (*MeshType,* *optional*) -- 3D printing use of mesh. Defaults to MeshType.MODEL.
- **part_number** (*str,* *optional*) -- part #. Defaults to None.
- **uuid_value** (*uuid,* *optional*) -- value from uuid package. Defaults to None.
Raises[:]{.colon}
: - **RuntimeError** -- 3mf mesh is invalid
- **Warning** -- Degenerate shape skipped
- **Warning** -- 3mf mesh is not manifold
[[get_mesh_properties]{.pre}]{.sig-name .descname}[(]{.sig-paren}[)]{.sig-paren} [[→]{.sig-return-icon} [[list]{.pre}[[\[]{.pre}]{.p}[dict]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Retrieve the properties from all the meshes
[[get_meta_data]{.pre}]{.sig-name .descname}[(]{.sig-paren}[)]{.sig-paren} [[→]{.sig-return-icon} [[list]{.pre}[[\[]{.pre}]{.p}[dict]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: Retrieve all of the metadata
[[get_meta_data_by_key]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[name_space]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*, *[[name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[dict]{.pre}]{.sig-return-typehint}]{.sig-return}
: Retrieve the metadata value and type for the provided name space and name
*[property]{.pre}[ ]{.w}*[[library_version]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[str]{.pre}*
: 3MF Consortium Lib#MF version
*[property]{.pre}[ ]{.w}*[[mesh_count]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[int]{.pre}*
: Number of meshes in the model
*[property]{.pre}[ ]{.w}*[[model_unit]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[Unit]{.pre}*
: Unit used in the model
[[read]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[file_name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[list]{.pre}[[\[]{.pre}]{.p}[Shape]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
:
Parameters[:]{.colon}
: - **Union\[PathLike** (*file_name*) -- file path
- **str** -- file path
- **bytes\]** -- file path
Raises[:]{.colon}
: **ValueError** -- Unknown file format - must be 3mf or stl
Returns[:]{.colon}
: build123d shapes extracted from mesh file
Return type[:]{.colon}
: *list*\[[*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}\]
*[property]{.pre}[ ]{.w}*[[triangle_counts]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[list]{.pre}[[\[]{.pre}]{.p}[int]{.pre}[[\]]{.pre}]{.p}*
: Number of triangles in each of the model's meshes
*[property]{.pre}[ ]{.w}*[[vertex_counts]{.pre}]{.sig-name .descname}*[[:]{.pre}]{.p}[ ]{.w}[list]{.pre}[[\[]{.pre}]{.p}[int]{.pre}[[\]]{.pre}]{.p}*
: Number of vertices in each of the models's meshes
[[write]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[file_name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}]{.n}*[)]{.sig-paren}
:
Parameters[:]{.colon}
: - **Union\[Pathlike** (*file_name*) -- file path
- **str** -- file path
- **bytes\]** -- file path
Raises[:]{.colon}
: **ValueError** -- Unknown file format - must be 3mf or stl
[[write_stream]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[stream]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[BytesIO]{.pre}]{.n}*, *[[file_type]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[str]{.pre}]{.n}*[)]{.sig-paren}
:
::: {.admonition .note}
Note
If you need to align multiple components for 3D printing, you can use the [[pack()]{.std .std-ref}](#assemblies.xhtml#pack){.reference .internal} function to arrange the objects side by side and align them on the same plane. This ensures that your components are well-organized and ready for the printing process.
:::
:::
:::
::: {#import_export.xhtml#module-importers .section}
[]{#import_export.xhtml#d-importers}
## 2D Importers
[[import_svg]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[svg_file:]{.pre} [str]{.pre} [\|]{.pre} [\~pathlib.Path]{.pre} [\|]{.pre} [\~typing.TextIO]{.pre}]{.n}*, *[[\*]{.pre}]{.n}*, *[[flip_y:]{.pre} [bool]{.pre} [=]{.pre} [True]{.pre}]{.n}*, *[[align:]{.pre} [\~build123d.build_enums.Align]{.pre} [\|]{.pre} [tuple\[\~build123d.build_enums.Align]{.pre}]{.n}*, *[[\~build123d.build_enums.Align\]]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [\]{.pre}]{.n}*, *[[ignore_visibility:]{.pre} [bool]{.pre} [=]{.pre} [False]{.pre}]{.n}*, *[[label_by:]{.pre} [\~typing.Literal\[\'id\']{.pre}]{.n}*, *[[\'class\']{.pre}]{.n}*, *[[\'inkscape:label\'\]]{.pre} [\|]{.pre} [str]{.pre} [=]{.pre} [\'id\']{.pre}]{.n}*, *[[is_inkscape_label:]{.pre} [bool]{.pre} [\|]{.pre} [None]{.pre} [=]{.pre} [None]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[ShapeList]{.pre}[[\[]{.pre}]{.p}[Wire]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[Face]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
:
Parameters[:]{.colon}
: - **svg_file** (*Union\[str,* *Path,* *TextIO\]*) -- svg file
- **flip_y** (*bool,* *optional*) -- flip objects to compensate for svg orientation. Defaults to True.
- **align** ([*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal} *\|* *tuple\[*[*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*,* [*Align*](#builder_api_reference.xhtml#build_enums.Align "build_enums.Align"){.hxr-hoverxref .hxr-tooltip .reference .internal}*\]* *\|* *None,* *optional*) -- alignment of the SVG's viewbox, if None, the viewbox's origin will be at ``{=html}(0,0,0)``{=html}. Defaults to Align.MIN.
- **ignore_visibility** (*bool,* *optional*) -- Defaults to False.
- **label_by** (*str,* *optional*) -- XML attribute to use for imported shapes' ``{=html}label``{=html} property. Defaults to "id". Use ``{=html}inkscape:label``{=html} to read labels set from Inkscape's "Layers and Objects" panel.
Raises[:]{.colon}
: **ValueError** -- unexpected shape type
Returns[:]{.colon}
: objects contained in svg
Return type[:]{.colon}
: [*ShapeList*](#direct_api_reference.xhtml#topology.ShapeList "topology.ShapeList"){.hxr-hoverxref .hxr-tooltip .reference .internal}\[*Union*\[[*Wire*](#direct_api_reference.xhtml#topology.Wire "topology.Wire"){.hxr-hoverxref .hxr-tooltip .reference .internal}, [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}\]\]
```{=html}
```
[[import_svg_as_buildline_code]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[file_name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[tuple]{.pre}[[\[]{.pre}]{.p}[str]{.pre}[[,]{.pre}]{.p}[ ]{.w}[str]{.pre}[[\]]{.pre}]{.p}]{.sig-return-typehint}]{.sig-return}
: translate_to_buildline_code
Translate the contents of the given svg file into executable build123d/BuildLine code.
Parameters[:]{.colon}
: **file_name** (*Union\[PathLike,* *str,* *bytes\]*) -- svg file name
Returns[:]{.colon}
: code, builder instance name
Return type[:]{.colon}
: *tuple*\[*str*, *str*\]
:::
::: {#import_export.xhtml#id2 .section}
## 3D Importers
[[import_brep]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[file_name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Shape]{.pre}]{.sig-return-typehint}]{.sig-return}
: Import shape from a BREP file
Parameters[:]{.colon}
: **file_name** (*Union\[PathLike,* *str,* *bytes\]*) -- brep file
Raises[:]{.colon}
: **ValueError** -- file not found
Returns[:]{.colon}
: build123d object
Return type[:]{.colon}
: [*Shape*](#direct_api_reference.xhtml#topology.Shape "topology.Shape"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[import_step]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[filename]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Compound]{.pre}]{.sig-return-typehint}]{.sig-return}
: Extract shapes from a STEP file and return them as a Compound object.
Parameters[:]{.colon}
: **file_name** (*Union\[PathLike,* *str,* *bytes\]*) -- file path of STEP file to import
Raises[:]{.colon}
: **ValueError** -- can't open file
Returns[:]{.colon}
: contents of STEP file
Return type[:]{.colon}
: [*Compound*](#direct_api_reference.xhtml#topology.Compound "topology.Compound"){.hxr-hoverxref .hxr-tooltip .reference .internal}
```{=html}
```
[[import_stl]{.pre}]{.sig-name .descname}[(]{.sig-paren}*[[file_name]{.pre}]{.n}[[:]{.pre}]{.p}[ ]{.w}[[PathLike]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[str]{.pre}[ ]{.w}[[\|]{.pre}]{.p}[ ]{.w}[bytes]{.pre}]{.n}*[)]{.sig-paren} [[→]{.sig-return-icon} [[Face]{.pre}]{.sig-return-typehint}]{.sig-return}
: Extract shape from an STL file and return it as a Face reference object.
Note that importing with this method and creating a reference is very fast while creating an editable model (with Mesher) may take minutes depending on the size of the STL file.
Parameters[:]{.colon}
: **file_name** (*Union\[PathLike,* *str,* *bytes\]*) -- file path of STL file to import
Raises[:]{.colon}
: **ValueError** -- Could not import file
Returns[:]{.colon}
: STL model
Return type[:]{.colon}
: [*Face*](#direct_api_reference.xhtml#topology.Face "topology.Face"){.hxr-hoverxref .hxr-tooltip .reference .internal}
::: {#import_export.xhtml#d-mesh-import .section}
### 3D Mesh Import
Both 3MF and STL import (and export) are provided with the [`Mesher`{.xref .py .py-class .docutils .literal .notranslate}](#import_export.xhtml#mesher.Mesher "mesher.Mesher"){.hxr-hoverxref .hxr-tooltip .reference .internal} class.
For example:
::: {.highlight-build123d .notranslate}
::: highlight
importer = Mesher()
cone, cyl = importer.read("example.3mf")
print(
f"{importer.mesh_count=}, {importer.vertex_counts=}, {importer.triangle_counts=}"
)
print(f"Imported model unit: {importer.model_unit}")
print(f"{cone.label=}")
print(f"{cone.color.to_tuple()=}")
print(f"{cyl.label=}")
print(f"{cyl.color.to_tuple()=}")
:::
:::
::: {.highlight-none .notranslate}
::: highlight
importer.mesh_count=2, importer.vertex_counts=[66, 52], importer.triangle_counts=[128, 100]
Imported model unit: Unit.MM
cone.label='blue'
cone.color.to_tuple()=(0.0, 0.0, 1.0, 1.0)
cyl.label='red'
cyl.color.to_tuple()=(1.0, 0.0, 0.0, 1.0)
:::
:::
:::
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#advanced.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#advanced.xhtml#advanced-topics .section}
# Advanced Topics
::: {.toctree-wrapper .compound}
- [Performance considerations in algebra mode](#algebra_performance.xhtml){.reference .internal}
- [Location arithmetic for algebra mode](#location_arithmetic.xhtml){.reference .internal}
- [Position a shape relative to the XY plane](#location_arithmetic.xhtml#position-a-shape-relative-to-the-xy-plane){.reference .internal}
- [Relative positioning to a plane](#location_arithmetic.xhtml#relative-positioning-to-a-plane){.reference .internal}
- [Algebraic definition](#algebra_definition.xhtml){.reference .internal}
- [Objects and arithmetic](#algebra_definition.xhtml#objects-and-arithmetic){.reference .internal}
- [Locations, planes and location arithmetic](#algebra_definition.xhtml#locations-planes-and-location-arithmetic){.reference .internal}
- [CAD Object Centers](#center.xhtml){.reference .internal}
- [Debugging & Logging](#debugging_logging.xhtml){.reference .internal}
- [Python Debugger](#debugging_logging.xhtml#python-debugger){.reference .internal}
- [Logging](#debugging_logging.xhtml#logging){.reference .internal}
- [Printing](#debugging_logging.xhtml#printing){.reference .internal}
:::
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#algebra_performance.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#algebra_performance.xhtml#performance-considerations-in-algebra-mode .section}
[]{#algebra_performance.xhtml#algebra-performance}
# Performance considerations in algebra mode
Creating lots of Shapes in a loop means for every step `fuse`{.docutils .literal .notranslate} and `clean`{.docutils .literal .notranslate} will be called. In an example like the below, both functions get slower and slower the more objects are already fused. Overall it takes on an M1 Mac 4.76 sec.
::: {.highlight-build123d .notranslate}
::: highlight
diam = 80
holes = Sketch()
r = Rectangle(2, 2)
for loc in GridLocations(4, 4, 20, 20):
if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2:
holes += loc * r
c = Circle(diam / 2) - holes
:::
:::
One way to avoid it is to use lazy evaluation for the algebra operations. Just collect all objects and then call `fuse`{.docutils .literal .notranslate} (`+`{.docutils .literal .notranslate}) once with all objects and `clean`{.docutils .literal .notranslate} once. Overall it takes 0.19 sec.
::: {.highlight-build123d .notranslate}
::: highlight
r = Rectangle(2, 2)
holes = [
loc * r
for loc in GridLocations(4, 4, 20, 20).locations
if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2
]
c = Circle(diam / 2) - holes
:::
:::
Another way to leverage the vectorized algebra operations is to add a list comprehension of objects to an empty `Part`{.docutils .literal .notranslate}, `Sketch`{.docutils .literal .notranslate} or `Curve`{.docutils .literal .notranslate}:
::: {.highlight-build123d .notranslate}
::: highlight
polygons = Sketch() + [
loc * RegularPolygon(radius=5, side_count=5)
for loc in GridLocations(40, 30, 2, 2)
]
:::
:::
This again ensures one single `fuse`{.docutils .literal .notranslate} and `clean`{.docutils .literal .notranslate} call.
:::
::: clearer
:::
:::
:::
::: clearer
:::
:::
[]{#location_arithmetic.xhtml}
::: document
::: documentwrapper
::: {.body role="main"}
::: {#location_arithmetic.xhtml#location-arithmetic-for-algebra-mode .section}
[]{#location_arithmetic.xhtml#location-arithmetics}
# Location arithmetic for algebra mode
::: {#location_arithmetic.xhtml#position-a-shape-relative-to-the-xy-plane .section}
## Position a shape relative to the XY plane
For the following use the helper function:
::: {.highlight-build123d .notranslate}
::: highlight
def location_symbol(location: Location, scale: float = 1) -> Compound:
return Compound.make_triad(axes_scale=scale).locate(location)
def plane_symbol(plane: Plane, scale: float = 1) -> Compound:
triad = Compound.make_triad(axes_scale=scale)
circle = Circle(scale * .8).edge()
return (triad + circle).locate(plane.location)
:::
:::
1. **Positioning at a location**
>