Merge branch 'dev' into builder_context_selectors

This commit is contained in:
Roger Maitland 2023-11-15 14:12:30 -05:00 committed by GitHub
commit ef8ace0261
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 2169 additions and 479 deletions

View file

@ -1,7 +1,11 @@
[MAIN]
extension-pkg-allow-list=OCP
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,x,y,z,ex,Run,_,X,Y,Z,XY,YZ,ZX,XZ,YX,ZY
good-names=i,j,k,u,v,x,y,z,ex,Run,_,X,Y,Z,XY,YZ,ZX,XZ,YX,ZY
disable=
unsubscriptable-object, # False positives
@ -9,3 +13,6 @@ disable=
protected-access, # _variable to be hiddened from external users
too-many-arguments, # CAD is complex
too-few-public-methods # Objects and Operations will not have methods outside of _init
ignore-paths=
./src/build123d/_version.py # Generated

12
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,12 @@
When writing code for inclusion in build123d please add docs and
tests, ensure they build and pass, and ensure that `pylint` and `mypy`
are happy with your code.
- Install `pip` following their [documentation](https://pip.pypa.io/en/stable/installation/).
- Install development dependencies: `pip install pylint pytest mypy sphinx`
- Install docs dependencies: `pip install -r docs/requirements.txt` (might need to comment out the build123d line in that file)
- Install `build123d` in editable mode from current dir: `pip install -e .`
- Run tests with: `python -m pytest`
- Build docs with: `cd docs && make html`
- Check added files' style with: `pylint <path/to/file.py>`
- Check added files' type annotations with: `mypy <path/to/file.py>`

View file

@ -12,7 +12,7 @@ Objects and arithmetic
:math:`C^2` is the set of all ``Sketch`` objects ``s`` with ``s._dim = 2``
:math:`C^1` is the set of all ``Curve`` objects ``c`` with ``c._dim = 3``
:math:`C^1` is the set of all ``Curve`` objects ``c`` with ``c._dim = 1``
**Neutral elements:**
@ -49,7 +49,7 @@ with :math:`B^3 \subset C^3, B^2 \subset C^2` and :math:`B^1 \subset C^1`
:math:`\; a \; \& \; b :=` ``a.intersect(b)`` for each operation
* :math:`\&` is not defined for :math:`n=1` in build123d
* :math:`\&` is not defined for :math:`n=1` in build123d
* The following relationship holds: :math:`a \; \& \; b = (a + b) + -(a + (-b)) + -(b + (-a))`
@ -66,12 +66,12 @@ Locations, planes and location arithmentic
**Set definitions:**
:math:`L := \lbrace` ``Location((x, y, z), (a, b, c))`` :math:`: x,y,z \in R \land a,b,c \in R \rbrace\;`
:math:`L := \lbrace` ``Location((x, y, z), (a, b, c))`` :math:`: x,y,z \in R \land a,b,c \in R \rbrace\;`
with :math:`a,b,c` being angles in degrees.
:math:`P := \lbrace` ``Plane(o, x, z)`` :math:`: o,x,z ∈ R^3 \land \|x\| = \|z\| = 1\rbrace`
:math:`P := \lbrace` ``Plane(o, x, z)`` :math:`: o,x,z ∈ R^3 \land \|x\| = \|z\| = 1\rbrace`
with ``o`` being the origin and ``x``, ``z`` the x- and z-direction of the plane.
Neutral element: :math:`\; l_0 \in L`: ``Location()``
@ -79,7 +79,7 @@ Neutral element: :math:`\; l_0 \in L`: ``Location()``
**Operations:**
:math:`*: L \times L \rightarrow L` with :math:`(l_1,l_2) \mapsto l_1 * l_2`
:math:`\; l_1 * l_2 :=` ``l1 * l2`` (multiply two locations)
:math:`*: P \times L \rightarrow P` with :math:`(p,l) \mapsto p * l`

View file

@ -0,0 +1,113 @@
<?xml version='1.0' encoding='utf-8'?>
<svg width="100.09mm" height="98.074625mm" viewBox="-12.957705 -12.696793 25.91541 25.393587" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g transform="scale(1,-1)" stroke-linecap="round">
<g fill="none" stroke="rgb(0,0,0)" stroke-width="0.023302896428222534" id="Visible">
<line x1="5.41638" y1="-12.685142" x2="3.121076" y2="-11.694396" />
<line x1="-10.650749" y1="-5.749916" x2="-12.946054" y2="-4.75917" />
<line x1="3.121076" y1="-11.694396" x2="-10.650749" y2="-5.749916" />
<line x1="3.841048" y1="-12.549755" x2="4.039197" y2="-12.090694" />
<line x1="-11.76702" y1="-5.812679" x2="-11.568871" y2="-5.353618" />
<line x1="3.841048" y1="-12.549755" x2="-11.76702" y2="-5.812679" />
<line x1="5.41638" y1="-12.685142" x2="8.983067" y2="-4.422047" />
<line x1="8.983067" y1="-4.422047" x2="9.379366" y2="-3.503925" />
<line x1="9.379366" y1="-3.503925" x2="12.946054" y2="4.75917" />
<line x1="-12.946054" y1="-4.75917" x2="-9.379366" y2="3.503925" />
<line x1="-9.379366" y1="3.503925" x2="-8.983067" y2="4.422047" />
<line x1="-8.983067" y1="4.422047" x2="-5.41638" y2="12.685142" />
<line x1="-10.650749" y1="-5.749916" x2="-9.858152" y2="-3.913673" />
<line x1="8.002184" y1="-2.909477" x2="10.776274" y2="3.517374" />
<line x1="10.776274" y1="3.517374" x2="9.858152" y2="3.913673" />
<line x1="9.858152" y1="3.913673" x2="10.650749" y2="5.749916" />
<line x1="10.650749" y1="5.749916" x2="-3.121076" y2="11.694396" />
<line x1="-3.913673" y1="9.858152" x2="-3.121076" y2="11.694396" />
<line x1="-3.913673" y1="9.858152" x2="-4.831795" y2="10.254451" />
<line x1="-7.605885" y1="3.827599" x2="-4.831795" y2="10.254451" />
<line x1="-7.605885" y1="3.827599" x2="-8.983067" y2="4.422047" />
<line x1="-8.002184" y1="2.909477" x2="-9.379366" y2="3.503925" />
<line x1="-10.776274" y1="-3.517374" x2="-8.002184" y2="2.909477" />
<line x1="-9.858152" y1="-3.913673" x2="-10.776274" y2="-3.517374" />
<line x1="3.121076" y1="-11.694396" x2="3.913673" y2="-9.858152" />
<line x1="4.831795" y1="-10.254451" x2="3.913673" y2="-9.858152" />
<line x1="4.831795" y1="-10.254451" x2="7.605885" y2="-3.827599" />
<line x1="8.983067" y1="-4.422047" x2="7.605885" y2="-3.827599" />
<line x1="9.379366" y1="-3.503925" x2="8.002184" y2="-2.909477" />
<line x1="12.946054" y1="4.75917" x2="10.650749" y2="5.749916" />
<line x1="-3.121076" y1="11.694396" x2="-5.41638" y2="12.685142" />
<line x1="-4.039197" y1="12.090694" x2="-3.841048" y2="12.549755" />
<line x1="11.568871" y1="5.353618" x2="11.76702" y2="5.812679" />
<line x1="11.76702" y1="5.812679" x2="-3.841048" y2="12.549755" />
</g>
<g fill="none" stroke="rgb(99,99,99)" stroke-width="0.023302896428222534" id="Hidden" stroke-dasharray="0.000592 0.070205">
<line x1="4.039197" y1="-12.090694" x2="-11.568871" y2="-5.353618" />
<line x1="-11.568871" y1="-5.353618" x2="4.039197" y2="-12.090694" />
<line x1="5.41638" y1="-12.685142" x2="3.121076" y2="-11.694396" />
<line x1="3.121076" y1="-11.694396" x2="-10.650749" y2="-5.749916" />
<line x1="-10.650749" y1="-5.749916" x2="-12.946054" y2="-4.75917" />
<line x1="-11.76702" y1="-5.812679" x2="-11.568871" y2="-5.353618" />
<line x1="3.841048" y1="-12.549755" x2="-11.76702" y2="-5.812679" />
<line x1="3.841048" y1="-12.549755" x2="4.039197" y2="-12.090694" />
<line x1="3.913673" y1="-9.858152" x2="3.121076" y2="-11.694396" />
<line x1="4.831795" y1="-10.254451" x2="3.913673" y2="-9.858152" />
<line x1="4.831795" y1="-10.254451" x2="7.605885" y2="-3.827599" />
<line x1="7.605885" y1="-3.827599" x2="8.983067" y2="-4.422047" />
<line x1="5.41638" y1="-12.685142" x2="8.983067" y2="-4.422047" />
<line x1="9.379366" y1="-3.503925" x2="12.946054" y2="4.75917" />
<line x1="8.983067" y1="-4.422047" x2="9.379366" y2="-3.503925" />
<line x1="3.913673" y1="-9.858152" x2="4.831795" y2="-10.254451" />
<line x1="4.831795" y1="-10.254451" x2="7.605885" y2="-3.827599" />
<line x1="7.605885" y1="-3.827599" x2="8.983067" y2="-4.422047" />
<line x1="3.913673" y1="-9.858152" x2="3.121076" y2="-11.694396" />
<line x1="3.121076" y1="-11.694396" x2="3.913673" y2="-9.858152" />
<line x1="-10.650749" y1="-5.749916" x2="-9.858152" y2="-3.913673" />
<line x1="4.831795" y1="-10.254451" x2="3.913673" y2="-9.858152" />
<line x1="4.831795" y1="-10.254451" x2="7.605885" y2="-3.827599" />
<line x1="8.983067" y1="-4.422047" x2="7.605885" y2="-3.827599" />
<line x1="9.379366" y1="-3.503925" x2="8.002184" y2="-2.909477" />
<line x1="8.002184" y1="-2.909477" x2="10.776274" y2="3.517374" />
<line x1="10.776274" y1="3.517374" x2="9.858152" y2="3.913673" />
<line x1="9.858152" y1="3.913673" x2="10.650749" y2="5.749916" />
<line x1="10.650749" y1="5.749916" x2="-3.121076" y2="11.694396" />
<line x1="-3.913673" y1="9.858152" x2="-3.121076" y2="11.694396" />
<line x1="-3.913673" y1="9.858152" x2="-4.831795" y2="10.254451" />
<line x1="-7.605885" y1="3.827599" x2="-4.831795" y2="10.254451" />
<line x1="-7.605885" y1="3.827599" x2="-8.983067" y2="4.422047" />
<line x1="-9.379366" y1="3.503925" x2="-8.983067" y2="4.422047" />
<line x1="-8.002184" y1="2.909477" x2="-9.379366" y2="3.503925" />
<line x1="-10.776274" y1="-3.517374" x2="-8.002184" y2="2.909477" />
<line x1="-9.858152" y1="-3.913673" x2="-10.776274" y2="-3.517374" />
<line x1="-9.858152" y1="-3.913673" x2="-10.650749" y2="-5.749916" />
<line x1="-12.946054" y1="-4.75917" x2="-9.379366" y2="3.503925" />
<line x1="-9.379366" y1="3.503925" x2="-8.002184" y2="2.909477" />
<line x1="-10.776274" y1="-3.517374" x2="-8.002184" y2="2.909477" />
<line x1="-10.776274" y1="-3.517374" x2="-9.858152" y2="-3.913673" />
<line x1="-8.983067" y1="4.422047" x2="-5.41638" y2="12.685142" />
<line x1="-9.379366" y1="3.503925" x2="-8.002184" y2="2.909477" />
<line x1="-10.776274" y1="-3.517374" x2="-8.002184" y2="2.909477" />
<line x1="-9.858152" y1="-3.913673" x2="-10.776274" y2="-3.517374" />
<line x1="-9.858152" y1="-3.913673" x2="-10.650749" y2="-5.749916" />
<line x1="8.002184" y1="-2.909477" x2="9.379366" y2="-3.503925" />
<line x1="8.002184" y1="-2.909477" x2="10.776274" y2="3.517374" />
<line x1="9.858152" y1="3.913673" x2="10.776274" y2="3.517374" />
<line x1="10.650749" y1="5.749916" x2="9.858152" y2="3.913673" />
<line x1="12.946054" y1="4.75917" x2="10.650749" y2="5.749916" />
<line x1="-3.121076" y1="11.694396" x2="-5.41638" y2="12.685142" />
<line x1="11.568871" y1="5.353618" x2="-4.039197" y2="12.090694" />
<line x1="-4.039197" y1="12.090694" x2="11.568871" y2="5.353618" />
<line x1="8.002184" y1="-2.909477" x2="9.379366" y2="-3.503925" />
<line x1="8.002184" y1="-2.909477" x2="10.776274" y2="3.517374" />
<line x1="9.858152" y1="3.913673" x2="10.776274" y2="3.517374" />
<line x1="10.650749" y1="5.749916" x2="9.858152" y2="3.913673" />
<line x1="-3.121076" y1="11.694396" x2="-3.913673" y2="9.858152" />
<line x1="-4.831795" y1="10.254451" x2="-3.913673" y2="9.858152" />
<line x1="-7.605885" y1="3.827599" x2="-4.831795" y2="10.254451" />
<line x1="-8.983067" y1="4.422047" x2="-7.605885" y2="3.827599" />
<line x1="-8.983067" y1="4.422047" x2="-7.605885" y2="3.827599" />
<line x1="-3.121076" y1="11.694396" x2="-3.913673" y2="9.858152" />
<line x1="-4.831795" y1="10.254451" x2="-3.913673" y2="9.858152" />
<line x1="-7.605885" y1="3.827599" x2="-4.831795" y2="10.254451" />
<line x1="-4.039197" y1="12.090694" x2="-3.841048" y2="12.549755" />
<line x1="11.568871" y1="5.353618" x2="11.76702" y2="5.812679" />
<line x1="11.76702" y1="5.812679" x2="-3.841048" y2="12.549755" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -0,0 +1,609 @@
<?xml version='1.0' encoding='utf-8'?>
<svg width="100.09mm" height="94.828286mm" viewBox="-75.158411 -79.079813 143.393259 135.8551" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g transform="scale(1,-1)" stroke-linecap="round">
<g fill="none" stroke="rgb(0,0,0)" stroke-width="0.12893788891198896" id="Visible">
<line x1="-5.693759" y1="67.888004" x2="-5.693759" y2="68.634199" />
<line x1="-5.693759" y1="68.634199" x2="-6.509998" y2="69.170199" />
<line x1="-6.509998" y1="68.424004" x2="-6.509998" y2="69.170199" />
<line x1="-5.693759" y1="67.888004" x2="-6.509998" y2="68.424004" />
<line x1="0.083391" y1="75.461001" x2="0.083391" y2="76.207196" />
<line x1="0.083391" y1="76.207196" x2="-0.732847" y2="76.743196" />
<line x1="-5.693759" y1="67.888004" x2="0.083391" y2="75.461001" />
<line x1="-5.693759" y1="68.634199" x2="0.083391" y2="76.207196" />
<line x1="-6.509998" y1="69.170199" x2="-0.732847" y2="76.743196" />
<line x1="43.105509" y1="48.532875" x2="43.105509" y2="48.905973" />
<line x1="43.105509" y1="48.905973" x2="40.656793" y2="50.513971" />
<line x1="40.656793" y1="50.140874" x2="40.656793" y2="50.513971" />
<line x1="43.105509" y1="48.532875" x2="40.656793" y2="50.140874" />
<line x1="43.683224" y1="49.290175" x2="43.683224" y2="49.663272" />
<line x1="43.683224" y1="49.663272" x2="41.234508" y2="51.271271" />
<line x1="43.105509" y1="48.532875" x2="43.683224" y2="49.290175" />
<line x1="43.105509" y1="48.905973" x2="43.683224" y2="49.663272" />
<line x1="40.656793" y1="50.513971" x2="41.234508" y2="51.271271" />
<line x1="-6.562586" y1="29.998376" x2="-6.562586" y2="30.371473" />
<line x1="-6.562586" y1="30.371473" x2="-7.378825" y2="30.907473" />
<line x1="-7.378825" y1="30.534375" x2="-7.378825" y2="30.907473" />
<line x1="-6.562586" y1="29.998376" x2="-7.378825" y2="30.534375" />
<line x1="-1.363151" y1="36.814073" x2="-1.363151" y2="37.18717" />
<line x1="-1.363151" y1="37.18717" x2="-2.179389" y2="37.72317" />
<line x1="-6.562586" y1="29.998376" x2="-1.363151" y2="36.814073" />
<line x1="-6.562586" y1="30.371473" x2="-1.363151" y2="37.18717" />
<line x1="-7.378825" y1="30.907473" x2="-2.179389" y2="37.72317" />
<line x1="8.945945" y1="19.627836" x2="8.945945" y2="20.374031" />
<line x1="8.945945" y1="20.374031" x2="-4.113871" y2="28.950023" />
<line x1="-4.113871" y1="28.203828" x2="-4.113871" y2="28.950023" />
<line x1="8.945945" y1="19.627836" x2="-4.113871" y2="28.203828" />
<line x1="13.567666" y1="25.686234" x2="13.567666" y2="26.432428" />
<line x1="13.567666" y1="26.432428" x2="0.50785" y2="35.008421" />
<line x1="8.945945" y1="19.627836" x2="13.567666" y2="25.686234" />
<line x1="8.945945" y1="20.374031" x2="13.567666" y2="26.432428" />
<line x1="-4.113871" y1="28.950023" x2="0.50785" y2="35.008421" />
<line x1="28.737006" y1="13.451644" x2="28.737006" y2="14.197839" />
<line x1="28.737006" y1="14.197839" x2="14.860951" y2="23.309831" />
<line x1="14.860951" y1="22.563636" x2="14.860951" y2="23.309831" />
<line x1="28.737006" y1="13.451644" x2="14.860951" y2="22.563636" />
<line x1="29.314721" y1="14.208944" x2="29.314721" y2="14.955139" />
<line x1="29.314721" y1="14.955139" x2="15.438666" y2="24.06713" />
<line x1="28.737006" y1="13.451644" x2="29.314721" y2="14.208944" />
<line x1="28.737006" y1="14.197839" x2="29.314721" y2="14.955139" />
<line x1="14.860951" y1="23.309831" x2="15.438666" y2="24.06713" />
<line x1="24.242248" y1="30.229782" x2="24.242248" y2="30.602879" />
<line x1="24.242248" y1="30.602879" x2="14.447386" y2="37.034874" />
<line x1="14.447386" y1="36.661776" x2="14.447386" y2="37.034874" />
<line x1="24.242248" y1="30.229782" x2="14.447386" y2="36.661776" />
<line x1="31.752544" y1="40.074678" x2="31.752544" y2="40.447775" />
<line x1="31.752544" y1="40.447775" x2="21.957682" y2="46.879769" />
<line x1="24.242248" y1="30.229782" x2="31.752544" y2="40.074678" />
<line x1="24.242248" y1="30.602879" x2="31.752544" y2="40.447775" />
<line x1="14.447386" y1="37.034874" x2="21.957682" y2="46.879769" />
<line x1="11.908895" y1="7.079047" x2="11.908895" y2="8.571436" />
<line x1="11.908895" y1="8.571436" x2="10.276418" y2="9.643436" />
<line x1="10.276418" y1="8.151046" x2="10.276418" y2="9.643436" />
<line x1="11.908895" y1="7.079047" x2="10.276418" y2="8.151046" />
<line x1="15.375185" y1="11.622845" x2="15.375185" y2="13.115235" />
<line x1="15.375185" y1="13.115235" x2="13.742708" y2="14.187234" />
<line x1="11.908895" y1="7.079047" x2="15.375185" y2="11.622845" />
<line x1="11.908895" y1="8.571436" x2="15.375185" y2="13.115235" />
<line x1="10.276418" y1="9.643436" x2="13.742708" y2="14.187234" />
<line x1="-16.134326" y1="-12.015848" x2="-16.134326" y2="-10.523458" />
<line x1="-16.134326" y1="-10.523458" x2="-28.377904" y2="-2.483465" />
<line x1="-28.377904" y1="-3.975855" x2="-28.377904" y2="-2.483465" />
<line x1="-16.134326" y1="-12.015848" x2="-28.377904" y2="-3.975855" />
<line x1="-6.31317" y1="0.858246" x2="-6.31317" y2="2.350636" />
<line x1="-6.31317" y1="2.350636" x2="-18.556748" y2="10.390629" />
<line x1="-16.134326" y1="-12.015848" x2="-6.31317" y2="0.858246" />
<line x1="-16.134326" y1="-10.523458" x2="-6.31317" y2="2.350636" />
<line x1="-28.377904" y1="-2.483465" x2="-18.556748" y2="10.390629" />
<line x1="-39.868724" y1="-7.983404" x2="-39.868724" y2="-6.117917" />
<line x1="-39.868724" y1="-6.117917" x2="-44.766155" y2="-2.90192" />
<line x1="-44.766155" y1="-4.767407" x2="-44.766155" y2="-2.90192" />
<line x1="-39.868724" y1="-7.983404" x2="-44.766155" y2="-4.767407" />
<line x1="-30.047568" y1="4.89069" x2="-30.047568" y2="6.756178" />
<line x1="-30.047568" y1="6.756178" x2="-34.944999" y2="9.972175" />
<line x1="-39.868724" y1="-7.983404" x2="-30.047568" y2="4.89069" />
<line x1="-39.868724" y1="-6.117917" x2="-30.047568" y2="6.756178" />
<line x1="-44.766155" y1="-2.90192" x2="-34.944999" y2="9.972175" />
<line x1="-6.063754" y1="-27.16259" x2="-6.063754" y2="-26.789493" />
<line x1="-6.063754" y1="-26.789493" x2="-7.696231" y2="-25.717494" />
<line x1="-7.696231" y1="-26.090591" x2="-7.696231" y2="-25.717494" />
<line x1="-6.063754" y1="-27.16259" x2="-7.696231" y2="-26.090591" />
<line x1="4.335117" y1="-13.531196" x2="4.335117" y2="-13.158099" />
<line x1="4.335117" y1="-13.158099" x2="2.70264" y2="-12.0861" />
<line x1="-6.063754" y1="-27.16259" x2="4.335117" y2="-13.531196" />
<line x1="-6.063754" y1="-26.789493" x2="4.335117" y2="-13.158099" />
<line x1="-7.696231" y1="-25.717494" x2="2.70264" y2="-12.0861" />
<line x1="15.300811" y1="27.398486" x2="15.300811" y2="29.263974" />
<line x1="15.300811" y1="29.263974" x2="12.035857" y2="31.407972" />
<line x1="12.035857" y1="29.542484" x2="12.035857" y2="31.407972" />
<line x1="15.300811" y1="27.398486" x2="12.035857" y2="29.542484" />
<line x1="17.033956" y1="29.670385" x2="17.033956" y2="31.535873" />
<line x1="17.033956" y1="31.535873" x2="13.769002" y2="33.679871" />
<line x1="15.300811" y1="27.398486" x2="17.033956" y2="29.670385" />
<line x1="15.300811" y1="29.263974" x2="17.033956" y2="31.535873" />
<line x1="12.035857" y1="31.407972" x2="13.769002" y2="33.679871" />
<line x1="7.827702" y1="10.132142" x2="7.827702" y2="10.878337" />
<line x1="7.827702" y1="10.878337" x2="5.378987" y2="12.486335" />
<line x1="5.378987" y1="11.74014" x2="5.378987" y2="12.486335" />
<line x1="7.827702" y1="10.132142" x2="5.378987" y2="11.74014" />
<line x1="9.560847" y1="12.404041" x2="9.560847" y2="13.150236" />
<line x1="9.560847" y1="13.150236" x2="7.112132" y2="14.758234" />
<line x1="7.827702" y1="10.132142" x2="9.560847" y2="12.404041" />
<line x1="7.827702" y1="10.878337" x2="9.560847" y2="13.150236" />
<line x1="5.378987" y1="12.486335" x2="7.112132" y2="14.758234" />
<line x1="28.308039" y1="-19.416585" x2="28.308039" y2="-18.297293" />
<line x1="28.308039" y1="-18.297293" x2="12.799507" y2="-8.113302" />
<line x1="12.799507" y1="-9.232595" x2="12.799507" y2="-8.113302" />
<line x1="28.308039" y1="-19.416585" x2="12.799507" y2="-9.232595" />
<line x1="34.662904" y1="-11.086289" x2="34.662904" y2="-9.966997" />
<line x1="34.662904" y1="-9.966997" x2="19.154373" y2="0.216994" />
<line x1="28.308039" y1="-19.416585" x2="34.662904" y2="-11.086289" />
<line x1="28.308039" y1="-18.297293" x2="34.662904" y2="-9.966997" />
<line x1="12.799507" y1="-8.113302" x2="19.154373" y2="0.216994" />
<line x1="41.595485" y1="-2.37179" x2="41.595485" y2="-0.506303" />
<line x1="41.595485" y1="-0.506303" x2="27.719431" y2="8.605689" />
<line x1="27.719431" y1="6.740201" x2="27.719431" y2="8.605689" />
<line x1="41.595485" y1="-2.37179" x2="27.719431" y2="6.740201" />
<line x1="50.261211" y1="8.987705" x2="50.261211" y2="10.853192" />
<line x1="50.261211" y1="10.853192" x2="36.385157" y2="19.965184" />
<line x1="41.595485" y1="-2.37179" x2="50.261211" y2="8.987705" />
<line x1="41.595485" y1="-0.506303" x2="50.261211" y2="10.853192" />
<line x1="27.719431" y1="8.605689" x2="36.385157" y2="19.965184" />
<line x1="48.99422" y1="31.789489" x2="48.99422" y2="32.908781" />
<line x1="48.99422" y1="32.908781" x2="39.199358" y2="39.340775" />
<line x1="39.199358" y1="38.221483" x2="39.199358" y2="39.340775" />
<line x1="48.99422" y1="31.789489" x2="39.199358" y2="38.221483" />
<line x1="55.349086" y1="40.119785" x2="55.349086" y2="41.239077" />
<line x1="55.349086" y1="41.239077" x2="45.554224" y2="47.671071" />
<line x1="48.99422" y1="31.789489" x2="55.349086" y2="40.119785" />
<line x1="48.99422" y1="32.908781" x2="55.349086" y2="41.239077" />
<line x1="39.199358" y1="39.340775" x2="45.554224" y2="47.671071" />
<line x1="-4.580025" y1="3.316694" x2="-4.580025" y2="4.435987" />
<line x1="-4.580025" y1="4.435987" x2="-7.844979" y2="6.579985" />
<line x1="-7.844979" y1="5.460692" x2="-7.844979" y2="6.579985" />
<line x1="-4.580025" y1="3.316694" x2="-7.844979" y2="5.460692" />
<line x1="0.041696" y1="9.375092" x2="0.041696" y2="10.494384" />
<line x1="0.041696" y1="10.494384" x2="-3.223258" y2="12.638382" />
<line x1="-4.580025" y1="3.316694" x2="0.041696" y2="9.375092" />
<line x1="-4.580025" y1="4.435987" x2="0.041696" y2="10.494384" />
<line x1="-7.844979" y1="6.579985" x2="-3.223258" y2="12.638382" />
<line x1="-8.958713" y1="70.218551" x2="-8.958713" y2="70.591649" />
<line x1="-8.958713" y1="70.591649" x2="-11.407429" y2="72.199647" />
<line x1="-11.407429" y1="71.82655" x2="-11.407429" y2="72.199647" />
<line x1="-8.958713" y1="70.218551" x2="-11.407429" y2="71.82655" />
<line x1="-3.759278" y1="77.034248" x2="-3.759278" y2="77.407346" />
<line x1="-3.759278" y1="77.407346" x2="-6.207993" y2="79.015344" />
<line x1="-8.958713" y1="70.218551" x2="-3.759278" y2="77.034248" />
<line x1="-8.958713" y1="70.591649" x2="-3.759278" y2="77.407346" />
<line x1="-11.407429" y1="72.199647" x2="-6.207993" y2="79.015344" />
<line x1="10.350792" y1="-7.811145" x2="10.350792" y2="-6.318755" />
<line x1="10.350792" y1="-6.318755" x2="-1.892786" y2="1.721238" />
<line x1="-1.892786" y1="0.228848" x2="-1.892786" y2="1.721238" />
<line x1="10.350792" y1="-7.811145" x2="-1.892786" y2="0.228848" />
<line x1="14.394797" y1="-2.510047" x2="14.394797" y2="-1.017657" />
<line x1="14.394797" y1="-1.017657" x2="2.15122" y2="7.022335" />
<line x1="10.350792" y1="-7.811145" x2="14.394797" y2="-2.510047" />
<line x1="10.350792" y1="-6.318755" x2="14.394797" y2="-1.017657" />
<line x1="-1.892786" y1="1.721238" x2="2.15122" y2="7.022335" />
<line x1="-2.391618" y1="57.389814" x2="-2.391618" y2="58.882204" />
<line x1="-2.391618" y1="58.882204" x2="-3.207857" y2="59.418203" />
<line x1="-3.207857" y1="57.925813" x2="-3.207857" y2="59.418203" />
<line x1="-2.391618" y1="57.389814" x2="-3.207857" y2="57.925813" />
<line x1="-1.236188" y1="58.904413" x2="-1.236188" y2="60.396803" />
<line x1="-1.236188" y1="60.396803" x2="-2.052427" y2="60.932802" />
<line x1="-2.391618" y1="57.389814" x2="-1.236188" y2="58.904413" />
<line x1="-2.391618" y1="58.882204" x2="-1.236188" y2="60.396803" />
<line x1="-3.207857" y1="59.418203" x2="-2.052427" y2="60.932802" />
<line x1="38.118302" y1="21.11779" x2="38.118302" y2="21.490888" />
<line x1="38.118302" y1="21.490888" x2="26.690963" y2="28.994881" />
<line x1="26.690963" y1="28.621783" x2="26.690963" y2="28.994881" />
<line x1="38.118302" y1="21.11779" x2="26.690963" y2="28.621783" />
<line x1="40.429162" y1="24.146989" x2="40.429162" y2="24.520086" />
<line x1="40.429162" y1="24.520086" x2="29.001823" y2="32.02408" />
<line x1="38.118302" y1="21.11779" x2="40.429162" y2="24.146989" />
<line x1="38.118302" y1="21.490888" x2="40.429162" y2="24.520086" />
<line x1="26.690963" y1="28.994881" x2="29.001823" y2="32.02408" />
<line x1="2.203808" y1="44.51522" x2="2.203808" y2="45.261415" />
<line x1="2.203808" y1="45.261415" x2="-8.407292" y2="52.229409" />
<line x1="-8.407292" y1="51.483214" x2="-8.407292" y2="52.229409" />
<line x1="2.203808" y1="44.51522" x2="-8.407292" y2="51.483214" />
<line x1="5.670099" y1="49.059018" x2="5.670099" y2="49.805213" />
<line x1="5.670099" y1="49.805213" x2="-4.941002" y2="56.773207" />
<line x1="2.203808" y1="44.51522" x2="5.670099" y2="49.059018" />
<line x1="2.203808" y1="45.261415" x2="5.670099" y2="49.805213" />
<line x1="-8.407292" y1="52.229409" x2="-4.941002" y2="56.773207" />
<line x1="25.997178" y1="-22.818882" x2="25.997178" y2="-20.953394" />
<line x1="25.997178" y1="-20.953394" x2="9.672408" y2="-10.233404" />
<line x1="9.672408" y1="-12.098891" x2="9.672408" y2="-10.233404" />
<line x1="25.997178" y1="-22.818882" x2="9.672408" y2="-12.098891" />
<line x1="26.574893" y1="-22.061582" x2="26.574893" y2="-20.196094" />
<line x1="26.574893" y1="-20.196094" x2="10.250123" y2="-9.476104" />
<line x1="25.997178" y1="-22.818882" x2="26.574893" y2="-22.061582" />
<line x1="25.997178" y1="-20.953394" x2="26.574893" y2="-20.196094" />
<line x1="9.672408" y1="-10.233404" x2="10.250123" y2="-9.476104" />
<line x1="-28.314423" y1="7.162589" x2="-28.314423" y2="9.028077" />
<line x1="-28.314423" y1="9.028077" x2="-31.579377" y2="11.172075" />
<line x1="-31.579377" y1="9.306587" x2="-31.579377" y2="11.172075" />
<line x1="-28.314423" y1="7.162589" x2="-31.579377" y2="9.306587" />
<line x1="-20.226412" y1="17.764785" x2="-20.226412" y2="19.630272" />
<line x1="-20.226412" y1="19.630272" x2="-23.491366" y2="21.77427" />
<line x1="-28.314423" y1="7.162589" x2="-20.226412" y2="17.764785" />
<line x1="-28.314423" y1="9.028077" x2="-20.226412" y2="19.630272" />
<line x1="-31.579377" y1="11.172075" x2="-23.491366" y2="21.77427" />
<line x1="-10.144946" y1="-25.228788" x2="-10.144946" y2="-23.3633" />
<line x1="-10.144946" y1="-23.3633" x2="-16.674854" y2="-19.075304" />
<line x1="-16.674854" y1="-20.940792" x2="-16.674854" y2="-19.075304" />
<line x1="-10.144946" y1="-25.228788" x2="-16.674854" y2="-20.940792" />
<line x1="-0.32379" y1="-12.354693" x2="-0.32379" y2="-10.489206" />
<line x1="-0.32379" y1="-10.489206" x2="-6.853698" y2="-6.20121" />
<line x1="-10.144946" y1="-25.228788" x2="-0.32379" y2="-12.354693" />
<line x1="-10.144946" y1="-23.3633" x2="-0.32379" y2="-10.489206" />
<line x1="-16.674854" y1="-19.075304" x2="-6.853698" y2="-6.20121" />
<line x1="12.709732" y1="-39.490579" x2="12.709732" y2="-39.117482" />
<line x1="12.709732" y1="-39.117482" x2="-1.166323" y2="-30.00549" />
<line x1="-1.166323" y1="-30.378587" x2="-1.166323" y2="-30.00549" />
<line x1="12.709732" y1="-39.490579" x2="-1.166323" y2="-30.378587" />
<line x1="24.264033" y1="-24.344586" x2="24.264033" y2="-23.971488" />
<line x1="24.264033" y1="-23.971488" x2="10.387979" y2="-14.859496" />
<line x1="12.709732" y1="-39.490579" x2="24.264033" y2="-24.344586" />
<line x1="12.709732" y1="-39.117482" x2="24.264033" y2="-23.971488" />
<line x1="-1.166323" y1="-30.00549" x2="10.387979" y2="-14.859496" />
<line x1="11.99867" y1="37.52358" x2="11.99867" y2="39.389067" />
<line x1="11.99867" y1="39.389067" x2="4.652524" y2="44.213063" />
<line x1="4.652524" y1="42.347575" x2="4.652524" y2="44.213063" />
<line x1="11.99867" y1="37.52358" x2="4.652524" y2="42.347575" />
<line x1="19.508966" y1="47.368475" x2="19.508966" y2="49.233963" />
<line x1="19.508966" y1="49.233963" x2="12.16282" y2="54.057958" />
<line x1="11.99867" y1="37.52358" x2="19.508966" y2="47.368475" />
<line x1="11.99867" y1="39.389067" x2="19.508966" y2="49.233963" />
<line x1="4.652524" y1="44.213063" x2="12.16282" y2="54.057958" />
<line x1="36.750643" y1="39.642932" x2="36.750643" y2="41.135322" />
<line x1="36.750643" y1="41.135322" x2="26.955781" y2="47.567316" />
<line x1="26.955781" y1="46.074927" x2="26.955781" y2="47.567316" />
<line x1="36.750643" y1="39.642932" x2="26.955781" y2="46.074927" />
<line x1="37.906073" y1="41.157532" x2="37.906073" y2="42.649922" />
<line x1="37.906073" y1="42.649922" x2="28.111211" y2="49.081916" />
<line x1="36.750643" y1="39.642932" x2="37.906073" y2="41.157532" />
<line x1="36.750643" y1="41.135322" x2="37.906073" y2="42.649922" />
<line x1="26.955781" y1="47.567316" x2="28.111211" y2="49.081916" />
<line x1="3.873472" y1="34.902479" x2="3.873472" y2="36.767967" />
<line x1="3.873472" y1="36.767967" x2="2.240995" y2="37.839966" />
<line x1="2.240995" y1="35.974478" x2="2.240995" y2="37.839966" />
<line x1="3.873472" y1="34.902479" x2="2.240995" y2="35.974478" />
<line x1="6.184332" y1="37.931678" x2="6.184332" y2="39.797166" />
<line x1="6.184332" y1="39.797166" x2="4.551855" y2="40.869165" />
<line x1="3.873472" y1="34.902479" x2="6.184332" y2="37.931678" />
<line x1="3.873472" y1="36.767967" x2="6.184332" y2="39.797166" />
<line x1="2.240995" y1="37.839966" x2="4.551855" y2="40.869165" />
<line x1="-10.293694" y1="7.255239" x2="-10.293694" y2="8.001434" />
<line x1="-10.293694" y1="8.001434" x2="-11.926171" y2="9.073433" />
<line x1="-11.926171" y1="8.327238" x2="-11.926171" y2="9.073433" />
<line x1="-10.293694" y1="7.255239" x2="-11.926171" y2="8.327238" />
<line x1="-5.671974" y1="13.313637" x2="-5.671974" y2="14.059832" />
<line x1="-5.671974" y1="14.059832" x2="-7.304451" y2="15.131831" />
<line x1="-10.293694" y1="7.255239" x2="-5.671974" y2="13.313637" />
<line x1="-10.293694" y1="8.001434" x2="-5.671974" y2="14.059832" />
<line x1="-11.926171" y1="9.073433" x2="-7.304451" y2="15.131831" />
<line x1="36.39605" y1="-8.627841" x2="36.39605" y2="-7.881646" />
<line x1="36.39605" y1="-7.881646" x2="21.703757" y2="1.766345" />
<line x1="21.703757" y1="1.02015" x2="21.703757" y2="1.766345" />
<line x1="36.39605" y1="-8.627841" x2="21.703757" y2="1.02015" />
<line x1="39.86234" y1="-4.084043" x2="39.86234" y2="-3.337848" />
<line x1="39.86234" y1="-3.337848" x2="25.170047" y2="6.310143" />
<line x1="36.39605" y1="-8.627841" x2="39.86234" y2="-4.084043" />
<line x1="36.39605" y1="-7.881646" x2="39.86234" y2="-3.337848" />
<line x1="21.703757" y1="1.766345" x2="25.170047" y2="6.310143" />
<line x1="61.237798" y1="23.936045" x2="61.237798" y2="24.68224" />
<line x1="61.237798" y1="24.68224" x2="51.442936" y2="31.114234" />
<line x1="51.442936" y1="30.368039" x2="51.442936" y2="31.114234" />
<line x1="61.237798" y1="23.936045" x2="51.442936" y2="30.368039" />
<line x1="68.170379" y1="33.023641" x2="68.170379" y2="33.769836" />
<line x1="68.170379" y1="33.769836" x2="58.375517" y2="40.20183" />
<line x1="61.237798" y1="23.936045" x2="68.170379" y2="33.023641" />
<line x1="61.237798" y1="24.68224" x2="68.170379" y2="33.769836" />
<line x1="51.442936" y1="31.114234" x2="58.375517" y2="40.20183" />
<line x1="25.270715" y1="8.721297" x2="25.270715" y2="9.84059" />
<line x1="25.270715" y1="9.84059" x2="11.394661" y2="18.952581" />
<line x1="11.394661" y1="17.833289" x2="11.394661" y2="18.952581" />
<line x1="25.270715" y1="8.721297" x2="11.394661" y2="17.833289" />
<line x1="27.003861" y1="10.993196" x2="27.003861" y2="12.112489" />
<line x1="27.003861" y1="12.112489" x2="13.127806" y2="21.22448" />
<line x1="25.270715" y1="8.721297" x2="27.003861" y2="10.993196" />
<line x1="25.270715" y1="9.84059" x2="27.003861" y2="12.112489" />
<line x1="11.394661" y1="18.952581" x2="13.127806" y2="21.22448" />
<line x1="-28.266343" y1="-27.732592" x2="-28.266343" y2="-26.6133" />
<line x1="-28.266343" y1="-26.6133" x2="-42.142397" y2="-17.501308" />
<line x1="-42.142397" y1="-18.620601" x2="-42.142397" y2="-17.501308" />
<line x1="-28.266343" y1="-27.732592" x2="-42.142397" y2="-18.620601" />
<line x1="-17.867472" y1="-14.101198" x2="-17.867472" y2="-12.981906" />
<line x1="-17.867472" y1="-12.981906" x2="-31.743526" y2="-3.869914" />
<line x1="-28.266343" y1="-27.732592" x2="-17.867472" y2="-14.101198" />
<line x1="-28.266343" y1="-26.6133" x2="-17.867472" y2="-12.981906" />
<line x1="-42.142397" y1="-17.501308" x2="-31.743526" y2="-3.869914" />
<line x1="0.0" y1="-56.710818" x2="0.0" y2="-55.218428" />
<line x1="0.0" y1="-55.218428" x2="-16.32477" y2="-44.498438" />
<line x1="-16.32477" y1="-45.990828" x2="-16.32477" y2="-44.498438" />
<line x1="0.0" y1="-56.710818" x2="-16.32477" y2="-45.990828" />
<line x1="10.976586" y1="-42.322124" x2="10.976586" y2="-40.829734" />
<line x1="10.976586" y1="-40.829734" x2="-5.348184" y2="-30.109744" />
<line x1="0.0" y1="-56.710818" x2="10.976586" y2="-42.322124" />
<line x1="0.0" y1="-55.218428" x2="10.976586" y2="-40.829734" />
<line x1="-16.32477" y1="-44.498438" x2="-5.348184" y2="-30.109744" />
<line x1="42.162308" y1="25.859242" x2="42.162308" y2="27.351632" />
<line x1="42.162308" y1="27.351632" x2="30.734969" y2="34.855625" />
<line x1="30.734969" y1="33.363235" x2="30.734969" y2="34.855625" />
<line x1="42.162308" y1="25.859242" x2="30.734969" y2="33.363235" />
<line x1="43.895453" y1="28.131141" x2="43.895453" y2="29.623531" />
<line x1="43.895453" y1="29.623531" x2="32.468114" y2="37.127524" />
<line x1="42.162308" y1="25.859242" x2="43.895453" y2="28.131141" />
<line x1="42.162308" y1="27.351632" x2="43.895453" y2="29.623531" />
<line x1="30.734969" y1="34.855625" x2="32.468114" y2="37.127524" />
<line x1="1.652387" y1="62.504363" x2="1.652387" y2="64.36985" />
<line x1="1.652387" y1="64.36985" x2="-3.245044" y2="67.585847" />
<line x1="-3.245044" y1="65.72036" x2="-3.245044" y2="67.585847" />
<line x1="1.652387" y1="62.504363" x2="-3.245044" y2="65.72036" />
<line x1="7.429538" y1="70.077359" x2="7.429538" y2="71.942847" />
<line x1="7.429538" y1="71.942847" x2="2.532107" y2="75.158844" />
<line x1="1.652387" y1="62.504363" x2="7.429538" y2="70.077359" />
<line x1="1.652387" y1="64.36985" x2="7.429538" y2="71.942847" />
<line x1="-3.245044" y1="67.585847" x2="2.532107" y2="75.158844" />
<line x1="51.994357" y1="12.005799" x2="51.994357" y2="12.378896" />
<line x1="51.994357" y1="12.378896" x2="40.567018" y2="19.882889" />
<line x1="40.567018" y1="19.509792" x2="40.567018" y2="19.882889" />
<line x1="51.994357" y1="12.005799" x2="40.567018" y2="19.509792" />
<line x1="59.504653" y1="21.850694" x2="59.504653" y2="22.223792" />
<line x1="59.504653" y1="22.223792" x2="48.077314" y2="29.727785" />
<line x1="51.994357" y1="12.005799" x2="59.504653" y2="21.850694" />
<line x1="51.994357" y1="12.378896" x2="59.504653" y2="22.223792" />
<line x1="40.567018" y1="19.882889" x2="48.077314" y2="29.727785" />
<line x1="31.047866" y1="16.480843" x2="31.047866" y2="17.227038" />
<line x1="31.047866" y1="17.227038" x2="20.436766" y2="24.195031" />
<line x1="20.436766" y1="23.448836" x2="20.436766" y2="24.195031" />
<line x1="31.047866" y1="16.480843" x2="20.436766" y2="23.448836" />
<line x1="33.936441" y1="20.267341" x2="33.936441" y2="21.013536" />
<line x1="33.936441" y1="21.013536" x2="23.325341" y2="27.98153" />
<line x1="31.047866" y1="16.480843" x2="33.936441" y2="20.267341" />
<line x1="31.047866" y1="17.227038" x2="33.936441" y2="21.013536" />
<line x1="20.436766" y1="24.195031" x2="23.325341" y2="27.98153" />
<line x1="-18.493266" y1="20.409781" x2="-18.493266" y2="21.529074" />
<line x1="-18.493266" y1="21.529074" x2="-21.75822" y2="23.673072" />
<line x1="-21.75822" y1="22.553779" x2="-21.75822" y2="23.673072" />
<line x1="-18.493266" y1="20.409781" x2="-21.75822" y2="22.553779" />
<line x1="-10.405255" y1="31.011976" x2="-10.405255" y2="32.131269" />
<line x1="-10.405255" y1="32.131269" x2="-13.670209" y2="34.275267" />
<line x1="-18.493266" y1="20.409781" x2="-10.405255" y2="31.011976" />
<line x1="-18.493266" y1="21.529074" x2="-10.405255" y2="32.131269" />
<line x1="-21.75822" y1="23.673072" x2="-13.670209" y2="34.275267" />
<line x1="13.079726" y1="55.560016" x2="13.079726" y2="56.306211" />
<line x1="13.079726" y1="56.306211" x2="4.101103" y2="62.202205" />
<line x1="4.101103" y1="61.45601" x2="4.101103" y2="62.202205" />
<line x1="13.079726" y1="55.560016" x2="4.101103" y2="61.45601" />
<line x1="18.279162" y1="62.375713" x2="18.279162" y2="63.121908" />
<line x1="18.279162" y1="63.121908" x2="9.300538" y2="69.017902" />
<line x1="13.079726" y1="55.560016" x2="18.279162" y2="62.375713" />
<line x1="13.079726" y1="56.306211" x2="18.279162" y2="63.121908" />
<line x1="4.101103" y1="62.202205" x2="9.300538" y2="69.017902" />
<line x1="24.507065" y1="48.242571" x2="24.507065" y2="48.615669" />
<line x1="24.507065" y1="48.615669" x2="15.528442" y2="54.511663" />
<line x1="15.528442" y1="54.138566" x2="15.528442" y2="54.511663" />
<line x1="24.507065" y1="48.242571" x2="15.528442" y2="54.138566" />
<line x1="30.861931" y1="56.572868" x2="30.861931" y2="56.945965" />
<line x1="30.861931" y1="56.945965" x2="21.883308" y2="62.84196" />
<line x1="24.507065" y1="48.242571" x2="30.861931" y2="56.572868" />
<line x1="24.507065" y1="48.615669" x2="30.861931" y2="56.945965" />
<line x1="15.528442" y1="54.511663" x2="21.883308" y2="62.84196" />
<line x1="-18.773486" y1="-44.569378" x2="-18.773486" y2="-42.703891" />
<line x1="-18.773486" y1="-42.703891" x2="-31.017063" y2="-34.663898" />
<line x1="-31.017063" y1="-36.529385" x2="-31.017063" y2="-34.663898" />
<line x1="-18.773486" y1="-44.569378" x2="-31.017063" y2="-36.529385" />
<line x1="-7.796899" y1="-30.180684" x2="-7.796899" y2="-28.315197" />
<line x1="-7.796899" y1="-28.315197" x2="-20.040477" y2="-20.275204" />
<line x1="-18.773486" y1="-44.569378" x2="-7.796899" y2="-30.180684" />
<line x1="-18.773486" y1="-42.703891" x2="-7.796899" y2="-28.315197" />
<line x1="-31.017063" y1="-34.663898" x2="-20.040477" y2="-20.275204" />
<line x1="19.255041" y1="2.255051" x2="19.255041" y2="3.747441" />
<line x1="19.255041" y1="3.747441" x2="14.35761" y2="6.963438" />
<line x1="14.35761" y1="5.471048" x2="14.35761" y2="6.963438" />
<line x1="19.255041" y1="2.255051" x2="14.35761" y2="5.471048" />
<line x1="22.143617" y1="6.041549" x2="22.143617" y2="7.533939" />
<line x1="22.143617" y1="7.533939" x2="17.246186" y2="10.749936" />
<line x1="19.255041" y1="2.255051" x2="22.143617" y2="6.041549" />
<line x1="19.255041" y1="3.747441" x2="22.143617" y2="7.533939" />
<line x1="14.35761" y1="6.963438" x2="17.246186" y2="10.749936" />
<line x1="-33.465779" y1="-34.548289" x2="-33.465779" y2="-33.428997" />
<line x1="-33.465779" y1="-33.428997" x2="-48.97431" y2="-23.245006" />
<line x1="-48.97431" y1="-24.364299" x2="-48.97431" y2="-23.245006" />
<line x1="-33.465779" y1="-34.548289" x2="-48.97431" y2="-24.364299" />
<line x1="-29.999488" y1="-30.004491" x2="-29.999488" y2="-28.885199" />
<line x1="-29.999488" y1="-28.885199" x2="-45.50802" y2="-18.701208" />
<line x1="-33.465779" y1="-34.548289" x2="-29.999488" y2="-30.004491" />
<line x1="-33.465779" y1="-33.428997" x2="-29.999488" y2="-28.885199" />
<line x1="-48.97431" y1="-23.245006" x2="-45.50802" y2="-18.701208" />
<line x1="39.639218" y1="43.61598" x2="39.639218" y2="44.735272" />
<line x1="39.639218" y1="44.735272" x2="32.293072" y2="49.559268" />
<line x1="32.293072" y1="48.439975" x2="32.293072" y2="49.559268" />
<line x1="39.639218" y1="43.61598" x2="32.293072" y2="48.439975" />
<line x1="41.372363" y1="45.887879" x2="41.372363" y2="47.007171" />
<line x1="41.372363" y1="47.007171" x2="34.026217" y2="51.831167" />
<line x1="39.639218" y1="43.61598" x2="41.372363" y2="45.887879" />
<line x1="39.639218" y1="44.735272" x2="41.372363" y2="47.007171" />
<line x1="32.293072" y1="49.559268" x2="34.026217" y2="51.831167" />
<line x1="-51.423026" y1="-22.383203" x2="-51.423026" y2="-22.010105" />
<line x1="-51.423026" y1="-22.010105" x2="-62.034126" y2="-15.042112" />
<line x1="-62.034126" y1="-15.415209" x2="-62.034126" y2="-15.042112" />
<line x1="-51.423026" y1="-22.383203" x2="-62.034126" y2="-15.415209" />
<line x1="-41.601869" y1="-9.509108" x2="-41.601869" y2="-9.136011" />
<line x1="-41.601869" y1="-9.136011" x2="-52.21297" y2="-2.168017" />
<line x1="-51.423026" y1="-22.383203" x2="-41.601869" y2="-9.509108" />
<line x1="-51.423026" y1="-22.010105" x2="-41.601869" y2="-9.136011" />
<line x1="-62.034126" y1="-15.042112" x2="-52.21297" y2="-2.168017" />
<line x1="7.403244" y1="51.517466" x2="7.403244" y2="51.890563" />
<line x1="7.403244" y1="51.890563" x2="0.057097" y2="56.714559" />
<line x1="0.057097" y1="56.341461" x2="0.057097" y2="56.714559" />
<line x1="7.403244" y1="51.517466" x2="0.057097" y2="56.341461" />
<line x1="8.558674" y1="53.032065" x2="8.558674" y2="53.405163" />
<line x1="8.558674" y1="53.405163" x2="1.212527" y2="58.229158" />
<line x1="7.403244" y1="51.517466" x2="8.558674" y2="53.032065" />
<line x1="7.403244" y1="51.890563" x2="8.558674" y2="53.405163" />
<line x1="0.057097" y1="56.714559" x2="1.212527" y2="58.229158" />
<line x1="9.587142" y1="31.896678" x2="9.587142" y2="32.269775" />
<line x1="9.587142" y1="32.269775" x2="6.322188" y2="34.413773" />
<line x1="6.322188" y1="34.040676" x2="6.322188" y2="34.413773" />
<line x1="9.587142" y1="31.896678" x2="6.322188" y2="34.040676" />
<line x1="11.320287" y1="34.168577" x2="11.320287" y2="34.541674" />
<line x1="11.320287" y1="34.541674" x2="8.055333" y2="36.685672" />
<line x1="9.587142" y1="31.896678" x2="11.320287" y2="34.168577" />
<line x1="9.587142" y1="32.269775" x2="11.320287" y2="34.541674" />
<line x1="6.322188" y1="34.413773" x2="8.055333" y2="36.685672" />
<line x1="-64.482842" y1="-13.993759" x2="-64.482842" y2="-13.247564" />
<line x1="-64.482842" y1="-13.247564" x2="-75.093942" y2="-6.279571" />
<line x1="-75.093942" y1="-7.025766" x2="-75.093942" y2="-6.279571" />
<line x1="-64.482842" y1="-13.993759" x2="-75.093942" y2="-7.025766" />
<line x1="-56.394831" y1="-3.391564" x2="-56.394831" y2="-2.645369" />
<line x1="-56.394831" y1="-2.645369" x2="-67.005931" y2="4.322625" />
<line x1="-64.482842" y1="-13.993759" x2="-56.394831" y2="-3.391564" />
<line x1="-64.482842" y1="-13.247564" x2="-56.394831" y2="-2.645369" />
<line x1="-75.093942" y1="-6.279571" x2="-67.005931" y2="4.322625" />
<line x1="-54.661685" y1="-0.933116" x2="-54.661685" y2="-0.560019" />
<line x1="-54.661685" y1="-0.560019" x2="-61.191593" y2="3.727977" />
<line x1="-61.191593" y1="3.35488" x2="-61.191593" y2="3.727977" />
<line x1="-54.661685" y1="-0.933116" x2="-61.191593" y2="3.35488" />
<line x1="-46.573674" y1="9.669079" x2="-46.573674" y2="10.042177" />
<line x1="-46.573674" y1="10.042177" x2="-53.103582" y2="14.330173" />
<line x1="-54.661685" y1="-0.933116" x2="-46.573674" y2="9.669079" />
<line x1="-54.661685" y1="-0.560019" x2="-46.573674" y2="10.042177" />
<line x1="-61.191593" y1="3.727977" x2="-53.103582" y2="14.330173" />
</g>
<g fill="none" stroke="rgb(99,99,99)" stroke-width="0.12893788891198896" id="Hidden" stroke-dasharray="0.003275 0.388451">
<line x1="-0.732847" y1="75.997001" x2="-0.732847" y2="76.743196" />
<line x1="0.083391" y1="75.461001" x2="-0.732847" y2="75.997001" />
<line x1="-6.509998" y1="68.424004" x2="-0.732847" y2="75.997001" />
<line x1="41.234508" y1="50.898173" x2="41.234508" y2="51.271271" />
<line x1="43.683224" y1="49.290175" x2="41.234508" y2="50.898173" />
<line x1="40.656793" y1="50.140874" x2="41.234508" y2="50.898173" />
<line x1="-2.179389" y1="37.350072" x2="-2.179389" y2="37.72317" />
<line x1="-1.363151" y1="36.814073" x2="-2.179389" y2="37.350072" />
<line x1="-7.378825" y1="30.534375" x2="-2.179389" y2="37.350072" />
<line x1="0.50785" y1="34.262226" x2="0.50785" y2="35.008421" />
<line x1="13.567666" y1="25.686234" x2="0.50785" y2="34.262226" />
<line x1="-4.113871" y1="28.203828" x2="0.50785" y2="34.262226" />
<line x1="15.438666" y1="23.320935" x2="15.438666" y2="24.06713" />
<line x1="29.314721" y1="14.208944" x2="15.438666" y2="23.320935" />
<line x1="14.860951" y1="22.563636" x2="15.438666" y2="23.320935" />
<line x1="21.957682" y1="46.506672" x2="21.957682" y2="46.879769" />
<line x1="31.752544" y1="40.074678" x2="21.957682" y2="46.506672" />
<line x1="14.447386" y1="36.661776" x2="21.957682" y2="46.506672" />
<line x1="13.742708" y1="12.694844" x2="13.742708" y2="14.187234" />
<line x1="15.375185" y1="11.622845" x2="13.742708" y2="12.694844" />
<line x1="10.276418" y1="8.151046" x2="13.742708" y2="12.694844" />
<line x1="-18.556748" y1="8.898239" x2="-18.556748" y2="10.390629" />
<line x1="-6.31317" y1="0.858246" x2="-18.556748" y2="8.898239" />
<line x1="-28.377904" y1="-3.975855" x2="-18.556748" y2="8.898239" />
<line x1="-34.944999" y1="8.106687" x2="-34.944999" y2="9.972175" />
<line x1="-30.047568" y1="4.89069" x2="-34.944999" y2="8.106687" />
<line x1="-44.766155" y1="-4.767407" x2="-34.944999" y2="8.106687" />
<line x1="2.70264" y1="-12.459197" x2="2.70264" y2="-12.0861" />
<line x1="4.335117" y1="-13.531196" x2="2.70264" y2="-12.459197" />
<line x1="-7.696231" y1="-26.090591" x2="2.70264" y2="-12.459197" />
<line x1="13.769002" y1="31.814383" x2="13.769002" y2="33.679871" />
<line x1="17.033956" y1="29.670385" x2="13.769002" y2="31.814383" />
<line x1="12.035857" y1="29.542484" x2="13.769002" y2="31.814383" />
<line x1="7.112132" y1="14.012039" x2="7.112132" y2="14.758234" />
<line x1="9.560847" y1="12.404041" x2="7.112132" y2="14.012039" />
<line x1="5.378987" y1="11.74014" x2="7.112132" y2="14.012039" />
<line x1="19.154373" y1="-0.902298" x2="19.154373" y2="0.216994" />
<line x1="34.662904" y1="-11.086289" x2="19.154373" y2="-0.902298" />
<line x1="12.799507" y1="-9.232595" x2="19.154373" y2="-0.902298" />
<line x1="36.385157" y1="18.099696" x2="36.385157" y2="19.965184" />
<line x1="50.261211" y1="8.987705" x2="36.385157" y2="18.099696" />
<line x1="27.719431" y1="6.740201" x2="36.385157" y2="18.099696" />
<line x1="45.554224" y1="46.551779" x2="45.554224" y2="47.671071" />
<line x1="55.349086" y1="40.119785" x2="45.554224" y2="46.551779" />
<line x1="39.199358" y1="38.221483" x2="45.554224" y2="46.551779" />
<line x1="-3.223258" y1="11.51909" x2="-3.223258" y2="12.638382" />
<line x1="0.041696" y1="9.375092" x2="-3.223258" y2="11.51909" />
<line x1="-7.844979" y1="5.460692" x2="-3.223258" y2="11.51909" />
<line x1="-6.207993" y1="78.642247" x2="-6.207993" y2="79.015344" />
<line x1="-3.759278" y1="77.034248" x2="-6.207993" y2="78.642247" />
<line x1="-11.407429" y1="71.82655" x2="-6.207993" y2="78.642247" />
<line x1="2.15122" y1="5.529945" x2="2.15122" y2="7.022335" />
<line x1="14.394797" y1="-2.510047" x2="2.15122" y2="5.529945" />
<line x1="-1.892786" y1="0.228848" x2="2.15122" y2="5.529945" />
<line x1="-2.052427" y1="59.440412" x2="-2.052427" y2="60.932802" />
<line x1="-1.236188" y1="58.904413" x2="-2.052427" y2="59.440412" />
<line x1="-3.207857" y1="57.925813" x2="-2.052427" y2="59.440412" />
<line x1="29.001823" y1="31.650982" x2="29.001823" y2="32.02408" />
<line x1="40.429162" y1="24.146989" x2="29.001823" y2="31.650982" />
<line x1="26.690963" y1="28.621783" x2="29.001823" y2="31.650982" />
<line x1="-4.941002" y1="56.027012" x2="-4.941002" y2="56.773207" />
<line x1="5.670099" y1="49.059018" x2="-4.941002" y2="56.027012" />
<line x1="-8.407292" y1="51.483214" x2="-4.941002" y2="56.027012" />
<line x1="10.250123" y1="-11.341592" x2="10.250123" y2="-9.476104" />
<line x1="26.574893" y1="-22.061582" x2="10.250123" y2="-11.341592" />
<line x1="9.672408" y1="-12.098891" x2="10.250123" y2="-11.341592" />
<line x1="-23.491366" y1="19.908783" x2="-23.491366" y2="21.77427" />
<line x1="-20.226412" y1="17.764785" x2="-23.491366" y2="19.908783" />
<line x1="-31.579377" y1="9.306587" x2="-23.491366" y2="19.908783" />
<line x1="-6.853698" y1="-8.066697" x2="-6.853698" y2="-6.20121" />
<line x1="-0.32379" y1="-12.354693" x2="-6.853698" y2="-8.066697" />
<line x1="-16.674854" y1="-20.940792" x2="-6.853698" y2="-8.066697" />
<line x1="10.387979" y1="-15.232594" x2="10.387979" y2="-14.859496" />
<line x1="24.264033" y1="-24.344586" x2="10.387979" y2="-15.232594" />
<line x1="-1.166323" y1="-30.378587" x2="10.387979" y2="-15.232594" />
<line x1="12.16282" y1="52.192471" x2="12.16282" y2="54.057958" />
<line x1="19.508966" y1="47.368475" x2="12.16282" y2="52.192471" />
<line x1="4.652524" y1="42.347575" x2="12.16282" y2="52.192471" />
<line x1="28.111211" y1="47.589526" x2="28.111211" y2="49.081916" />
<line x1="37.906073" y1="41.157532" x2="28.111211" y2="47.589526" />
<line x1="26.955781" y1="46.074927" x2="28.111211" y2="47.589526" />
<line x1="4.551855" y1="39.003677" x2="4.551855" y2="40.869165" />
<line x1="6.184332" y1="37.931678" x2="4.551855" y2="39.003677" />
<line x1="2.240995" y1="35.974478" x2="4.551855" y2="39.003677" />
<line x1="-7.304451" y1="14.385636" x2="-7.304451" y2="15.131831" />
<line x1="-5.671974" y1="13.313637" x2="-7.304451" y2="14.385636" />
<line x1="-11.926171" y1="8.327238" x2="-7.304451" y2="14.385636" />
<line x1="25.170047" y1="5.563948" x2="25.170047" y2="6.310143" />
<line x1="39.86234" y1="-4.084043" x2="25.170047" y2="5.563948" />
<line x1="21.703757" y1="1.02015" x2="25.170047" y2="5.563948" />
<line x1="58.375517" y1="39.455635" x2="58.375517" y2="40.20183" />
<line x1="68.170379" y1="33.023641" x2="58.375517" y2="39.455635" />
<line x1="51.442936" y1="30.368039" x2="58.375517" y2="39.455635" />
<line x1="13.127806" y1="20.105188" x2="13.127806" y2="21.22448" />
<line x1="27.003861" y1="10.993196" x2="13.127806" y2="20.105188" />
<line x1="11.394661" y1="17.833289" x2="13.127806" y2="20.105188" />
<line x1="-31.743526" y1="-4.989207" x2="-31.743526" y2="-3.869914" />
<line x1="-17.867472" y1="-14.101198" x2="-31.743526" y2="-4.989207" />
<line x1="-42.142397" y1="-18.620601" x2="-31.743526" y2="-4.989207" />
<line x1="-5.348184" y1="-31.602134" x2="-5.348184" y2="-30.109744" />
<line x1="10.976586" y1="-42.322124" x2="-5.348184" y2="-31.602134" />
<line x1="-16.32477" y1="-45.990828" x2="-5.348184" y2="-31.602134" />
<line x1="32.468114" y1="35.635134" x2="32.468114" y2="37.127524" />
<line x1="43.895453" y1="28.131141" x2="32.468114" y2="35.635134" />
<line x1="30.734969" y1="33.363235" x2="32.468114" y2="35.635134" />
<line x1="2.532107" y1="73.293356" x2="2.532107" y2="75.158844" />
<line x1="7.429538" y1="70.077359" x2="2.532107" y2="73.293356" />
<line x1="-3.245044" y1="65.72036" x2="2.532107" y2="73.293356" />
<line x1="48.077314" y1="29.354687" x2="48.077314" y2="29.727785" />
<line x1="59.504653" y1="21.850694" x2="48.077314" y2="29.354687" />
<line x1="40.567018" y1="19.509792" x2="48.077314" y2="29.354687" />
<line x1="23.325341" y1="27.235335" x2="23.325341" y2="27.98153" />
<line x1="33.936441" y1="20.267341" x2="23.325341" y2="27.235335" />
<line x1="20.436766" y1="23.448836" x2="23.325341" y2="27.235335" />
<line x1="-13.670209" y1="33.155974" x2="-13.670209" y2="34.275267" />
<line x1="-10.405255" y1="31.011976" x2="-13.670209" y2="33.155974" />
<line x1="-21.75822" y1="22.553779" x2="-13.670209" y2="33.155974" />
<line x1="9.300538" y1="68.271707" x2="9.300538" y2="69.017902" />
<line x1="18.279162" y1="62.375713" x2="9.300538" y2="68.271707" />
<line x1="4.101103" y1="61.45601" x2="9.300538" y2="68.271707" />
<line x1="21.883308" y1="62.468862" x2="21.883308" y2="62.84196" />
<line x1="30.861931" y1="56.572868" x2="21.883308" y2="62.468862" />
<line x1="15.528442" y1="54.138566" x2="21.883308" y2="62.468862" />
<line x1="-20.040477" y1="-22.140692" x2="-20.040477" y2="-20.275204" />
<line x1="-7.796899" y1="-30.180684" x2="-20.040477" y2="-22.140692" />
<line x1="-31.017063" y1="-36.529385" x2="-20.040477" y2="-22.140692" />
<line x1="17.246186" y1="9.257546" x2="17.246186" y2="10.749936" />
<line x1="22.143617" y1="6.041549" x2="17.246186" y2="9.257546" />
<line x1="14.35761" y1="5.471048" x2="17.246186" y2="9.257546" />
<line x1="-45.50802" y1="-19.820501" x2="-45.50802" y2="-18.701208" />
<line x1="-29.999488" y1="-30.004491" x2="-45.50802" y2="-19.820501" />
<line x1="-48.97431" y1="-24.364299" x2="-45.50802" y2="-19.820501" />
<line x1="34.026217" y1="50.711874" x2="34.026217" y2="51.831167" />
<line x1="41.372363" y1="45.887879" x2="34.026217" y2="50.711874" />
<line x1="32.293072" y1="48.439975" x2="34.026217" y2="50.711874" />
<line x1="-52.21297" y1="-2.541115" x2="-52.21297" y2="-2.168017" />
<line x1="-41.601869" y1="-9.509108" x2="-52.21297" y2="-2.541115" />
<line x1="-62.034126" y1="-15.415209" x2="-52.21297" y2="-2.541115" />
<line x1="1.212527" y1="57.856061" x2="1.212527" y2="58.229158" />
<line x1="8.558674" y1="53.032065" x2="1.212527" y2="57.856061" />
<line x1="0.057097" y1="56.341461" x2="1.212527" y2="57.856061" />
<line x1="8.055333" y1="36.312575" x2="8.055333" y2="36.685672" />
<line x1="11.320287" y1="34.168577" x2="8.055333" y2="36.312575" />
<line x1="6.322188" y1="34.040676" x2="8.055333" y2="36.312575" />
<line x1="-67.005931" y1="3.57643" x2="-67.005931" y2="4.322625" />
<line x1="-56.394831" y1="-3.391564" x2="-67.005931" y2="3.57643" />
<line x1="-75.093942" y1="-7.025766" x2="-67.005931" y2="3.57643" />
<line x1="-53.103582" y1="13.957075" x2="-53.103582" y2="14.330173" />
<line x1="-46.573674" y1="9.669079" x2="-53.103582" y2="13.957075" />
<line x1="-61.191593" y1="3.35488" x2="-53.103582" y2="13.957075" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 990 KiB

After

Width:  |  Height:  |  Size: 990 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

View file

@ -0,0 +1,41 @@
"""
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 s2:
Trapezoid(18, 8, 180 - 60, align=(Align.CENTER, Align.MIN))
extrude(amount=80, both=True, mode=Mode.SUBTRACT)
show(p)
print(f"\npart mass = {p.part.volume*densa:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

View file

@ -0,0 +1,48 @@
"""
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)
print(f"\npart mass = {p.part.volume*densa:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

View file

@ -0,0 +1,33 @@
"""
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)
print(f"\npart mass = {ppp0103.part.volume*densb:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

View file

@ -0,0 +1,56 @@
"""
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)
print(f"\npart mass = {p.part.volume*densa:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

View file

@ -0,0 +1,29 @@
"""
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)
print(f"\npart mass = {p.part.volume*densc:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

View file

@ -0,0 +1,51 @@
"""
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)
m6 = Line(m5 @ 1, m1 @ 0)
mirror(make_face(l.line), Plane.YZ)
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)
print(f"\npart mass = {p.part.volume*densa:0.2f}")
print(p.part.bounding_box().size)

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

View file

@ -0,0 +1,51 @@
"""
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)
print(f"\npart mass = {p.part.volume*densb:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

View file

@ -0,0 +1,46 @@
"""
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)
print(f"\npart mass = {p.part.volume*densa:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

View file

@ -0,0 +1,55 @@
"""
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) # throws a warning
extrude(f, amount=10)
fillet(ppp109.edge(Select.NEW), 16)
show(ppp109)
print(f"\npart mass = {ppp109.part.volume*densb:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

View file

@ -0,0 +1,51 @@
"""
Too Tall Toby Party Pack 01-10 Light 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
with BuildPart() as p:
with BuildSketch(Plane.YZ.rotated((90, 0, 0))) as s:
with BuildLine() as l:
n2 = JernArc((0, 46), (1, 0), 40, -90)
n3 = Line(n2 @ 1, n2 @ 0)
make_face()
with BuildLine() as l2:
m1 = Line((0, 0), (42, 0))
m2 = Line((0, 0.01), (42, 0.01))
m3 = Line(m1 @ 0, m2 @ 0)
m4 = Line(m1 @ 1, m2 @ 1)
make_face()
make_hull()
extrude(amount=100 / 2)
revolve(s.sketch, axis=Axis.Y.reverse(), revolution_arc=-90)
mirror(about=Plane(p.part.faces().sort_by(Axis.X)[-1]))
mirror(about=Plane.XY)
with BuildPart() as p2:
add(p.part)
offset(amount=-8)
with BuildPart() as pzzz:
add(p2.part)
split(bisect_by=Plane.XZ.offset(46 - 16), keep=Keep.BOTTOM)
fillet(pzzz.part.faces().filter_by(Axis.Y).sort_by(Axis.Y)[0].edges(), 12)
with BuildPart() as p3:
with BuildSketch(Plane.XZ) as s2:
add(p.part.faces().sort_by(Axis.Y)[-1])
offset(amount=-8)
loft([p2.part.faces().sort_by(Axis.Y)[-5], s2.sketch.faces()[0]])
with BuildPart() as ppp0110:
add(p.part)
add(pzzz.part, mode=Mode.SUBTRACT)
add(p3.part, mode=Mode.SUBTRACT)
show(ppp0110)
print(f"\npart mass = {ppp0110.part.volume*densc:0.2f}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View file

@ -662,7 +662,7 @@ example.
* **Algebra mode**
Use the operator ``*`` to relocate the plane (post-mulitplication!).
Use the operator ``*`` to relocate the plane (post-multiplication!).
.. literalinclude:: general_examples_algebra.py
:start-after: [Ex. 22]

View file

@ -26,7 +26,6 @@ For the following use the helper function:
show_object(face, name="face")
show_object(location_symbol(loc), name="location")
.. image:: assets/location-example-01.png
@ -40,7 +39,6 @@ For the following use the helper function:
show_object(face, name="face")
show_object(plane_symbol(plane), name="plane")
.. image:: assets/location-example-07.png
@ -53,7 +51,7 @@ Relative positioning to a plane
1. **Position an object on a plane relative to the plane**
.. code-block:: python
loc = Location((0.1, 0.2, 0.3), (10, 20, 30))
face = loc * Rectangle(1,2)
@ -65,11 +63,10 @@ Relative positioning to a plane
show_object(face, name="face")
show_object(location_symbol(loc), name="location")
show_object(box, name="box")
.. image:: assets/location-example-02.png
The ``x``, ``y``, ``z`` components of ``Pos(0.2, 0.4, 0.1)`` are relative to the ``x``-axis, ``y``-axis or
The ``x``, ``y``, ``z`` components of ``Pos(0.2, 0.4, 0.1)`` are relative to the ``x``-axis, ``y``-axis or
``z``-axis of the underlying location ``loc``.
Note: ``Plane(loc) *``, ``Plane(face.location) *`` and ``loc *`` are equivalent in this example.
@ -87,7 +84,6 @@ Relative positioning to a plane
show_object(face, name="face")
show_object(location_symbol(loc), name="location")
show_object(box, name="box")
.. image:: assets/location-example-03.png
@ -107,7 +103,6 @@ Relative positioning to a plane
show_object(face, name="face")
show_object(location_symbol(loc), name="location")
show_object(box, name="box")
.. image:: assets/location-example-04.png
@ -127,11 +122,10 @@ Relative positioning to a plane
show_object(location_symbol(loc), name="location")
show_object(box, name="box")
show_object(location_symbol(loc * Rot(20, 40, 80), 0.5), options={"color":(0, 255, 255)}, name="local_location")
.. image:: assets/location-example-05.png
The box is positioned via ``Pos(0.2, 0.4, 0.1)`` relativce to the location ``loc * Rot(20, 40, 80)``
The box is positioned via ``Pos(0.2, 0.4, 0.1)`` relative to the location ``loc * Rot(20, 40, 80)``
4. **Position and rotate an object relative to a location**
@ -147,7 +141,6 @@ Relative positioning to a plane
show_object(location_symbol(loc), name="location")
show_object(box, name="box")
show_object(location_symbol(loc * Pos(0.2, 0.4, 0.1), 0.5), options={"color":(0, 255, 255)}, name="local_location")
.. image:: assets/location-example-06.png

View file

@ -22,7 +22,7 @@ with BuildPart() as latch:
offset(amount=-2)
fillet(slide_hole.vertices(), 1)
extrude(amount=-68, mode=Mode.SUBTRACT)
# Slot for the hangle to slide in
# 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)

View file

@ -131,8 +131,27 @@ interchange objects between the two systems by transferring the ``wrapped`` obje
Self Intersection
*****************
Avoid creating objects that intersect themselves - even if at a single vertex - as these topoplogies
Avoid creating objects that intersect themselves - even if at a single vertex - as these topologies
will almost certainly be invalid (even if :meth:`~topology.Shape.is_valid` reports a ``True`` value).
An example of where this my 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.
**************************
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. :meth:`pack.pack` will translate the :class:`~topology.Shape`'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):
.. image:: assets/packed_boxes_input.svg
:width: 200
:align: left
.. image:: assets/packed_boxes_output.svg
:align: right

View file

@ -9,27 +9,228 @@ To enhance users' proficiency with Build123D, this section offers a series of ch
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 TooTallToby,
a renowned figure in the realm of 3D CAD. TooTallToby is the host of the World Championship
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>`_.
Feel free to click on the parts below to embark on these engaging challenges.
.. grid:: 2
.. grid:: 3
.. grid-item-card:: Party Pack 01-01 Bearing Bracket
:img-top: assets/ttt/ttt-ppp0101_object.png
:link: ttt-ppp0101
:link-type: ref
.. grid-item-card:: Party Pack 01-02 Post Cap
:img-top: assets/ttt/ttt-ppp0102_object.png
:link: ttt-ppp0102
:link-type: ref
.. grid-item-card:: Party Pack 01-03 C Clamp Base
:img-top: assets/ttt/ttt-ppp0103_object.png
:link: ttt-ppp0103
:link-type: ref
.. grid-item-card:: Party Pack 01-04 Angle Bracket
:img-top: assets/ttt/ttt-ppp0104_object.png
:link: ttt-ppp0104
:link-type: ref
.. grid-item-card:: Party Pack 01-05 Paste Sleeve
:img-top: assets/ttt/ttt-ppp0105_object.png
:link: ttt-ppp0105
:link-type: ref
.. grid-item-card:: Party Pack 01-06 Bearing Jig
:img-top: assets/ttt/ttt-ppp0106_object.png
:link: ttt-ppp0106
:link-type: ref
.. grid-item-card:: Party Pack 01-07 Flanged Hub
:img-top: assets/ttt/ttt-ppp0107_object.png
:link: ttt-ppp0107
:link-type: ref
.. grid-item-card:: Party Pack 01-08 Tie Plate
:img-top: assets/ttt/ttt-ppp0108_object.png
:link: ttt-ppp0108
:link-type: ref
.. grid-item-card:: Party Pack 01-09 Corner Tie
:img-top: assets/ttt/ttt-ppp0109_object.png
:link: ttt-ppp0109
:link-type: ref
.. grid-item-card:: Party Pack 01-10 Light Cap
:img-top: assets/ttt/ttt-ppp0110_object.png
:link: ttt-ppp0110
:link-type: ref
.. grid-item-card:: 23-T-24 Curved Support
:img-top: assets/ttt-23-t-24-curved_support_object.png
:img-top: assets/ttt/ttt-23-t-24-curved_support_object.png
:link: ttt-23-t-24
:link-type: ref
.. _ttt-ppp0101:
Party Pack 01-01 Bearing Bracket
--------------------------------
.. image:: assets/ttt/ttt-ppp0101.png
:align: center
.. dropdown:: Object Mass
797.15 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0101.py
.. _ttt-ppp0102:
Party Pack 01-02 Post Cap
--------------------------------
.. image:: assets/ttt/ttt-ppp0102.png
:align: center
.. dropdown:: Object Mass
43.09 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0102.py
.. _ttt-ppp0103:
Party Pack 01-03 C Clamp Base
--------------------------------
.. image:: assets/ttt/ttt-ppp0103.png
:align: center
.. dropdown:: Object Mass
96.13 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0103.py
.. _ttt-ppp0104:
Party Pack 01-04 Angle Bracket
--------------------------------
.. image:: assets/ttt/ttt-ppp0104.png
:align: center
.. dropdown:: Object Mass
310.00 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0104.py
.. _ttt-ppp0105:
Party Pack 01-05 Paste Sleeve
--------------------------------
.. image:: assets/ttt/ttt-ppp0105.png
:align: center
.. dropdown:: Object Mass
57.08 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0105.py
.. _ttt-ppp0106:
Party Pack 01-06 Bearing Jig
--------------------------------
.. image:: assets/ttt/ttt-ppp0106.png
:align: center
.. dropdown:: Object Mass
328.02 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0106.py
.. _ttt-ppp0107:
Party Pack 01-07 Flanged Hub
--------------------------------
.. image:: assets/ttt/ttt-ppp0107.png
:align: center
.. dropdown:: Object Mass
372.99 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0107.py
.. _ttt-ppp0108:
Party Pack 01-08 Tie Plate
--------------------------------
.. image:: assets/ttt/ttt-ppp0108.png
:align: center
.. dropdown:: Object Mass
3387.06 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0108.py
.. _ttt-ppp0109:
Party Pack 01-09 Corner Tie
--------------------------------
.. image:: assets/ttt/ttt-ppp0109.png
:align: center
.. dropdown:: Object Mass
307.23 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0109.py
.. _ttt-ppp0110:
Party Pack 01-10 Light Cap
--------------------------------
.. image:: assets/ttt/ttt-ppp0110.png
:align: center
.. dropdown:: Object Mass
211.30 g
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt/ttt-ppp0110.py
.. _ttt-23-t-24:
23-T-24 Curved Support
----------------------
.. image:: assets/ttt-23-t-24-curved_support.png
.. image:: assets/ttt/ttt-23-t-24-curved_support.png
:align: center
.. dropdown:: Object Mass
@ -38,4 +239,4 @@ Feel free to click on the parts below to embark on these engaging challenges.
.. dropdown:: Reference Implementation
.. literalinclude:: assets/ttt-23-t-24-curved_support.py
.. literalinclude:: assets/ttt/ttt-23-t-24-curved_support.py

34
examples/packed_boxes.py Normal file
View file

@ -0,0 +1,34 @@
"""
name: packed_boxes.py
by: fischman
date: November 9th 2023
desc: Demo packing a bunch of boxes in 2D.
"""
import functools
import operator
import random
import build123d as bd
random.seed(123456)
test_boxes = [bd.Box(random.randint(1, 20), random.randint(1, 20), random.randint(1, 5))
for _ in range(50)]
packed = bd.pack(test_boxes, 3)
# Lifted from https://build123d.readthedocs.io/en/latest/import_export.html#d-to-2d-projection
def export_svg(parts, name):
part = functools.reduce(operator.add, parts, bd.Part())
view_port_origin=(0, 0, 150)
visible, hidden = part.project_to_viewport(view_port_origin)
max_dimension = max(*bd.Compound(children=visible + hidden).bounding_box().size)
exporter = bd.ExportSVG(scale=100 / max_dimension)
exporter.add_layer("Visible")
exporter.add_layer("Hidden", line_color=(99, 99, 99), line_type=bd.LineType.ISO_DOT)
exporter.add_shape(visible, layer="Visible")
exporter.add_shape(hidden, layer="Hidden")
exporter.write(f"../docs/assets/{name}.svg")
export_svg(test_boxes, "packed_boxes_input")
export_svg(packed, "packed_boxes_output")

View file

@ -15,10 +15,10 @@ from build123d.objects_sketch import *
from build123d.operations_generic import *
from build123d.operations_part import *
from build123d.operations_sketch import *
from build123d.pack import *
from build123d.topology import *
from build123d.drafting import *
from build123d.persistence import modify_copyreg
from build123d.drafting import *
from .version import version as __version__
@ -159,10 +159,11 @@ __all__ = [
"import_svg",
"import_svg_as_buildline_code",
# Other functions
"polar",
"delta",
"new_edges",
"edges_to_wires",
"new_edges",
"pack",
"polar",
# Context aware selectors
"solids",
"faces",

View file

@ -2,7 +2,7 @@
import os.path as pth
try:
from setuptools_scm import get_version
from setuptools_scm import get_version # pylint: disable=import-error
version = get_version(root=pth.join("..", "..", ".."), relative_to=__file__)
except Exception as exc:

View file

@ -594,8 +594,7 @@ class Builder(ABC):
faces = self.faces(select)
face_count = len(faces)
if face_count != 1:
msg = f"Found {face_count} faces, returning first"
warnings.warn(msg)
warnings.warn(f"Found {face_count} faces, returning first")
return faces[0]
def solids(self, select: Select = Select.ALL) -> ShapeList[Solid]:
@ -801,7 +800,7 @@ class HexLocations(LocationList):
align (Union[Align, tuple[Align, Align]], optional): align min, center, or max of object.
Defaults to (Align.CENTER, Align.CENTER).
Atributes:
Attributes:
apothem (float): radius of the inscribed circle
xCount (int): number of points ( > 0 )
yCount (int): number of points ( > 0 )
@ -890,7 +889,7 @@ class PolarLocations(LocationList):
endpoint (bool, optional): If True, `start_angle` + `angular_range` is the last sample.
Otherwise, it is not included. Defaults to False.
Atributes:
Attributes:
local_locations (list{Location}): locations relative to workplane
Raises:
@ -908,7 +907,7 @@ class PolarLocations(LocationList):
):
if count < 1:
raise ValueError(f"At least 1 elements required, requested {count}")
elif count == 1:
if count == 1:
angle_step = 0
else:
angle_step = angular_range / (count - int(endpoint))
@ -937,7 +936,7 @@ class Locations(LocationList):
Args:
pts (Union[VectorLike, Vertex, Location]): sequence of points to push
Atributes:
Attributes:
local_locations (list{Location}): locations relative to workplane
"""

View file

@ -108,6 +108,8 @@ class BuildSketch(Builder):
wires = Wire.combine(self.pending_edges)
return wires if len(wires) > 1 else wires[0]
def _add_to_pending(self, *objects: Edge):
def _add_to_pending(self, *objects: Edge, face_plane: Plane = None):
"""Integrate a sequence of objects into existing builder object"""
if face_plane:
raise NotImplementedError("face_plane arg not supported for this method")
self.pending_edges.extend(objects)

View file

@ -44,12 +44,12 @@ from build123d.build_enums import (
)
from build123d.build_line import BuildLine
from build123d.build_sketch import BuildSketch
from build123d.geometry import Axis, Color, Location, Plane, Pos, Vector, VectorLike
from build123d.geometry import Axis, Location, Plane, Pos, Vector, VectorLike
from build123d.objects_curve import Line, TangentArc
from build123d.objects_sketch import BaseSketchObject, Polygon, Text
from build123d.operations_generic import fillet, mirror, sweep
from build123d.operations_sketch import make_face, trace
from build123d.topology import Compound, Curve, Edge, Sketch, Vertex, Wire
from build123d.topology import Compound, Edge, Sketch, Vertex, Wire
class ArrowHead(BaseSketchObject):
@ -166,7 +166,7 @@ class Draft:
arrow_length (float): arrow head length. Defaults to 3.0.
line_width (float): thickness of all lines. Defaults to 0.5.
pad_around_text (float): amount of padding around text. Defaults to 2.0.
unit (Unit): measurement unit. Defautls to Unit.MM.
unit (Unit): measurement unit. Defaults to Unit.MM.
number_display (NumberDisplay): numbers as decimal or fractions.
Default to NumberDisplay.DECIMAL.
display_units (bool): control the display of units with numbers. Defaults to True.
@ -177,6 +177,7 @@ class Draft:
Defaults to 2.0.
"""
# pylint: disable=too-many-instance-attributes
# Class Attributes
unit_LUT: ClassVar[dict] = {True: "mm", False: '"'}
@ -371,6 +372,8 @@ class DimensionLine(BaseSketchObject):
label_angle: bool = False,
mode: Mode = Mode.ADD,
) -> Sketch:
# pylint: disable=too-many-locals
context = BuildSketch._get_context(self)
if sketch is None and not (context is None or context.sketch is None):
sketch = context.sketch
@ -398,7 +401,7 @@ class DimensionLine(BaseSketchObject):
# Calculate the arrow shaft length for up to three types
if arrows.count(True) == 0:
raise ValueError("No output - no arrows selected")
elif label_length + arrows.count(True) * draft.arrow_length < path_length:
if label_length + arrows.count(True) * draft.arrow_length < path_length:
shaft_length = (path_length - label_length) / 2 - draft.pad_around_text
shaft_pair = [
path_obj.trim(0.0, shaft_length / path_length),
@ -506,6 +509,8 @@ class ExtensionLine(BaseSketchObject):
project_line: VectorLike = None,
mode: Mode = Mode.ADD,
):
# pylint: disable=too-many-locals
context = BuildSketch._get_context(self)
if sketch is None and not (context is None or context.sketch is None):
sketch = context.sketch
@ -582,7 +587,7 @@ class TechnicalDrawing(BaseSketchObject):
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): displayes as 1:value. Defaults to 1.0.
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, optional): combination mode. Defaults to Mode.ADD.
@ -620,6 +625,8 @@ class TechnicalDrawing(BaseSketchObject):
line_width: float = 0.5,
mode: Mode = Mode.ADD,
):
# pylint: disable=too-many-locals
page_dim = TechnicalDrawing.page_sizes[page_size]
# Frame
frame_width = page_dim[0] - 2 * TechnicalDrawing.margin - 2 * nominal_text_size

View file

@ -29,6 +29,7 @@ license:
# pylint has trouble with the OCP imports
# pylint: disable=no-name-in-module, import-error
# pylint: disable=too-many-lines
import math
import xml.etree.ElementTree as ET
@ -38,21 +39,9 @@ from copy import copy
import ezdxf
import svgpathtools as PT
from build123d.topology import (
BoundBox,
Compound,
Edge,
Wire,
GeomType,
Shape,
Vector,
VectorLike,
)
from build123d.build_enums import Unit
from ezdxf import zoom
from ezdxf.colors import RGB, aci2rgb
from ezdxf.math import Vec2
from ezdxf.tools.standards import linetypes as ezdxf_linetypes
from OCP.BRepLib import BRepLib # type: ignore
from OCP.BRepTools import BRepTools_WireExplorer # type: ignore
from OCP.Geom import Geom_BezierCurve # type: ignore
@ -65,6 +54,19 @@ from OCP.TopAbs import TopAbs_Orientation, TopAbs_ShapeEnum # type: ignore
from OCP.TopExp import TopExp_Explorer # type: ignore
from typing_extensions import Self
from build123d.build_enums import Unit
from build123d.geometry import TOLERANCE
from build123d.topology import (
BoundBox,
Compound,
Edge,
Wire,
GeomType,
Shape,
Vector,
VectorLike,
)
PathSegment = Union[PT.Line, PT.Arc, PT.QuadraticBezier, PT.CubicBezier]
# ---------------------------------------------------------------------------
@ -84,6 +86,7 @@ class Drawing:
with_hidden: bool = True,
focus: Union[float, None] = None,
):
# pylint: disable=too-many-locals
hlr = HLRBRep_Algo()
hlr.Add(shape.wrapped)
@ -129,15 +132,11 @@ class Drawing:
if not hidden_contour_edges.IsNull():
hidden.append(hidden_contour_edges)
# magic number from CQ
# TODO: figure out the proper source of this value.
tolerance = 1e-6
# Fix the underlying geometry - otherwise we will get segfaults
for el in visible:
BRepLib.BuildCurves3d_s(el, tolerance)
BRepLib.BuildCurves3d_s(el, TOLERANCE)
for el in hidden:
BRepLib.BuildCurves3d_s(el, tolerance)
BRepLib.BuildCurves3d_s(el, TOLERANCE)
# Convert and store the results.
self.visible_lines = Compound.make_compound(map(Shape, visible))
@ -149,6 +148,7 @@ class Drawing:
class AutoNameEnum(Enum):
"""An enum class that automatically sets members' value to their name."""
@staticmethod
def _generate_next_value_(name, start, count, last_values):
return name
@ -261,6 +261,7 @@ UNITS_PER_METER = {
def unit_conversion_scale(from_unit: Unit, to_unit: Unit) -> float:
"""Return the multiplicative conversion factor to go from from_unit to to_unit."""
result = UNITS_PER_METER[to_unit] / UNITS_PER_METER[from_unit]
return result
@ -270,7 +271,7 @@ def unit_conversion_scale(from_unit: Unit, to_unit: Unit) -> float:
# ---------------------------------------------------------------------------
class Export2D(object):
class Export2D:
"""Base class for 2D exporters (DXF, SVG)."""
# When specifying a parametric interval [u1, u2] on a spline,
@ -519,6 +520,7 @@ class ExportDXF(Export2D):
line_weight: Optional[float] = None,
line_type: Optional[LineType] = None,
):
self._non_planar_point_count = 0
if unit not in self._UNITS_LOOKUP:
raise ValueError(f"unit `{unit.name}` not supported.")
if unit in ExportDXF.METRIC_UNITS:
@ -619,14 +621,13 @@ class ExportDXF(Export2D):
Returns:
Self: Document with additional shape
"""
self._non_planar_point_count = 0
if isinstance(shape, Shape):
self._add_single_shape(shape, layer)
else:
for s in shape:
self._add_single_shape(s, layer)
if self._non_planar_point_count > 0:
print(f"WARNING, exporting non-planar shape to 2D format.")
print("WARNING, exporting non-planar shape to 2D format.")
print(" This is probably not what you want.")
print(
f" {self._non_planar_point_count} points found outside the XY plane."
@ -653,8 +654,8 @@ class ExportDXF(Export2D):
"""
# Reset the main CAD viewport of the model space to the
# extents of its entities.
# TODO: Expose viewport control to the user.
# Do the same for ExportSVG.
# https://github.com/gumyr/build123d/issues/382 tracks
# exposing viewport control to the user.
zoom.extents(self._modelspace)
self._document.saveas(file_name)
@ -806,9 +807,6 @@ class ExportDXF(Export2D):
def _convert_edge(self, edge: Edge, attribs: dict):
geom_type = edge.geom_type()
if False and geom_type not in self._CONVERTER_LOOKUP:
article = "an" if geom_type[0] in "AEIOU" else "a"
print(f"Hey neat, {article} {geom_type}!")
convert = self._CONVERTER_LOOKUP.get(geom_type, ExportDXF._convert_other)
convert(self, edge, attribs)
@ -867,7 +865,7 @@ class ExportSVG(Export2D):
ValueError: Invalid unit.
"""
# pylint: disable=too-many-instance-attributes
_Converter = Callable[[Edge], ET.Element]
# These are the units which are available in the Unit enum *and*
@ -878,7 +876,7 @@ class ExportSVG(Export2D):
Unit.IN: "in",
}
class _Layer(object):
class _Layer:
def __init__(
self,
name: str,
@ -893,8 +891,7 @@ class ExportSVG(Export2D):
aci2rgb() function. We prefer (0,0,0)."""
if ci == ColorIndex.BLACK:
return (0, 0, 0)
else:
return aci2rgb(ci.value)
return aci2rgb(ci.value)
if isinstance(fill_color, ColorIndex):
fill_color = color_from_index(fill_color)
@ -925,8 +922,8 @@ class ExportSVG(Export2D):
):
if unit not in ExportSVG._UNIT_STRING:
raise ValueError(
"Invalid unit. Supported units are %s."
% ", ".join(ExportSVG._UNIT_STRING.values())
"Invalid unit. Supported units are "
f"{', '.join(ExportSVG._UNIT_STRING.values())}."
)
self.unit = unit
self.scale = scale
@ -977,7 +974,7 @@ class ExportSVG(Export2D):
Raises:
ValueError: Duplicate layer name
ValueError: Unknow linetype
ValueError: Unknown linetype
Returns:
Self: Drawing with an additional layer
@ -985,7 +982,7 @@ class ExportSVG(Export2D):
if name in self._layers:
raise ValueError(f"Duplicate layer name '{name}'.")
if line_type.value not in Export2D.LINETYPE_DEFS:
raise ValueError(f"Unknow linetype `{line_type.value}`.")
raise ValueError(f"Unknown linetype `{line_type.value}`.")
layer = ExportSVG._Layer(
name=name,
fill_color=fill_color,
@ -1029,6 +1026,7 @@ class ExportSVG(Export2D):
self._add_single_shape(s, layer, reverse_wires)
def _add_single_shape(self, shape: Shape, layer: _Layer, reverse_wires: bool):
# pylint: disable=too-many-locals
self._non_planar_point_count = 0
bb = shape.bounding_box()
self._bounds = self._bounds.add(bb) if self._bounds else bb
@ -1096,7 +1094,7 @@ class ExportSVG(Export2D):
layer.elements.extend(elements)
if self._non_planar_point_count > 0:
print(f"WARNING, exporting non-planar shape to 2D format.")
print("WARNING, exporting non-planar shape to 2D format.")
print(" This is probably not what you want.")
print(
f" {self._non_planar_point_count} points found outside the XY plane."
@ -1192,6 +1190,7 @@ class ExportSVG(Export2D):
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def _circle_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
# pylint: disable=too-many-locals
curve = edge._geom_adaptor()
circle = curve.Circle()
radius = circle.Radius()
@ -1219,7 +1218,7 @@ class ExportSVG(Export2D):
def _circle_element(self, edge: Edge) -> ET.Element:
"""Converts a Circle object into an SVG circle element."""
if edge.is_closed():
if edge.is_closed:
curve = edge._geom_adaptor()
circle = curve.Circle()
radius = circle.Radius()
@ -1237,6 +1236,7 @@ class ExportSVG(Export2D):
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def _ellipse_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
# pylint: disable=too-many-locals
curve = edge._geom_adaptor()
ellipse = curve.Ellipse()
minor_radius = ellipse.MinorRadius()
@ -1352,12 +1352,8 @@ class ExportSVG(Export2D):
def _edge_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
edge_reversed = edge.wrapped.Orientation() == TopAbs_Orientation.TopAbs_REVERSED
geom_type = edge.geom_type()
if False and geom_type not in self._SEGMENT_LOOKUP:
article = "an" if geom_type[0] in "AEIOU" else "a"
print(f"Hey neat, {article} {geom_type}!")
segments = self._SEGMENT_LOOKUP.get(geom_type, ExportSVG._other_segments)
result = segments(self, edge, reverse ^ edge_reversed)
# print(f"{geom_type} {edge.wrapped.Orientation().name} reverse={reverse^edge_reversed} {result}")
return result
_ELEMENT_LOOKUP = {
@ -1369,9 +1365,6 @@ class ExportSVG(Export2D):
def _edge_element(self, edge: Edge) -> ET.Element:
geom_type = edge.geom_type()
if False and geom_type not in self._ELEMENT_LOOKUP:
article = "an" if geom_type[0] in "AEIOU" else "a"
print(f"Hey neat, {article} {geom_type}!")
element = self._ELEMENT_LOOKUP.get(geom_type, ExportSVG._other_element)
result = element(self, edge)
return result
@ -1382,10 +1375,7 @@ class ExportSVG(Export2D):
ltname = layer.line_type.value
_, pattern = Export2D.LINETYPE_DEFS[ltname]
try:
d = self.dot_length.value
except:
d = self.dot_length
d = self.dot_length.value if isinstance(self.dot_length, DotLength) else self.dot_length
pattern = copy(pattern)
plen = len(pattern)
for i in range(0, plen):
@ -1400,7 +1390,9 @@ class ExportSVG(Export2D):
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def _group_for_layer(self, layer: _Layer, attribs: dict = {}) -> ET.Element:
def _group_for_layer(self, layer: _Layer, attribs: dict = None) -> ET.Element:
if attribs is None:
attribs = {}
if layer.fill_color:
(r, g, b) = layer.fill_color
fill = f"rgb({r},{g},{b})"
@ -1444,6 +1436,7 @@ class ExportSVG(Export2D):
Args:
path (str): The file path where the SVG data will be written.
"""
# pylint: disable=too-many-locals
bb = self._bounds
doc_margin = self.margin
if self.fit_to_stroke:
@ -1472,7 +1465,7 @@ class ExportSVG(Export2D):
container_group = ET.Element(
"g",
{
"transform": f"scale(1,-1)",
"transform": "scale(1,-1)",
"stroke-linecap": "round",
},
)

View file

@ -69,6 +69,10 @@ from OCP.Quantity import Quantity_ColorRGBA
from OCP.TopLoc import TopLoc_Location
from OCP.TopoDS import TopoDS_Face, TopoDS_Shape
from build123d.build_enums import (
Align,
)
# Create a build123d logger to distinguish these logs from application logs.
# If the user doesn't configure logging, all build123d logs will be discarded.
logging.getLogger("build123d").addHandler(logging.NullHandler())
@ -101,58 +105,66 @@ class Vector:
_dim = 0
@overload
def __init__(self, x: float, y: float, z: float): # pragma: no cover
def __init__(self, X: float, Y: float, Z: float): # pragma: no cover
...
@overload
def __init__(self, x: float, y: float): # pragma: no cover
def __init__(self, X: float, Y: float): # pragma: no cover
...
@overload
def __init__(self, vec: Vector): # pragma: no cover
def __init__(self, v: Vector): # pragma: no cover
...
@overload
def __init__(self, vec: Sequence[float]): # pragma: no cover
def __init__(self, v: Sequence[float]): # pragma: no cover
...
@overload
def __init__(self, vec: Union[gp_Vec, gp_Pnt, gp_Dir, gp_XYZ]): # pragma: no cover
def __init__(self, v: Union[gp_Vec, gp_Pnt, gp_Dir, gp_XYZ]): # pragma: no cover
...
@overload
def __init__(self): # pragma: no cover
...
def __init__(self, *args):
def __init__(self, *args, **kwargs):
self.vector_index = 0
if len(args) == 3:
f_v = gp_Vec(*args)
elif len(args) == 2:
f_v = gp_Vec(*args, 0)
elif len(args) == 1:
if isinstance(args[0], Vector):
f_v = gp_Vec(args[0].wrapped.XYZ())
elif isinstance(args[0], (tuple, Iterable)):
x, y, z, ocp_vec = 0, 0, 0, None
unknown_args = ", ".join(set(kwargs.keys()).difference(["v", "X", "Y", "Z"]))
if unknown_args:
raise ValueError(f"Unexpected argument(s) {unknown_args}")
if args and all(isinstance(args[i], (int, float)) for i in range(len(args))):
values = list(args)
values += [0.0] * max(0, (3 - len(args)))
x, y, z = values[0:3]
elif len(args) == 1 or "v" in kwargs:
first_arg = args[0] if args else None
first_arg = kwargs.get("v", first_arg) # override with kwarg
if isinstance(first_arg, Vector):
ocp_vec = gp_Vec(first_arg.wrapped.XYZ())
elif isinstance(first_arg, (tuple, Iterable)):
try:
values = [float(value) for value in args[0]]
except ValueError:
raise TypeError("Expected floats")
values = [float(value) for value in first_arg]
except (TypeError, ValueError) as exc:
raise TypeError("Expected floats") from exc
if len(values) < 3:
values += [0.0] * (3 - len(values))
f_v = gp_Vec(*values)
elif isinstance(args[0], (gp_Vec, gp_Pnt, gp_Dir)):
f_v = gp_Vec(args[0].XYZ())
elif isinstance(args[0], gp_XYZ):
f_v = gp_Vec(args[0])
ocp_vec = gp_Vec(*values[0:3])
elif isinstance(first_arg, (gp_Vec, gp_Pnt, gp_Dir)):
ocp_vec = gp_Vec(first_arg.XYZ())
elif isinstance(first_arg, gp_XYZ):
ocp_vec = gp_Vec(first_arg)
else:
raise TypeError("Expected floats, OCC gp_, or 3-tuple")
elif len(args) == 0:
f_v = gp_Vec(0, 0, 0)
else:
raise TypeError("Expected floats, OCC gp_, or 3-tuple")
raise TypeError("Expected floats, OCC gp_, or iterable")
x = kwargs.get("X", x)
y = kwargs.get("Y", y)
z = kwargs.get("Z", z)
ocp_vec = gp_Vec(x, y, z) if ocp_vec is None else ocp_vec
self._wrapped = f_v
self._wrapped = ocp_vec
def __iter__(self):
"""Initialize to beginning"""
@ -361,7 +373,7 @@ class Vector:
return self - normal * (((self - base).dot(normal)) / normal.length**2)
def __neg__(self) -> Vector:
"""Flip direction of vector opertor -"""
"""Flip direction of vector operator -"""
return self * -1
def __abs__(self) -> float:
@ -429,7 +441,9 @@ VectorLike = Union[
]
class Axis_meta(type):
class AxisMeta(type):
"""Axis meta class to enable class properties"""
@property
def X(cls) -> Axis:
"""X Axis"""
@ -446,7 +460,7 @@ class Axis_meta(type):
return Axis((0, 0, 0), (0, 0, 1))
class Axis(metaclass=Axis_meta):
class Axis(metaclass=AxisMeta):
"""Axis
Axis defined by point and direction
@ -490,10 +504,8 @@ class Axis(metaclass=Axis_meta):
origin = args[0]
direction = args[1]
if "origin" in kwargs:
origin = kwargs["origin"]
if "direction" in kwargs:
direction = kwargs["direction"]
origin = kwargs.get("origin", origin)
direction = kwargs.get("direction", direction)
if "edge" in kwargs and type(kwargs["edge"]).__name__ == "Edge":
origin = kwargs["edge"].position_at(0)
direction = kwargs["edge"].tangent_at(0)
@ -820,6 +832,19 @@ class BoundBox:
and second_box.max.Z < self.max.Z
)
def to_align_offset(self, align: Tuple[float, float]) -> Tuple[float, float]:
align_offset = []
for i in range(2):
if align[i] == Align.MIN:
align_offset.append(-self.min.to_tuple()[i])
elif align[i] == Align.CENTER:
align_offset.append(
-(self.min.to_tuple()[i] + self.max.to_tuple()[i]) / 2
)
elif align[i] == Align.MAX:
align_offset.append(-self.max.to_tuple()[i])
return align_offset
class Color:
"""
@ -867,14 +892,10 @@ class Color:
blue = args[2]
if len(args) == 4:
alpha = args[3]
if "red" in kwargs:
red = kwargs["red"]
if "green" in kwargs:
green = kwargs["green"]
if "blue" in kwargs:
blue = kwargs["blue"]
if "alpha" in kwargs:
alpha = kwargs["alpha"]
red = kwargs.get("red", red)
green = kwargs.get("green", green)
blue = kwargs.get("blue", blue)
alpha = kwargs.get("alpha", alpha)
if name:
self.wrapped = Quantity_ColorRGBA()
@ -1203,27 +1224,23 @@ class Rotation(Location):
"""Subclass of Location used only for object rotation
Attributes:
about_x (float): rotation in degrees about X axis
about_y (float): rotation in degrees about Y axis
about_z (float): rotation in degrees about Z axis
X (float): rotation in degrees about X axis
Y (float): rotation in degrees about Y axis
Z (float): rotation in degrees about Z axis
"""
def __init__(self, about_x: float = 0, about_y: float = 0, about_z: float = 0):
self.about_x = about_x
self.about_y = about_y
self.about_z = about_z
def __init__(self, X: float = 0, Y: float = 0, Z: float = 0):
self.X = X
self.Y = Y
self.Z = Z
super().__init__((0, 0, 0), (X, Y, Z))
quaternion = gp_Quaternion()
quaternion.SetEulerAngles(
gp_EulerSequence.gp_Intrinsic_XYZ,
radians(about_x),
radians(about_y),
radians(about_z),
)
transformation = gp_Trsf()
transformation.SetRotationPart(quaternion)
super().__init__(transformation)
Rot = Rotation # Short form for Algebra users who like compact notation
#:TypeVar("RotationLike"): Three tuple of angles about x, y, z or Rotation
RotationLike = Union[tuple[float, float, float], Rotation]
class Pos(Location):
@ -1253,6 +1270,10 @@ class Pos(Location):
elif 1 <= len(args) <= 3 and all([isinstance(v, (float, int)) for v in args]):
position = list(args) + [0] * (3 - len(args))
unknown_args = ", ".join(set(kwargs.keys()).difference(["v", "X", "Y", "Z"]))
if unknown_args:
raise ValueError(f"Unexpected argument(s) {unknown_args}")
if "X" in kwargs:
position[0] = kwargs["X"]
if "Y" in kwargs:
@ -1263,17 +1284,6 @@ class Pos(Location):
super().__init__(tuple(position))
class Rot(Location):
"""A rotation only sub-class of Location"""
def __init__(self, x: float = 0, y: float = 0, z: float = 0):
super().__init__((0, 0, 0), (x, y, z))
#:TypeVar("RotationLike"): Three tuple of angles about x, y, z or Rotation
RotationLike = Union[tuple[float, float, float], Rotation]
class Matrix:
"""A 3d , 4x4 transformation matrix.
@ -1407,7 +1417,9 @@ class Matrix:
return f"Matrix([{matrix_str}])"
class Plane_meta(type):
class PlaneMeta(type):
"""Plane meta class to enable class properties"""
@property
def XY(cls) -> Plane:
"""XY Plane"""
@ -1469,7 +1481,7 @@ class Plane_meta(type):
return Plane((0, 0, 0), (1, 0, 0), (0, 0, -1))
class Plane(metaclass=Plane_meta):
class Plane(metaclass=PlaneMeta):
"""Plane
A plane is positioned in space with a coordinate system such that the plane is defined by
@ -1716,21 +1728,22 @@ class Plane(metaclass=Plane_meta):
self, other: Union[Location, "Shape"]
) -> Union[Plane, List[Plane], "Shape"]:
if isinstance(other, Location):
return Plane(self.location * other)
result = Plane(self.location * other)
elif ( # LocationList
hasattr(other, "local_locations") and hasattr(other, "location_index")
) or ( # tuple of locations
isinstance(other, (list, tuple))
and all([isinstance(o, Location) for o in other])
):
return [self * loc for loc in other]
result = [self * loc for loc in other]
elif hasattr(other, "wrapped") and not isinstance(other, Vector): # Shape
return self.location * other
result = self.location * other
else:
raise TypeError(
"Planes can only be multiplied with Locations or Shapes to relocate them"
)
return result
def __repr__(self):
"""To String
@ -1775,7 +1788,7 @@ class Plane(metaclass=Plane_meta):
ValueError: Axis doesn't intersect plane
Returns:
Plane: plane with new ogin
Plane: plane with new origin
"""
if type(locator).__name__ == "Vertex":

View file

@ -34,8 +34,6 @@ from pathlib import Path
from typing import TextIO, Union
import OCP.IFSelect
from build123d.geometry import Color
from build123d.topology import Compound, Face, Shape, ShapeList, Wire
from OCP.BRep import BRep_Builder
from OCP.BRepTools import BRepTools
from OCP.RWStl import RWStl
@ -44,6 +42,8 @@ from OCP.TopoDS import TopoDS_Face, TopoDS_Shape, TopoDS_Wire
from ocpsvg import ColorAndLabel, import_svg_document
from svgpathtools import svg2paths
from build123d.geometry import Color
from build123d.topology import Compound, Face, Shape, ShapeList, Wire
def import_brep(file_name: str) -> Shape:
"""Import shape from a BREP file
@ -85,7 +85,8 @@ def import_step(file_name: str) -> Compound:
# Now read and return the shape
reader = STEPControl_Reader()
read_status = reader.ReadFile(file_name)
if read_status != OCP.IFSelect.IFSelect_RetDone:
# pylint fails to understand OCP's module here, so suppress on the next line.
if read_status != OCP.IFSelect.IFSelect_RetDone: # pylint: disable=no-member
raise ValueError(f"STEP File {file_name} could not be loaded")
for i in range(reader.NbRootsForTransfer()):
reader.TransferRoot(i + 1)
@ -155,7 +156,8 @@ def import_svg_as_buildline_code(file_name: str) -> tuple[str, str]:
"sweep",
],
}
paths, _path_attributes = svg2paths(file_name)
paths_info = svg2paths(file_name)
paths, _path_attributes = paths_info[0], paths_info[1]
builder_name = os.path.basename(file_name).split(".")[0]
builder_name = builder_name if builder_name.isidentifier() else "builder"
buildline_code = [

View file

@ -87,7 +87,7 @@ class RigidJoint(Joint):
super().__init__(label, to_part)
@overload
def connect_to(self, other: BallJoint, *, angles: RotationLike = None):
def connect_to(self, other: BallJoint, *, angles: RotationLike = None, **kwargs):
"""Connect RigidJoint and BallJoint"""
@overload
@ -113,7 +113,7 @@ class RigidJoint(Joint):
Args:
other (Joint): joint to connect to
angle (float, optional): angle in degrees. Deaults to range min.
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.
@ -148,13 +148,14 @@ class RigidJoint(Joint):
Args:
other (RigidJoint): relative to joint
angle (float, optional): angle in degrees. Deaults to range min.
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:
TypeError: other must of type BallJoint, CylindricalJoint, LinearJoint, RevoluteJoint, RigidJoint
TypeError: other must be of a type in: BallJoint, CylindricalJoint,
LinearJoint, RevoluteJoint, RigidJoint.
"""
if isinstance(other, RigidJoint):
@ -184,8 +185,9 @@ class RigidJoint(Joint):
other_location = other.relative_to(self, angles=angles).inverse()
else:
raise TypeError(
f"other must one of type BallJoint, CylindricalJoint, LinearJoint, RevoluteJoint, RigidJoint"
f" not {type(other)}"
"other must one of type "
"BallJoint, CylindricalJoint, LinearJoint, RevoluteJoint, RigidJoint "
f"not {type(other)}"
)
return other_location
@ -206,7 +208,7 @@ class RevoluteJoint(Joint):
Attributes:
angle (float): angle of joint
angle_reference (Vector): reference for angular poitions
angle_reference (Vector): reference for angular positions
angular_range (tuple[float,float]): min and max angular position of joint
relative_axis (Axis): joint axis relative to bound part
@ -259,7 +261,7 @@ class RevoluteJoint(Joint):
Args:
other (RigidJoint): relative to joint
angle (float, optional): angle in degrees. Deaults to range min.
angle (float, optional): angle in degrees. Defaults to range min.
Returns:
TypeError: other must of type RigidJoint
@ -274,7 +276,7 @@ class RevoluteJoint(Joint):
Args:
other (RigidJoint): relative to joint
angle (float, optional): angle in degrees. Deaults to range min.
angle (float, optional): angle in degrees. Defaults to range min.
Raises:
TypeError: other must of type RigidJoint
@ -373,7 +375,7 @@ class LinearJoint(Joint):
Args:
other (Joint): joint to connect to
angle (float, optional): angle in degrees. Deaults to range min.
angle (float, optional): angle in degrees. Defaults to range min.
position (float, optional): linear position. Defaults to linear range min.
Raises:
@ -400,7 +402,7 @@ class LinearJoint(Joint):
Args:
other (Joint): joint to connect to
angle (float, optional): angle in degrees. Deaults to range min.
angle (float, optional): angle in degrees. Defaults to range min.
position (float, optional): linear position. Defaults to linear range min.
Raises:
@ -480,7 +482,7 @@ class CylindricalJoint(Joint):
axis (Axis): joint axis
linear_position (float): linear joint position
rotational_position (float): revolute joint angle in degrees
angle_reference (Vector): reference for angular poitions
angle_reference (Vector): 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): joint axis relative to bound part
@ -490,6 +492,7 @@ class CylindricalJoint(Joint):
Raises:
ValueError: angle_reference must be normal to axis
"""
# pylint: disable=too-many-instance-attributes
@property
def symbol(self) -> Compound:
@ -551,7 +554,7 @@ class CylindricalJoint(Joint):
Args:
other (Joint): joint to connect to
position (float, optional): linear position. Defaults to linear range min.
angle (float, optional): angle in degrees. Deaults to range min.
angle (float, optional): angle in degrees. Defaults to range min.
Raises:
TypeError: other must be of type RigidJoint
@ -568,7 +571,7 @@ class CylindricalJoint(Joint):
Args:
other (Joint): joint to connect to
position (float, optional): linear position. Defaults to linear range min.
angle (float, optional): angle in degrees. Deaults to range min.
angle (float, optional): angle in degrees. Defaults to range min.
Raises:
TypeError: other must be of type RigidJoint

View file

@ -220,14 +220,12 @@ def display(shape: Any) -> Javascript:
if not hasattr(shape, "wrapped"): # Is a "Shape"
raise ValueError(f"Type {type(shape)} is not supported")
payload.append(
dict(
shape=to_vtkpoly_string(shape),
color=DEFAULT_COLOR,
position=[0, 0, 0],
orientation=[0, 0, 0],
)
)
payload.append({
"shape": to_vtkpoly_string(shape),
"color": DEFAULT_COLOR,
"position": [0, 0, 0],
"orientation": [0, 0, 0],
})
code = TEMPLATE.format(data=dumps(payload), element="element", ratio=0.5)
return Javascript(code)

View file

@ -6,7 +6,7 @@ by: Gumyr
date: Aug 9th 2023
desc:
This module provides the Mesher class that implements exporting and importing
This module provides the Mesher class that implements exporting and importing
both 3MF and STL mesh files. It uses the 3MF Consortium's Lib3MF library
(see https://github.com/3MFConsortium/lib3mf).
@ -188,7 +188,7 @@ class Mesher:
name_space (str): categorizer of different metadata entries
name (str): metadata label
value (str): metadata content
metadata_type (str): metadata trype
metadata_type (str): metadata type
must_preserve (bool): metadata must not be removed if unused
"""
# Get an existing meta data group if there is one
@ -367,7 +367,7 @@ class Mesher:
part_number (str, optional): part #. Defaults to None.
uuid_value (uuid, optional): value from uuid package. Defaults to None.
Rasises:
Raises:
RuntimeError: 3mf mesh is invalid
Warning: Degenerate shape skipped
Warning: 3mf mesh is not manifold

View file

@ -61,18 +61,7 @@ class BaseSketchObject(Sketch):
):
if align is not None:
align = tuplify(align, 2)
bbox = obj.bounding_box()
align_offset = []
for i in range(2):
if align[i] == Align.MIN:
align_offset.append(-bbox.min.to_tuple()[i])
elif align[i] == Align.CENTER:
align_offset.append(
-(bbox.min.to_tuple()[i] + bbox.max.to_tuple()[i]) / 2
)
elif align[i] == Align.MAX:
align_offset.append(-bbox.max.to_tuple()[i])
obj.move(Location(Vector(*align_offset)))
obj.move(Location(Vector(*obj.bounding_box().to_align_offset(align))))
context: BuildSketch = BuildSketch._get_context(self, log=False)
if context is None:
@ -298,6 +287,7 @@ class RegularPolygon(BaseSketchObject):
align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
):
# pylint: disable=too-many-locals
context = BuildSketch._get_context(self)
validate_inputs(context, self)
@ -495,14 +485,17 @@ class SlotOverall(BaseSketchObject):
self.width = width
self.slot_height = height
face = Face.make_from_wires(
Wire.make_wire(
[
Edge.make_line(Vector(-width / 2 + height / 2, 0, 0), Vector()),
Edge.make_line(Vector(), Vector(+width / 2 - height / 2, 0, 0)),
]
).offset_2d(height / 2)
)
if width != height:
face = Face.make_from_wires(
Wire.make_wire(
[
Edge.make_line(Vector(-width / 2 + height / 2, 0, 0), Vector()),
Edge.make_line(Vector(), Vector(+width / 2 - height / 2, 0, 0)),
]
).offset_2d(height / 2)
)
else:
face = Circle(width/2, mode=mode).face()
super().__init__(face, rotation, align, mode)
@ -525,7 +518,7 @@ class Text(BaseSketchObject):
rotation (float, optional): angles to rotate objects. Defaults to 0.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""
# pylint: disable=too-many-instance-attributes
_applies_to = [BuildSketch._tag]
def __init__(

View file

@ -230,23 +230,23 @@ def bounding_box(
if context is not None:
context._add_to_context(*new_faces, mode=mode)
return Sketch(Compound.make_compound(new_faces).wrapped)
else:
new_objects = []
for obj in object_list:
if isinstance(obj, Vertex):
continue
bbox = obj.bounding_box()
new_objects.append(
Solid.make_box(
bbox.size.X,
bbox.size.Y,
bbox.size.Z,
Plane((bbox.min.X, bbox.min.Y, bbox.min.Z)),
)
new_objects = []
for obj in object_list:
if isinstance(obj, Vertex):
continue
bbox = obj.bounding_box()
new_objects.append(
Solid.make_box(
bbox.size.X,
bbox.size.Y,
bbox.size.Z,
Plane((bbox.min.X, bbox.min.Y, bbox.min.Z)),
)
if context is not None:
context._add_to_context(*new_objects, mode=mode)
return Part(Compound.make_compound(new_objects).wrapped)
)
if context is not None:
context._add_to_context(*new_objects, mode=mode)
return Part(Compound.make_compound(new_objects).wrapped)
#:TypeVar("ChamferFilletType"): Type of objects which can be chamfered or filleted
@ -325,7 +325,7 @@ def chamfer(
context._add_to_context(new_part, mode=Mode.REPLACE)
return Part(Compound.make_compound([new_part]).wrapped)
elif target._dim == 2:
if target._dim == 2:
# Convert BaseSketchObject into Sketch so casting into Sketch during construction works
target = (
Sketch(target.wrapped) if isinstance(target, BaseSketchObject) else target
@ -347,7 +347,7 @@ def chamfer(
context._add_to_context(new_sketch, mode=Mode.REPLACE)
return new_sketch
elif target._dim == 1:
if target._dim == 1:
target = (
Wire(target.wrapped)
if isinstance(target, BaseLineObject)
@ -356,7 +356,7 @@ def chamfer(
if not all([isinstance(obj, Vertex) for obj in object_list]):
raise ValueError("1D fillet operation takes only Vertices")
# Remove any end vertices as these can't be filleted
if not target.is_closed():
if not target.is_closed:
object_list = filter(
lambda v: not (
(Vector(*v.to_tuple()) - target.position_at(0)).length == 0
@ -420,7 +420,7 @@ def fillet(
context._add_to_context(new_part, mode=Mode.REPLACE)
return Part(Compound.make_compound([new_part]).wrapped)
elif target._dim == 2:
if target._dim == 2:
# Convert BaseSketchObject into Sketch so casting into Sketch during construction works
target = (
Sketch(target.wrapped) if isinstance(target, BaseSketchObject) else target
@ -441,7 +441,7 @@ def fillet(
context._add_to_context(new_sketch, mode=Mode.REPLACE)
return new_sketch
elif target._dim == 1:
if target._dim == 1:
target = (
Wire(target.wrapped)
if isinstance(target, BaseLineObject)
@ -450,7 +450,7 @@ def fillet(
if not all([isinstance(obj, Vertex) for obj in object_list]):
raise ValueError("1D fillet operation takes only Vertices")
# Remove any end vertices as these can't be filleted
if not target.is_closed():
if not target.is_closed:
object_list = filter(
lambda v: not (
(Vector(*v.to_tuple()) - target.position_at(0)).length == 0
@ -508,12 +508,11 @@ def mirror(
mirrored_compound = Compound.make_compound(mirrored)
if all([obj._dim == 3 for obj in object_list]):
return Part(mirrored_compound.wrapped)
elif all([obj._dim == 2 for obj in object_list]):
if all([obj._dim == 2 for obj in object_list]):
return Sketch(mirrored_compound.wrapped)
elif all([obj._dim == 1 for obj in object_list]):
if all([obj._dim == 1 for obj in object_list]):
return Curve(mirrored_compound.wrapped)
else:
return mirrored_compound
return mirrored_compound
#:TypeVar("OffsetType"): Type of objects which can be offset
@ -641,12 +640,11 @@ def offset(
offset_compound = Compound.make_compound(new_objects)
if all([obj._dim == 3 for obj in object_list]):
return Part(offset_compound.wrapped)
elif all([obj._dim == 2 for obj in object_list]):
if all([obj._dim == 2 for obj in object_list]):
return Sketch(offset_compound.wrapped)
elif all([obj._dim == 1 for obj in object_list]):
if all([obj._dim == 1 for obj in object_list]):
return Curve(offset_compound.wrapped)
else:
return offset_compound
return offset_compound
#:TypeVar("ProjectType"): Type of objects which can be projected
@ -693,7 +691,7 @@ def project(
if not objects and context is None:
raise ValueError("No object to project")
elif not objects and context is not None and isinstance(context, BuildPart):
if not objects and context is not None and isinstance(context, BuildPart):
object_list = context.pending_edges + context.pending_faces
context.pending_edges = []
context.pending_faces = []
@ -870,12 +868,11 @@ def scale(
scale_compound = Compound.make_compound(new_objects)
if all([obj._dim == 3 for obj in object_list]):
return Part(scale_compound.wrapped)
elif all([obj._dim == 2 for obj in object_list]):
if all([obj._dim == 2 for obj in object_list]):
return Sketch(scale_compound.wrapped)
elif all([obj._dim == 1 for obj in object_list]):
if all([obj._dim == 1 for obj in object_list]):
return Curve(scale_compound.wrapped)
else:
return scale_compound
return scale_compound
#:TypeVar("SplitType"): Type of objects which can be offset
@ -925,12 +922,11 @@ def split(
split_compound = Compound.make_compound(new_objects)
if all([obj._dim == 3 for obj in object_list]):
return Part(split_compound.wrapped)
elif all([obj._dim == 2 for obj in object_list]):
if all([obj._dim == 2 for obj in object_list]):
return Sketch(split_compound.wrapped)
elif all([obj._dim == 1 for obj in object_list]):
if all([obj._dim == 1 for obj in object_list]):
return Curve(split_compound.wrapped)
else:
return split_compound
return split_compound
#:TypeVar("SweepType"): Type of objects which can be swept
@ -1051,5 +1047,4 @@ def sweep(
if new_solids:
return Part(Compound.make_compound(new_solids).wrapped)
else:
return Sketch(Compound.make_compound(new_faces).wrapped)
return Sketch(Compound.make_compound(new_faces).wrapped)

View file

@ -80,6 +80,7 @@ def extrude(
Returns:
Part: extruded object
"""
# pylint: disable=too-many-locals
context: BuildPart = BuildPart._get_context("extrude")
validate_inputs(context, "extrude", to_extrude)

View file

@ -117,8 +117,8 @@ def trace(
Convert edges, wires or pending edges into faces by sweeping a perpendicular line along them.
Args:
lines (Union[Curve, Edge, Wire, Iterable[Union[Curve, Edge, Wire]]], optional): lines to trace.
Defaults to sketch pending edges.
lines (Union[Curve, Edge, Wire, Iterable[Union[Curve, Edge, Wire]]], optional): lines to
trace. Defaults to sketch pending edges.
line_width (float, optional): Defaults to 1.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.

125
src/build123d/pack.py Normal file
View file

@ -0,0 +1,125 @@
"""
build123d Pack
name: pack.py
by: fischman
date: November 9th 2023
desc:
Utility code for packing objects in a squarish 2D area.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable, Collection, Optional, cast
from build123d import Location, Shape
def _pack2d(objects: Collection[object],
width_fn: Callable[[object], float],
length_fn: Callable[[object], float]) -> Collection[tuple[float,float]]:
"""Takes an iterable of objects to pack into a square(ish) 2D
arrangement, and return a list of (x,y) locations to place each to
achieve the packing.
Based on https://codeincomplete.com/articles/bin-packing/ and
implemented as a straight-forward port of
https://github.com/jakesgordon/bin-packing/blob/master/js/packer.growing.js
"""
@dataclass
class _Node:
used: bool = False
x: float = 0
y: float = 0
w: float = 0
h: float = 0
down: Optional["_Node"] = None
right: Optional["_Node"] = None
def find_node(start, w, h):
if start.used:
return find_node(start.right, w, h) or find_node(start.down, w, h)
if o[1] <= start.w and o[2] <= start.h:
return start
return None
def split_node(node, w, h):
assert not node.used
node.used = True
node.down = _Node(x=node.x, y=node.y+h, w=node.w, h=node.h-h)
node.right = _Node(x=node.x+w, y=node.y, w=node.w-w, h=h)
return node
def grow_node(w, h):
nonlocal root
can_grow_down = w <= root.w
can_grow_right = h <= root.h
should_grow_right = can_grow_right and (root.h >= (root.w + w))
should_grow_down = can_grow_down and (root.w >= (root.h + h))
if should_grow_right:
return grow_right(w, h)
if should_grow_down:
return grow_down(w, h)
if can_grow_right:
return grow_right(w, h)
if can_grow_down:
return grow_down(w, h)
assert False, f"Failed to grow! root: {root}, w: {w}, h: {h}"
def grow_right(w, h):
nonlocal root
root = _Node(used=True, x=0, y=0, w=root.w+w, h=root.h,
down=root, right=_Node(x=root.w, w=w, h=root.h))
node = find_node(root, w, h)
assert node, "Failed to grow right! root: {root}, w: {w}, h: {h}"
return split_node(node, w, h)
def grow_down(w, h):
nonlocal root
root = _Node(used=True, x=0, y=0, w=root.w, h=root.h+h,
down=_Node(y=root.h, w=root.w, h=h), right=root)
node = find_node(root, w, h)
assert node, "Failed to grow down! root: {root}, w: {w}, h: {h}"
return split_node(node, w, h)
assert len(objects)>0
sorted_objects = sorted([(i, width_fn(o), length_fn(o)) for (i, o) in enumerate(objects)],
key=lambda d: min(d[1], d[2]), reverse=True)
sorted_objects = sorted(sorted_objects, key=lambda d: max(d[1], d[2]), reverse=True)
root = _Node(False, w=sorted_objects[0][1], h=sorted_objects[0][2])
translations = []
for o in sorted_objects:
node = find_node(root, o[1], o[2])
if node:
node = split_node(node, o[1], o[2])
else:
node = grow_node(o[1], o[2])
translations.append((o[0], node.x, node.y))
return [(t[1], t[2]) for t in sorted(translations, key=lambda t: t[0])]
def pack(objects: Collection[Shape], padding: float) -> Collection[Shape]:
"""Pack objects in a squarish area in Plane.XY."""
bounding_boxes = {o: o.bounding_box().size + (padding, padding) for o in objects}
translations = _pack2d(
objects,
width_fn=lambda o: bounding_boxes[cast(Shape, o)].X,
length_fn=lambda o: bounding_boxes[cast(Shape, o)].Y)
translated = [
Location((t[0]-o.bounding_box().min.X, t[1]-o.bounding_box().min.Y, 0)) * o
for (o,t) in zip(objects, translations)
]
# Assert the packing didn't cause any overlaps.
def _overlapping(bb1, bb2):
# Boundaries of the intersection of the two bounding boxes.
min_x = max(bb1.min.X, bb2.min.X)
min_y = max(bb1.min.Y, bb2.min.Y)
max_x = min(bb1.max.X, bb2.max.X)
max_y = min(bb1.max.Y, bb2.max.Y)
return max_x > min_x and max_y > min_y
bb = [t.bounding_box() for t in translated]
for (i, bb_i) in enumerate(bb):
for (j, bb_j) in enumerate(bb[i+1:]):
assert not _overlapping(bb_i, bb_j), f"Objects at indexes {i} and {j} overlap!"
return translated

View file

@ -83,9 +83,9 @@ def serialize_location(location: TopLoc_Location) -> bytes:
"""
if location is None:
return None
transfo = location.Transformation()
translation = transfo.TranslationPart()
rotation = transfo.GetRotation()
transform = location.Transformation()
translation = transform.TranslationPart()
rotation = transform.GetRotation()
# convert floats in bytes
translation_bytes = bytearray()
for i in range(1, 4):
@ -126,10 +126,10 @@ def deserialize_location(buffer: bytes) -> TopLoc_Location:
)
# Create the TopLoc_Location object
transfo = gp_Trsf()
transfo.SetTransformation(rotation, translation)
transform = gp_Trsf()
transform.SetTransformation(rotation, translation)
return TopLoc_Location(transfo)
return TopLoc_Location(transform)
def reduce_shape(shape: TopoDS_Shape) -> tuple:

View file

@ -30,6 +30,7 @@ from __future__ import annotations
# pylint has trouble with the OCP imports
# pylint: disable=no-name-in-module, import-error
# pylint: disable=too-many-lines
# other pylint warning to temp remove:
# too-many-arguments, too-many-locals, too-many-public-methods,
# too-many-statements, too-many-instance-attributes, too-many-branches
@ -41,10 +42,9 @@ import platform
import sys
import warnings
from abc import ABC, abstractmethod
from datetime import datetime
from io import BytesIO
from itertools import combinations
from math import degrees, radians, inf, pi, sqrt, sin, cos, tan, copysign, ceil, floor
from math import radians, inf, pi, sin, cos, tan, copysign, ceil, floor
from typing import (
Any,
Callable,
@ -60,13 +60,10 @@ from typing import (
overload,
)
from typing import cast as tcast
import xml.etree.cElementTree as ET
from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
from typing_extensions import Self, Literal
from anytree import NodeMixin, PreOrderIter, RenderTree
from scipy.spatial import ConvexHull
from scipy.optimize import minimize
from vtkmodules.vtkCommonDataModel import vtkPolyData
from vtkmodules.vtkFiltersCore import vtkPolyDataNormals, vtkTriangleFilter
@ -88,7 +85,6 @@ from OCP.BRepAlgoAPI import (
BRepAlgoAPI_Cut,
BRepAlgoAPI_Fuse,
BRepAlgoAPI_Splitter,
BRepAlgoAPI_Section,
)
from OCP.BRepBuilderAPI import (
BRepBuilderAPI_Copy,
@ -152,8 +148,7 @@ from OCP.Font import (
)
from OCP.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse # geometry construction
from OCP.gce import gce_MakeLin
from OCP.GCE2d import GCE2d_MakeSegment
from OCP.GCPnts import GCPnts_AbscissaPoint, GCPnts_QuasiUniformDeflection
from OCP.GCPnts import GCPnts_AbscissaPoint
from OCP.Geom import (
Geom_BezierCurve,
Geom_ConicalSurface,
@ -164,19 +159,16 @@ from OCP.Geom import (
Geom_Line,
)
from OCP.Geom2d import Geom2d_Curve, Geom2d_Line, Geom2d_TrimmedCurve
from OCP.Geom2dAdaptor import Geom2dAdaptor_Curve
from OCP.Geom2dAPI import Geom2dAPI_InterCurveCurve
from OCP.GeomAbs import GeomAbs_C0, GeomAbs_Intersection, GeomAbs_JoinType
from OCP.GeomAPI import (
GeomAPI_Interpolate,
GeomAPI_IntCS,
GeomAPI_IntSS,
GeomAPI_PointsToBSpline,
GeomAPI_PointsToBSplineSurface,
GeomAPI_ProjectPointOnSurf,
GeomAPI_ProjectPointOnCurve,
)
from OCP.GeomConvert import GeomConvert
from OCP.GeomFill import (
GeomFill_CorrectedFrenet,
GeomFill_Frenet,
@ -190,13 +182,10 @@ from OCP.gp import (
gp_Dir,
gp_Dir2d,
gp_Elips,
gp_Lin,
gp_Lin2d,
gp_Pnt,
gp_Pnt2d,
gp_Trsf,
gp_Vec,
gp_Vec2d,
)
# properties used to store mass calculation result
@ -218,7 +207,6 @@ from OCP.ShapeFix import (
ShapeFix_Face,
ShapeFix_Shape,
ShapeFix_Solid,
ShapeFix_Wire,
ShapeFix_Wireframe,
)
from OCP.ShapeUpgrade import ShapeUpgrade_UnifySameDomain
@ -275,7 +263,6 @@ from OCP.TopTools import (
from build123d.build_enums import (
Align,
AngularDirection,
ApproxOption,
CenterOf,
FontStyle,
FrameMethod,
@ -286,34 +273,27 @@ from build123d.build_enums import (
Side,
SortBy,
Transition,
Unit,
Until,
)
from build123d.geometry import (
DEG2RAD,
TOLERANCE,
Axis,
BoundBox,
Color,
Location,
Matrix,
Plane,
Rotation,
RotationLike,
Vector,
VectorLike,
logger,
)
# Create a build123d logger to distinguish these logs from application logs.
# If the user doesn't configure logging, all build123d logs will be discarded.
logging.getLogger("build123d").addHandler(logging.NullHandler())
logger = logging.getLogger("build123d")
TOLERANCE = 1e-6
TOL = 1e-2
DEG2RAD = pi / 180.0
RAD2DEG = 180 / pi
HASH_CODE_MAX = 2147483647 # max 32bit signed int, required by OCC.Core.HashCode
shape_LUT = {
ta.TopAbs_VERTEX: "Vertex",
ta.TopAbs_EDGE: "Edge",
@ -412,13 +392,15 @@ Geoms = Literal[
]
def tuplify(obj, dim):
def tuplify(obj: Any, dim: int) -> tuple:
"""Create a size tuple"""
if obj is None:
return None
result = None
elif isinstance(obj, (tuple, list)):
return obj
result = tuple(obj)
else:
return tuple([obj] * dim)
result = tuple([obj] * dim)
return result
class Mixin1D:
@ -672,6 +654,7 @@ class Mixin1D:
"""Does the Edge/Wire loop forward or reverse"""
return self.wrapped.Orientation() == TopAbs_Orientation.TopAbs_FORWARD
@property
def is_closed(self) -> bool:
"""Are the start and end points equal?"""
return BRep_Tool.IsClosed_s(self.wrapped)
@ -857,8 +840,8 @@ class Mixin1D:
obj = downcast(offset_builder.Shape())
if isinstance(obj, TopoDS_Compound):
offset_wire = None
for i, el in enumerate(Compound(obj)):
offset_wire = Wire(el.wrapped)
for i, shape in enumerate(Compound(obj)):
offset_wire = Wire(shape.wrapped)
if i >= 1:
raise RuntimeError("Multiple Wires generated")
if offset_wire is None:
@ -899,20 +882,17 @@ class Mixin1D:
end0 = offset_wire.position_at(0)
end1 = offset_wire.position_at(1)
if (self0 - end0).length - distance <= TOLERANCE:
e0 = Edge.make_line(self0, end0)
e1 = Edge.make_line(self1, end1)
edge0 = Edge.make_line(self0, end0)
edge1 = Edge.make_line(self1, end1)
else:
e0 = Edge.make_line(self0, end1)
e1 = Edge.make_line(self1, end0)
edge0 = Edge.make_line(self0, end1)
edge1 = Edge.make_line(self1, end0)
offset_wire = Wire.make_wire(
line.edges() + offset_wire.edges() + [e0, e1]
line.edges() + offset_wire.edges() + [edge0, edge1]
)
offset_edges = offset_wire.edges()
if len(offset_edges) == 1:
return offset_edges[0]
else:
return offset_wire
return offset_edges[0] if len(offset_edges) == 1 else offset_wire
def perpendicular_line(
self, length: float, u_value: float, plane: Plane = Plane.XY
@ -1494,40 +1474,44 @@ class Shape(NodeMixin):
bool: is the shape manifold or water tight
"""
if isinstance(self, Compound):
return all([sub_shape.is_manifold for sub_shape in self])
else:
# Create an empty indexed data map to store the edges and their corresponding faces.
map = TopTools_IndexedDataMapOfShapeListOfShape()
# pylint: disable=not-an-iterable
return all(sub_shape.is_manifold for sub_shape in self)
# Fill the map with edges and their associated faces in the given shape. Each edge in
# the map is associated with a list of faces that share that edge.
TopExp.MapShapesAndAncestors_s(
self.wrapped, ta.TopAbs_EDGE, ta.TopAbs_FACE, map
)
result = True
# Create an empty indexed data map to store the edges and their corresponding faces.
shape_map = TopTools_IndexedDataMapOfShapeListOfShape()
# Iterate over the edges in the map and checks if each edge is non-degenerate and has
# exactly two faces associated with it.
for i in range(map.Extent()):
# Access each edge in the map sequentially
edge = downcast(map.FindKey(i + 1))
# Fill the map with edges and their associated faces in the given shape. Each edge in
# the map is associated with a list of faces that share that edge.
TopExp.MapShapesAndAncestors_s(
self.wrapped, ta.TopAbs_EDGE, ta.TopAbs_FACE, shape_map
)
vertex0 = TopoDS_Vertex()
vertex1 = TopoDS_Vertex()
# Iterate over the edges in the map and checks if each edge is non-degenerate and has
# exactly two faces associated with it.
for i in range(shape_map.Extent()):
# Access each edge in the map sequentially
edge = downcast(shape_map.FindKey(i + 1))
# Extract the two vertices of the current edge and stores them in vertex0 and vertex1.
TopExp.Vertices_s(edge, vertex0, vertex1)
vertex0 = TopoDS_Vertex()
vertex1 = TopoDS_Vertex()
# Check if both vertices are null and if they are the same vertex. If so, the edge is
# considered degenerate (i.e., has zero length), and it is skipped.
if vertex0.IsNull() and vertex1.IsNull() and vertex0.IsSame(vertex1):
continue
# Extract the two vertices of the current edge and stores them in vertex0/1.
TopExp.Vertices_s(edge, vertex0, vertex1)
# Check if the current edge has exactly two faces associated with it. If not, it means
# the edge is not shared by exactly two faces, indicating that the shape is not manifold.
if map.FindFromIndex(i + 1).Extent() != 2:
return False
# Check if both vertices are null and if they are the same vertex. If so, the
# edge is considered degenerate (i.e., has zero length), and it is skipped.
if vertex0.IsNull() and vertex1.IsNull() and vertex0.IsSame(vertex1):
continue
return True
# Check if the current edge has exactly two faces associated with it. If not,
# it means the edge is not shared by exactly two faces, indicating that the
# shape is not manifold.
if shape_map.FindFromIndex(i + 1).Extent() != 2:
result = False
break
return result
class _DisplayNode(NodeMixin):
"""Used to create anytree structures from TopoDS_Shapes"""
@ -1590,12 +1574,13 @@ class Shape(NodeMixin):
# Calculate the size of the tree labels
size_tuples = [(node.height, len(node.label)) for node in root_node.descendants]
size_tuples.append((root_node.height, len(root_node.label)))
# pylint: disable=cell-var-from-loop
size_tuples_per_level = [
list(filter(lambda ll: ll[0] == l, size_tuples))
for l in range(root_node.height + 1)
]
max_sizes_per_level = [
max(4, max([l[1] for l in level])) for level in size_tuples_per_level
max(4, max(l[1] for l in level)) for level in size_tuples_per_level
]
level_sizes_per_level = [
l + i * 4 for i, l in enumerate(reversed(max_sizes_per_level))
@ -1605,7 +1590,7 @@ class Shape(NodeMixin):
# Build the tree line by line
result = ""
for pre, _fill, node in RenderTree(root_node):
treestr = ("%s%s" % (pre, node.label)).ljust(tree_label_width)
treestr = f"{pre}{node.label}".ljust(tree_label_width)
if hasattr(root_node, "address"):
address = node.address
name = ""
@ -1760,6 +1745,10 @@ class Shape(NodeMixin):
)
return [loc * self for loc in other]
def center(self) -> Vector:
"""All of the derived classes from Shape need a center method"""
raise NotImplementedError
def clean(self) -> Self:
"""clean
@ -2558,7 +2547,7 @@ class Shape(NodeMixin):
Args:
to_fuse (sequence Shape): shapes to fuse
glue (bool, optional): performance improvement for some shapes. Defaults to False.
tol (float, optional): tolerarance. Defaults to None.
tol (float, optional): tolerance. Defaults to None.
Returns:
Shape: fused shape
@ -2657,13 +2646,15 @@ class Shape(NodeMixin):
if vertex_intersections:
if shape_intersections.is_null():
shape_intersections = Compound.fuse(*vertex_intersections)
shape_intersections = vertex_intersections.pop().fuse(
*vertex_intersections
)
else:
shape_intersections = shape_intersections.fuse(*vertex_intersections)
if edge_intersections:
if shape_intersections.is_null():
shape_intersections = Compound.fuse(*edge_intersections)
shape_intersections = edge_intersections.pop().fuse(*edge_intersections)
else:
shape_intersections = shape_intersections.fuse(*edge_intersections)
@ -2741,7 +2732,7 @@ class Shape(NodeMixin):
if keep == Keep.BOTH:
result = Compound(downcast(splitter.Shape()))
else:
parts = [shape for shape in Compound(downcast(splitter.Shape()))]
parts = list(Compound(downcast(splitter.Shape())))
tops = []
bottoms = []
for part in parts:
@ -3095,7 +3086,7 @@ class Shape(NodeMixin):
Extrude self in the provided direction.
Args:
direction (VectorLike): direction and magnitue of extrusion
direction (VectorLike): direction and magnitude of extrusion
Raises:
ValueError: Unsupported class
@ -3147,7 +3138,7 @@ class Shape(NodeMixin):
* Shells generate Compounds
Args:
direction (VectorLike): direction and magnitue of extrusion
direction (VectorLike): direction and magnitude of extrusion
Raises:
ValueError: Unsupported class
@ -3255,6 +3246,8 @@ K = TypeVar("K")
class ShapePredicate(Protocol):
"""Predicate for shape filters"""
def __call__(self, shape: Shape) -> bool:
...
@ -3287,7 +3280,9 @@ class ShapeList(list[T]):
objects.
Args:
filter_by (Union[Axis,Plane,GeomType]): axis, plane, or geom type to filter and possibly sort by. Filtering by a plane returns faces/edges parallel to that plane.
filter_by (Union[Axis,Plane,GeomType]): axis, plane, or geom type to filter
and possibly sort by. Filtering by a plane returns faces/edges parallel
to that plane.
reverse (bool, optional): invert the geom type filter. Defaults to False.
tolerance (float, optional): maximum deviation from axis. Defaults to 1e-5.
@ -3340,13 +3335,19 @@ class ShapeList(list[T]):
elif isinstance(filter_by, Plane):
predicate = plane_parallel_predicate(filter_by, tolerance=tolerance)
elif isinstance(filter_by, GeomType):
predicate = lambda o: o.geom_type() == filter_by.name
def predicate(obj):
return obj.geom_type() == filter_by.name
else:
raise ValueError(f"Unsupported filter_by predicate: {filter_by}")
# final predicate is negated if `reverse=True`
if reverse:
actual_predicate = lambda shape: not predicate(shape)
def actual_predicate(shape):
return not predicate(shape)
else:
actual_predicate = predicate
@ -3428,27 +3429,46 @@ class ShapeList(list[T]):
if isinstance(group_by, Axis):
axis_as_location = group_by.location.inverse()
key_f = lambda obj: round(
# group_by.to_plane().to_local_coords(obj).center().Z, tol_digits
(axis_as_location * Location(obj.center())).position.Z,
tol_digits,
)
def key_f(obj):
return round(
(axis_as_location * Location(obj.center())).position.Z,
tol_digits,
)
elif isinstance(group_by, (Edge, Wire)):
key_f = lambda obj: round(
group_by.param_at_point(obj.center()),
tol_digits,
)
def key_f(obj):
return round(
group_by.param_at_point(obj.center()),
tol_digits,
)
elif isinstance(group_by, SortBy):
if group_by == SortBy.LENGTH:
key_f = lambda obj: round(obj.length, tol_digits)
def key_f(obj):
return round(obj.length, tol_digits)
elif group_by == SortBy.RADIUS:
key_f = lambda obj: round(obj.radius, tol_digits)
def key_f(obj):
return round(obj.radius, tol_digits)
elif group_by == SortBy.DISTANCE:
key_f = lambda obj: round(obj.center().length, tol_digits)
def key_f(obj):
return round(obj.center().length, tol_digits)
elif group_by == SortBy.AREA:
key_f = lambda obj: round(obj.area, tol_digits)
def key_f(obj):
return round(obj.area, tol_digits)
elif group_by == SortBy.VOLUME:
key_f = lambda obj: round(obj.volume, tol_digits)
def key_f(obj):
return round(obj.volume, tol_digits)
elif callable(group_by):
key_f = group_by
@ -3482,11 +3502,12 @@ class ShapeList(list[T]):
)
elif isinstance(sort_by, (Edge, Wire)):
def u_of_closest_center(o) -> float:
def u_of_closest_center(obj) -> float:
"""u-value of closest point between object center and sort_by"""
p1, _p2 = sort_by.closest_points(o.center())
return sort_by.param_at_point(p1)
pnt1, _pnt2 = sort_by.closest_points(obj.center())
return sort_by.param_at_point(pnt1)
# pylint: disable=unnecessary-lambda
objects = sorted(
self, key=lambda o: u_of_closest_center(o), reverse=reverse
)
@ -3719,16 +3740,18 @@ class GroupBy:
return self.groups[key]
def group(self, key: K):
"""Select group by key"""
for k, i in self.key_to_group_index:
if key == k:
return self.groups[i]
raise KeyError(key)
def group_for(self, shape: Shape):
"""Select group by shape"""
return self.group(self.key_f(shape))
class Compound(Shape, Mixin3D):
class Compound(Mixin3D, Shape):
"""Compound
A collection of Shapes
@ -4012,18 +4035,8 @@ class Compound(Shape, Mixin3D):
# Align the text from the bounding box
align = tuplify(align, 2)
bbox = text_flat.bounding_box()
align_offset = []
for i in range(2):
if align[i] == Align.MIN:
align_offset.append(-bbox.min.to_tuple()[i])
elif align[i] == Align.CENTER:
align_offset.append(
-(bbox.min.to_tuple()[i] + bbox.max.to_tuple()[i]) / 2
)
elif align[i] == Align.MAX:
align_offset.append(-bbox.max.to_tuple()[i])
text_flat = text_flat.translate(Vector(*align_offset))
text_flat = text_flat.translate(Vector(
*text_flat.bounding_box().to_align_offset(align)))
if text_path is not None:
path_length = text_path.length
@ -4224,7 +4237,7 @@ class Curve(Compound):
return Wire.combine(self.edges())
class Edge(Shape, Mixin1D):
class Edge(Mixin1D, Shape):
"""A trimmed curve that represents the border of a face"""
_dim = 1
@ -4235,7 +4248,7 @@ class Edge(Shape, Mixin1D):
def close(self) -> Union[Edge, Wire]:
"""Close an Edge"""
if not self.is_closed():
if not self.is_closed:
return_value = Wire.make_wire([self]).close()
else:
return_value = self
@ -4265,7 +4278,6 @@ class Edge(Shape, Mixin1D):
def find_tangent(
self,
angle: float,
plane: Plane = Plane.XY,
) -> list[float]:
"""find_tangent
@ -4273,7 +4285,6 @@ class Edge(Shape, Mixin1D):
Args:
angle (float): target angle in degrees
plane (Plane, optional): plane that Edge was constructed on. Defaults to Plane.XY.
Returns:
list[float]: u values between 0.0 and 1.0
@ -4287,7 +4298,7 @@ class Edge(Shape, Mixin1D):
u_values = []
else:
# Solve this problem geometrically by creating a tangent curve and finding intercepts
periodic = int(self.is_closed()) # if closed don't include end point
periodic = int(self.is_closed) # if closed don't include end point
tan_pnts = []
previous_tangent = None
@ -4392,8 +4403,8 @@ class Edge(Shape, Mixin1D):
else:
if 0.0 <= self.param_at_point(pnt) <= 1.0:
valid_crosses.append(pnt)
except:
pass
except ValueError:
pass # skip invalid points
return ShapeList(valid_crosses)
@ -4457,16 +4468,12 @@ class Edge(Shape, Mixin1D):
curve = BRep_Tool.Curve_s(self.wrapped, 0, 1)
param_min = _project_point_on_curve(curve, self.position_at(0).to_pnt())
param_value = _project_point_on_curve(curve, point.to_pnt())
if self.is_closed():
if self.is_closed:
u_value = (param_value - param_min) / (self.param_at(1) - self.param_at(0))
else:
param_max = _project_point_on_curve(curve, self.position_at(1).to_pnt())
u_value = (param_value - param_min) / (param_max - param_min)
# if not (-TOLERANCE <= u_value <= 1.0 + TOLERANCE):
# raise RuntimeError(
# f"param_at_point returned {u_value}, which is invalid {param_value=}, {param_min=}, {param_max=}"
# )
return u_value
@classmethod
@ -5167,7 +5174,7 @@ class Face(Shape):
def wire(self) -> Wire:
"""Return the outerwire, generate a warning if inner_wires present"""
if self.inner_wires():
warnings.warn(f"Found holes, returning outer_wire")
warnings.warn("Found holes, returning outer_wire")
return self.outer_wire()
@classmethod
@ -5254,7 +5261,7 @@ class Face(Shape):
Returns:
Face: planar face potentially with holes
"""
if inner_wires and not outer_wire.is_closed():
if inner_wires and not outer_wire.is_closed:
raise ValueError("Cannot build face(s): outer wire is not closed")
inner_wires = inner_wires if inner_wires else []
@ -5273,7 +5280,7 @@ class Face(Shape):
face_builder = BRepBuilderAPI_MakeFace(topo_wire, True)
for inner_wire in inner_wires:
if not inner_wire.is_closed():
if not inner_wire.is_closed:
raise ValueError("Cannot build face(s): inner wire is not closed")
face_builder.Add(inner_wire.wrapped)
@ -5778,7 +5785,7 @@ class Shell(Shape):
return Vector(properties.CentreOfMass())
class Solid(Shape, Mixin3D):
class Solid(Mixin3D, Shape):
"""a single solid"""
_dim = 3
@ -6225,11 +6232,11 @@ class Solid(Shape, Mixin3D):
# and exclude the planar faces normal to the direction of extrusion and these
# will have no volume when extruded
faces = []
for f in section.project_to_shape(target_object, direction):
if isinstance(f, Face):
faces.append(f)
for face in section.project_to_shape(target_object, direction):
if isinstance(face, Face):
faces.append(face)
else:
faces += f.faces()
faces += face.faces()
clip_faces = [
f
@ -6464,7 +6471,7 @@ class Vertex(Shape):
"""Default Vertext at the origin"""
@overload
def __init__(self, obj: TopoDS_Vertex): # pragma: no cover
def __init__(self, v: TopoDS_Vertex): # pragma: no cover
"""Vertex from OCCT TopoDS_Vertex object"""
@overload
@ -6472,36 +6479,51 @@ class Vertex(Shape):
"""Vertex from three float values"""
@overload
def __init__(self, values: Iterable[float]):
def __init__(self, v: Iterable[float]):
"""Vertex from Vector or other iterators"""
@overload
def __init__(self, values: tuple[float]):
def __init__(self, v: tuple[float]):
"""Vertex from tuple of floats"""
def __init__(self, *args):
def __init__(self, *args, **kwargs):
self.vertex_index = 0
if len(args) == 0:
self.wrapped = downcast(
BRepBuilderAPI_MakeVertex(gp_Pnt(0.0, 0.0, 0.0)).Vertex()
)
elif len(args) == 1 and isinstance(args[0], TopoDS_Vertex):
self.wrapped = args[0]
elif len(args) == 1 and isinstance(args[0], (Iterable, tuple)):
values = [float(value) for value in args[0]]
if len(values) < 3:
values += [0.0] * (3 - len(values))
self.wrapped = downcast(BRepBuilderAPI_MakeVertex(gp_Pnt(*values)).Vertex())
elif len(args) == 3 and all(isinstance(v, (int, float)) for v in args):
self.wrapped = downcast(
BRepBuilderAPI_MakeVertex(gp_Pnt(args[0], args[1], args[2])).Vertex()
)
else:
raise ValueError(
"Invalid Vertex - expected three floats or OCC TopoDS_Vertex"
)
x, y, z, ocp_vx = 0, 0, 0, None
unknown_args = ", ".join(set(kwargs.keys()).difference(["v", "X", "Y", "Z"]))
if unknown_args:
raise ValueError(f"Unexpected argument(s) {unknown_args}")
if args and all(isinstance(args[i], (int, float)) for i in range(len(args))):
values = list(args)
values += [0.0] * max(0, (3 - len(args)))
x, y, z = values[0:3]
elif len(args) == 1 or "v" in kwargs:
first_arg = args[0] if args else None
first_arg = kwargs.get("v", first_arg) # override with kwarg
if isinstance(first_arg, (tuple, Iterable)):
try:
values = [float(value) for value in first_arg]
except (TypeError, ValueError) as exc:
raise TypeError("Expected floats") from exc
if len(values) < 3:
values += [0.0] * (3 - len(values))
x, y, z = values
elif isinstance(first_arg, TopoDS_Vertex):
ocp_vx = first_arg
else:
raise TypeError("Expected floats, TopoDS_Vertex, or iterable")
x = kwargs.get("X", x)
y = kwargs.get("Y", y)
z = kwargs.get("Z", z)
ocp_vx = (
downcast(BRepBuilderAPI_MakeVertex(gp_Pnt(x, y, z)).Vertex())
if ocp_vx is None
else ocp_vx
)
super().__init__(ocp_vx)
self.X, self.Y, self.Z = self.to_tuple()
super().__init__(self.wrapped)
def to_tuple(self) -> tuple[float, float, float]:
"""Return vertex as three tuple of floats"""
@ -6510,7 +6532,7 @@ class Vertex(Shape):
def center(self) -> Vector:
"""The center of a vertex is itself!"""
return Vector(self.to_tuple())
return Vector(self)
def __add__(
self, other: Union[Vertex, Vector, Tuple[float, float, float]]
@ -6611,7 +6633,7 @@ class Vertex(Shape):
return value
class Wire(Shape, Mixin1D):
class Wire(Mixin1D, Shape):
"""A series of connected, ordered edges, that typically bounds a Face"""
_dim = 1
@ -6623,7 +6645,7 @@ class Wire(Shape, Mixin1D):
def close(self) -> Wire:
"""Close a Wire"""
if not self.is_closed():
if not self.is_closed:
edge = Edge.make_line(self.end_point(), self.start_point())
return_value = Wire.combine((self, edge))[0]
else:
@ -6682,7 +6704,7 @@ class Wire(Shape, Mixin1D):
def param_at_point(self, point: VectorLike) -> float:
"""Parameter at point on Wire"""
# OCP doesn't support this so this algoritm finds the edge that contains the
# OCP doesn't support this so this algorithm finds the edge that contains the
# point, finds the u value/fractional distance of the point on that edge and
# sums up the length of the edges from the start to the edge with the point.
@ -6714,8 +6736,7 @@ class Wire(Shape, Mixin1D):
else:
distance += u_value * edge.length
break
else:
distance += edge.length
distance += edge.length
if not found:
raise ValueError(f"{point} not on wire")
@ -6758,25 +6779,25 @@ class Wire(Shape, Mixin1D):
# Trim edges containing start or end points
degenerate = False
if contains_start:
u = edge.param_at_point(trim_start_point)
u_value = edge.param_at_point(trim_start_point)
if not flipped:
degenerate = u == 1.0
degenerate = u_value == 1.0
if not degenerate:
edge = edge.trim(u, 1.0)
edge = edge.trim(u_value, 1.0)
elif flipped:
degenerate = u == 0.0
degenerate = u_value == 0.0
if not degenerate:
edge = edge.trim(0.0, u)
edge = edge.trim(0.0, u_value)
if contains_end:
u = edge.param_at_point(trim_end_point)
u_value = edge.param_at_point(trim_end_point)
if not flipped:
degenerate = u == 0.0
degenerate = u_value == 0.0
if not degenerate:
edge = edge.trim(0.0, u)
edge = edge.trim(0.0, u_value)
elif flipped:
degenerate = u == 1.0
degenerate = u_value == 1.0
if not degenerate:
edge = edge.trim(u, 1.0)
edge = edge.trim(u_value, 1.0)
if not degenerate:
if contains_start or contains_end:
modified_edges.append(edge)
@ -6785,7 +6806,7 @@ class Wire(Shape, Mixin1D):
# Select the wire containing the start and end points
wire_segments = edges_to_wires(modified_edges + original_edges)
trimed_wire = filter(
trimmed_wire = filter(
lambda w: all(
[
w.distance_to(p) <= TOLERANCE
@ -6794,9 +6815,9 @@ class Wire(Shape, Mixin1D):
),
wire_segments,
)
if not trimed_wire:
if not trimmed_wire:
raise RuntimeError("Invalid trim result")
return next(trimed_wire)
return next(trimmed_wire)
def order_edges(self) -> ShapeList[Edge]:
"""Return the edges in self ordered by wire direction and orientation"""
@ -6993,7 +7014,7 @@ class Wire(Shape, Mixin1D):
distance (float): chamfer length
distance2 (float): chamfer length
vertices (Iterable[Vertex]): vertices to chamfer
edge (Edge): identifies the side where length is measured. The virtices must be
edge (Edge): identifies the side where length is measured. The vertices must be
part of the edge
Returns:
@ -7282,7 +7303,7 @@ class Joint(ABC):
self.connected_to = other
@abstractmethod
def connect_to(self, other: Joint, **kwags):
def connect_to(self, other: Joint, **kwargs):
"""All derived classes must provide a connect_to method"""
raise NotImplementedError
@ -7359,9 +7380,10 @@ def shapetype(obj: TopoDS_Shape) -> TopAbs_ShapeEnum:
return obj.ShapeType()
def unwrapped_shapetype(obj: Shape):
def unwrapped_shapetype(obj: Shape) -> TopAbs_ShapeEnum:
"""Return Shape's TopAbs_ShapeEnum"""
if isinstance(obj, Compound):
shapetypes = set([shapetype(o.wrapped) for o in obj])
shapetypes = set(shapetype(o.wrapped) for o in obj)
if len(shapetypes) == 1:
result = shapetypes.pop()
else:
@ -7422,8 +7444,8 @@ def polar(length: float, angle: float) -> tuple[float, float]:
def delta(shapes_one: Iterable[Shape], shapes_two: Iterable[Shape]) -> list[Shape]:
"""Compare the OCCT objects of each list and return the differences"""
occt_one = set([shape.wrapped for shape in shapes_one])
occt_two = set([shape.wrapped for shape in shapes_two])
occt_one = set(shape.wrapped for shape in shapes_one)
occt_two = set(shape.wrapped for shape in shapes_two)
occt_delta = list(occt_one - occt_two)
all_shapes = []
@ -7462,10 +7484,10 @@ def new_edges(*objects: Shape, combined: Shape) -> ShapeList[Edge]:
operation.SetRunParallel(True)
operation.Build()
new_edges = Shape.cast(operation.Shape()).edges()
for edge in new_edges:
edges = Shape.cast(operation.Shape()).edges()
for edge in edges:
edge.topo_parent = combined
return ShapeList(new_edges)
return ShapeList(edges)
class SkipClean:
@ -7529,10 +7551,11 @@ def _axis_intersect(self: Axis, *to_intersect: Union[Shape, Axis, Plane]) -> Sha
if isinstance(intersector, Shape):
intersections.extend(self_i_edge.intersect(intersector))
if len(intersections) == 1:
return intersections[0]
else:
return Compound(children=intersections)
return (
intersections[0]
if len(intersections) == 1
else Compound(children=intersections)
)
Axis.intersect = _axis_intersect

View file

@ -1,15 +1,18 @@
"""
Export a version string.
"""
try:
try:
from ._dev.scm_version import version
from ._dev.scm_version import version # pylint: disable=unused-import
except ImportError:
from ._version import version
except Exception:
except Exception: # pylint: disable=broad-exception-caught
import warnings
warnings.warn(
f'could not determine {__name__.split(".")[0]} package version; '
f'could not determine {__name__.split(".", maxsplit=1)[0]} package version; '
"this indicates a broken installation"
)
del warnings
version = "0.0.0"
version = "0.0.0" # pylint: disable=invalid-name

View file

@ -512,7 +512,7 @@ class AlgebraTests(unittest.TestCase):
l2 = Line((1, 1), (0, 0))
l = l1 + l2
w = Wire.make_wire(l)
self.assertTrue(w.is_closed())
self.assertTrue(w.is_closed)
self.assertTupleAlmostEquals(
w.center(CenterOf.MASS), (0.6464466094067263, 0.35355339059327373, 0.0), 6
)
@ -626,7 +626,7 @@ class LocationTests(unittest.TestCase):
def test_wheels(self):
plane = Plane.ZX
rotations = [Rot(y=a) for a in (0, 45, 90, 135)]
rotations = [Rot(Y=a) for a in (0, 45, 90, 135)]
s = Sketch()
for i, outer_loc in enumerate(GridLocations(3, 3, 2, 2)):

View file

@ -169,7 +169,7 @@ class BuildLineTests(unittest.TestCase):
with BuildLine() as l:
l1 = JernArc(start=(0, 0, 0), tangent=(1, 0, 0), radius=1, arc_size=360)
self.assertTrue(l1.is_closed())
self.assertTrue(l1.is_closed)
circle_face = Face.make_from_wires(l1)
self.assertAlmostEqual(circle_face.area, pi, 5)
self.assertTupleAlmostEquals(circle_face.center().to_tuple(), (0, 1, 0), 5)

View file

@ -55,6 +55,7 @@ from build123d.geometry import (
Location,
Matrix,
Pos,
Rot,
Rotation,
Vector,
VectorLike,
@ -438,7 +439,7 @@ class TestCadObjects(DirectApiTestCase):
tangent_arc = Edge.make_tangent_arc(
Vector(1, 1), # starts at 1, 1
Vector(0, 1), # tangent at start of arc is in the +y direction
Vector(2, 1), # arc cureturn_valuees 180 degrees and ends at 2, 1
Vector(2, 1), # arc cureturn_values 180 degrees and ends at 2, 1
)
self.assertVectorAlmostEquals(tangent_arc.start_point(), (1, 1, 0), 3)
self.assertVectorAlmostEquals(tangent_arc.end_point(), (2, 1, 0), 3)
@ -1521,12 +1522,12 @@ class TestLocation(DirectApiTestCase):
def test_eq(self):
loc = Location((1, 2, 3), (4, 5, 6))
diff_posistion = Location((10, 20, 30), (4, 5, 6))
diff_position = Location((10, 20, 30), (4, 5, 6))
diff_orientation = Location((1, 2, 3), (40, 50, 60))
same = Location((1, 2, 3), (4, 5, 6))
self.assertEqual(loc, same)
self.assertNotEqual(loc, diff_posistion)
self.assertNotEqual(loc, diff_position)
self.assertNotEqual(loc, diff_orientation)
def test_neg(self):
@ -1647,11 +1648,11 @@ class TestMatrix(DirectApiTestCase):
mz.rotate(Axis.Z, 30 * DEG2RAD)
matrix_almost_equal(mz, m_rotate_z_30)
# Test matrix multipy vector
# Test matrix multiply vector
v = Vector(1, 0, 0)
self.assertVectorAlmostEquals(mz.multiply(v), (root_3_over_2, 1 / 2, 0), 7)
# Test matrix multipy matrix
# Test matrix multiply matrix
m_rotate_xy_30 = [
[root_3_over_2, 0, 1 / 2, 0],
[1 / 4, root_3_over_2, -root_3_over_2 / 2, 0],
@ -1816,7 +1817,7 @@ class TestMixin1D(DirectApiTestCase):
corner = base_wire.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[-1]
base_wire = base_wire.fillet_2d(0.4, [corner])
offset_wire = base_wire.offset_2d(0.1, side=Side.LEFT)
self.assertTrue(offset_wire.is_closed())
self.assertTrue(offset_wire.is_closed)
self.assertEqual(len(offset_wire.edges().filter_by(GeomType.LINE)), 6)
self.assertEqual(len(offset_wire.edges().filter_by(GeomType.CIRCLE)), 2)
offset_wire_right = base_wire.offset_2d(0.1, side=Side.RIGHT)
@ -2034,7 +2035,7 @@ class TestPlane(DirectApiTestCase):
with self.assertRaises(TypeError):
Plane()
with self.assertRaises(TypeError):
Plane(o, z_dir=1)
Plane(o, z_dir="up")
# rotated location around z
loc = Location((0, 0, 0), (0, 0, 45))
@ -2414,6 +2415,16 @@ class TestProjection(DirectApiTestCase):
Edge.make_circle(1, end_angle=30).to_axis()
class TestRotation(DirectApiTestCase):
def test_rotation_parameters(self):
r = Rotation(10, 20, 30)
self.assertVectorAlmostEquals(r.orientation, (10, 20, 30), 5)
r = Rotation(10, 20, Z=30)
self.assertVectorAlmostEquals(r.orientation, (10, 20, 30), 5)
with self.assertRaises(TypeError):
Rotation(x=10)
class TestShape(DirectApiTestCase):
"""Misc Shape tests"""
@ -2617,7 +2628,7 @@ class TestShape(DirectApiTestCase):
self.assertEqual(len(visible), 6)
self.assertEqual(len(hidden), 2)
# Hidden coutour edges
# Hidden contour edges
hole = box - cyl
visible, hidden = hole.project_to_viewport((-20, 20, 20))
self.assertEqual(len(visible), 13)
@ -3074,15 +3085,18 @@ class TestVector(DirectApiTestCase):
v3 = Vector(gp_Vec(1, 2, 3))
v4 = Vector([1, 2, 3])
v5 = Vector(gp_XYZ(1, 2, 3))
v5b = Vector(X=1, Y=2, Z=3)
v5c = Vector(v=gp_XYZ(1, 2, 3))
for v in [v1, v2, v3, v4, v5]:
for v in [v1, v2, v3, v4, v5, v5b, v5c]:
self.assertVectorAlmostEquals(v, (1, 2, 3), 4)
v6 = Vector((1, 2))
v7 = Vector([1, 2])
v8 = Vector(1, 2)
v8b = Vector(X=1, Y=2)
for v in [v6, v7, v8]:
for v in [v6, v7, v8, v8b]:
self.assertVectorAlmostEquals(v, (1, 2, 0), 4)
v9 = Vector()
@ -3092,11 +3106,19 @@ class TestVector(DirectApiTestCase):
v9.Y = 2.0
v9.Z = 3.0
self.assertVectorAlmostEquals(v9, (1, 2, 3), 4)
self.assertVectorAlmostEquals(Vector(1, 2, 3, 4), (1, 2, 3), 4)
v10 = Vector(1)
v11 = Vector((1,))
v12 = Vector([1])
v13 = Vector(X=1)
for v in [v10, v11, v12]:
self.assertVectorAlmostEquals(v, (1, 0, 0), 4)
with self.assertRaises(TypeError):
Vector("vector")
with self.assertRaises(TypeError):
Vector(1, 2, 3, 4)
with self.assertRaises(ValueError):
Vector(x=1)
def test_vector_rotate(self):
"""Validate vector rotate methods"""
@ -3318,8 +3340,12 @@ class TestVertex(DirectApiTestCase):
self.assertVectorAlmostEquals(Vector(Vertex(0, 0, 0)), (0.0, 0.0, 0.0), 7)
def test_vertex_init_error(self):
with self.assertRaises(TypeError):
Vertex(Axis.Z)
with self.assertRaises(ValueError):
Vertex(0.0, 1.0)
Vertex(x=1)
with self.assertRaises(TypeError):
Vertex((Axis.X, Axis.Y, Axis.Z))
def test_no_intersect(self):
with self.assertRaises(NotImplementedError):

View file

@ -297,9 +297,8 @@ class ExtensionLineTestCase(unittest.TestCase):
class TestTechnicalDrawing(unittest.TestCase):
def test_basic_drawing(self):
with BuildSketch() as drawing:
TechnicalDrawing(design_date=date(2023, 9, 17), sheet_number=1)
bbox = drawing.sketch.bounding_box()
drawing = TechnicalDrawing(design_date=date(2023, 9, 17), sheet_number=1)
bbox = drawing.bounding_box()
self.assertGreater(bbox.size.X, 280)
self.assertGreater(bbox.size.Y, 195)
self.assertGreater(len(drawing.faces()), 110)

58
tests/test_pack.py Normal file
View file

@ -0,0 +1,58 @@
"""
build123d Pack tests
name: build_pack.py
by: fischman
date: November 9th 2023
desc: Unit tests for the build123d pack module
"""
import operator
import random
import unittest
from functools import reduce
from build123d import *
class TestPack(unittest.TestCase):
"""Tests for the pack helper."""
def test_simple(self):
"""Test pack with hand-picked data against expected output."""
packed = pack([Box(10, 2, 1), Box(1, 5, 1), Box(1, 5, 1)], padding=1)
self.assertEqual(
# Nothing magically interesting here, and other packings
# would also be fine, but this shows that padding is
# working, as is the preference towards square(ish)
# output.
"[bbox: 0.0 <= x <= 10.0, 0.0 <= y <= 2.0, -0.5 <= z <= 0.5,"
" bbox: 0.0 <= x <= 1.0, 3.0 <= y <= 8.0, -0.5 <= z <= 0.5,"
" bbox: 2.0 <= x <= 3.0, 3.0 <= y <= 8.0, -0.5 <= z <= 0.5]",
str([p.bounding_box() for p in packed]))
def test_random_boxes(self):
"""Test pack with larger (and randomized) inputs."""
random.seed(123456)
# 50 is an arbitrary number that is large enough to exercise
# different aspects of the packer while still completing quickly.
test_boxes = [Box(random.randint(1, 20), random.randint(1, 20), 1)
for _ in range(50)]
# Not raising in this call shows successfull non-overlap.
packed = pack(test_boxes, 1)
self.assertEqual(
"bbox: 0.0 <= x <= 94.0, 0.0 <= y <= 86.0, -0.5 <= z <= 0.5",
str(reduce(operator.add, packed, Part()).bounding_box()))
def test_random_slots(self):
"""Test pack for 2D objects."""
random.seed(123456)
# 50 is an arbitrary number that is large enough to exercise
# different aspects of the packer while still completing quickly.
inputs = [SlotOverall(random.randint(1,20), random.randint(1,20)) for _ in range(50)]
# Not raising in this call shows successfull non-overlap.
packed = pack(inputs, 1)
self.assertEqual(
"bbox: 0.0 <= x <= 124.0, 0.0 <= y <= 105.0, 0.0 <= z <= 0.0",
str(reduce(operator.add, packed, Sketch()).bounding_box()))
if __name__ == "__main__":
unittest.main()

View file

@ -1,4 +1,4 @@
# write me the test for testing the persistence module with unnitest
# write me the test for testing the persistence module with unittest
import unittest
from build123d.persistence import (