mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Merge branch 'gumyr:dev' into deglob_write
This commit is contained in:
commit
2d2a89bddf
64 changed files with 7427 additions and 1000 deletions
BIN
docs/assets/examples/bicycle_tire.png
Normal file
BIN
docs/assets/examples/bicycle_tire.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 250 KiB |
BIN
docs/assets/examples/cast_bearing_unit.png
Normal file
BIN
docs/assets/examples/cast_bearing_unit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/assets/examples/fast_grid_holes.png
Normal file
BIN
docs/assets/examples/fast_grid_holes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
771
docs/assets/stepper_drawing.svg
Normal file
771
docs/assets/stepper_drawing.svg
Normal file
|
|
@ -0,0 +1,771 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<svg width="287.09mm" height="200.730007mm" viewBox="-143.545 -101.454993 287.09 200.730007" 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.09" id="Visible">
|
||||||
|
<line x1="88.539518" y1="76.052448" x2="113.571098" y2="61.600459" />
|
||||||
|
<path d="M 88.539518,76.052448 A 3.4499999999999997,1.991858428704209 0.0 0,1 83.660482,76.052448" />
|
||||||
|
<path d="M 113.571098,58.783545 A 3.4499999999999997,1.991858428704209 0.0 0,1 114.58158,60.192002" />
|
||||||
|
<path d="M 114.58158,60.192002 A 3.4499999999999997,1.991858428704209 0.0 0,1 113.571098,61.600459" />
|
||||||
|
<line x1="83.660482" y1="76.052448" x2="58.628902" y2="61.600459" />
|
||||||
|
<line x1="88.539518" y1="44.331556" x2="113.571098" y2="58.783545" />
|
||||||
|
<path d="M 58.628902,61.600459 A 3.4499999999999997,1.991858428704209 0.0 0,1 57.61842,60.192002" />
|
||||||
|
<path d="M 57.61842,60.192002 A 3.4499999999999997,1.991858428704209 0.0 0,1 58.628902,58.783545" />
|
||||||
|
<path d="M 83.660482,44.331556 A 3.4499999999999997,1.991858428704209 0.0 0,1 88.539518,44.331556" />
|
||||||
|
<line x1="58.628902" y1="58.783545" x2="83.660482" y2="44.331556" />
|
||||||
|
<path d="M 87.542592,73.900687 A 1.9124999999999999,1.1041823898251593 0.0 0,1 88.0125,74.62562" />
|
||||||
|
<path d="M 88.0125,74.62562 A 1.9125000000000012,1.10418238982516 0.0 0,1 84.1875,74.62562" />
|
||||||
|
<path d="M 84.1875,74.62562 A 1.9124999999999999,1.1041823898251593 0.0 0,1 84.657408,73.900687" />
|
||||||
|
<path d="M 59.747898,59.411227 A 1.9124999999999999,1.1041823898251593 0.0 0,1 63.01274,60.192002" />
|
||||||
|
<path d="M 63.01274,60.192002 A 1.9125000000000012,1.10418238982516 0.0 0,1 59.18774,60.192002" />
|
||||||
|
<path d="M 59.18774,60.192002 A 1.9124999999999999,1.1041823898251593 0.0 0,1 59.747898,59.411227" />
|
||||||
|
<path d="M 109.747419,59.411227 A 1.9124999999999999,1.1041823898251593 0.0 0,1 113.01226,60.192002" />
|
||||||
|
<path d="M 113.01226,60.192002 A 1.9125000000000012,1.10418238982516 0.0 0,1 109.18726,60.192002" />
|
||||||
|
<path d="M 109.18726,60.192002 A 1.9124999999999999,1.1041823898251593 0.0 0,1 109.747419,59.411227" />
|
||||||
|
<path d="M 75.997212,54.359155 A 14.287500000000001,8.24889197104678 0.0 0,1 100.3875,60.192002" />
|
||||||
|
<path d="M 71.8125,60.192002 A 14.287500000000001,8.24889197104678 0.0 0,1 75.997212,54.359155" />
|
||||||
|
<path d="M 84.747658,44.977609 A 1.9124999999999999,1.1041823898251593 0.0 0,1 88.0125,45.758384" />
|
||||||
|
<path d="M 88.0125,45.758384 A 1.9125000000000012,1.10418238982516 0.0 0,1 84.1875,45.758384" />
|
||||||
|
<path d="M 84.1875,45.758384 A 1.9124999999999999,1.1041823898251593 0.0 0,1 84.747658,44.977609" />
|
||||||
|
<path d="M 79.667096,39.901041 A 1.875,1.0825317547305484 0.0 0,1 82.318746,39.901041" />
|
||||||
|
<line x1="62.622287" y1="49.741866" x2="79.667096" y2="39.901041" />
|
||||||
|
<line x1="82.318746" y1="39.901041" x2="83.44835" y2="40.553218" />
|
||||||
|
<path d="M 62.622287,49.741866 A 1.875,1.0825317547305484 180.0 0,0 62.073112,50.507332" />
|
||||||
|
<path d="M 83.44835,40.553218 A 3.75,2.165063509461097 180.0 0,0 84.600538,41.006736" />
|
||||||
|
<path d="M 87.599462,41.006736 A 3.75,2.165063509461097 180.0 0,0 88.75165,40.553218" />
|
||||||
|
<line x1="89.881254" y1="39.901041" x2="88.75165" y2="40.553218" />
|
||||||
|
<path d="M 92.532904,39.901041 A 1.875,1.0825317547305484 180.0 0,0 89.881254,39.901041" />
|
||||||
|
<line x1="92.532904" y1="39.901041" x2="109.577713" y2="49.741866" />
|
||||||
|
<path d="M 109.577713,49.741866 A 1.875,1.0825317547305484 0.0 0,1 110.126888,50.507332" />
|
||||||
|
<path d="M 113.571098,55.844158 A 3.4499999999999997,1.991858428704209 0.0 0,1 114.58158,57.252614" />
|
||||||
|
<line x1="113.571098" y1="55.844158" x2="109.577713" y2="53.538575" />
|
||||||
|
<line x1="113.571098" y1="58.783545" x2="113.571098" y2="55.844158" />
|
||||||
|
<line x1="88.539518" y1="44.331556" x2="88.539518" y2="41.392168" />
|
||||||
|
<line x1="92.532904" y1="43.69775" x2="88.539518" y2="41.392168" />
|
||||||
|
<line x1="92.532904" y1="43.69775" x2="92.532904" y2="39.901041" />
|
||||||
|
<line x1="109.577713" y1="53.538575" x2="109.577713" y2="49.741866" />
|
||||||
|
<line x1="58.628902" y1="58.783545" x2="58.628902" y2="55.844158" />
|
||||||
|
<path d="M 57.61842,57.252614 A 3.4499999999999997,1.991858428704209 0.0 0,1 58.628902,55.844158" />
|
||||||
|
<line x1="83.660482" y1="44.331556" x2="83.660482" y2="41.392168" />
|
||||||
|
<path d="M 83.660482,41.392168 A 3.4499999999999997,1.991858428704209 0.0 0,1 88.539518,41.392168" />
|
||||||
|
<line x1="58.628902" y1="55.844158" x2="62.622287" y2="53.538575" />
|
||||||
|
<line x1="62.622287" y1="53.538575" x2="62.622287" y2="49.741866" />
|
||||||
|
<line x1="79.667096" y1="43.69775" x2="79.667096" y2="39.901041" />
|
||||||
|
<line x1="79.667096" y1="43.69775" x2="83.660482" y2="41.392168" />
|
||||||
|
<path d="M 75.997212,55.33895 A 14.287500000000001,8.24889197104678 0.0 0,1 100.3875,61.171798" />
|
||||||
|
<path d="M 100.3875,61.171798 A 14.287500000000001,8.24889197104678 0.0 0,1 88.48125,69.305315" />
|
||||||
|
<path d="M 83.71875,69.305315 A 14.287500000000001,8.24889197104678 0.0 0,1 71.8125,61.171798" />
|
||||||
|
<path d="M 71.8125,61.171798 A 14.287500000000001,8.24889197104678 0.0 0,1 75.997212,55.33895" />
|
||||||
|
<path d="M 79.720129,19.110997 A 1.7999999999999998,1.0392304845413265 0.0 0,1 82.265713,19.110997" />
|
||||||
|
<line x1="82.265713" y1="19.110997" x2="83.395317" y2="19.763174" />
|
||||||
|
<path d="M 83.395317,19.763174 A 3.8249999999999997,2.2083647796503185 180.0 0,0 88.804683,19.763174" />
|
||||||
|
<line x1="89.934287" y1="19.110997" x2="88.804683" y2="19.763174" />
|
||||||
|
<path d="M 92.479871,19.110997 A 1.7999999999999998,1.0392304845413263 -180.0 0,0 89.934287,19.110997" />
|
||||||
|
<line x1="92.479871" y1="19.110997" x2="109.52468" y2="28.951822" />
|
||||||
|
<path d="M 109.52468,28.951822 A 1.7999999999999998,1.0392304845413263 -3.895368034302951e-15 0,1 110.051888,29.686669" />
|
||||||
|
<path d="M 62.67532,28.951822 A 1.7999999999999998,1.0392304845413265 180.0 0,0 62.148112,29.686669" />
|
||||||
|
<line x1="62.67532" y1="28.951822" x2="79.720129" y2="19.110997" />
|
||||||
|
<path d="M 79.667096,19.080379 A 1.875,1.0825317547305484 0.0 0,1 82.318746,19.080379" />
|
||||||
|
<line x1="62.622287" y1="28.921204" x2="79.667096" y2="19.080379" />
|
||||||
|
<line x1="82.318746" y1="19.080379" x2="83.44835" y2="19.732555" />
|
||||||
|
<path d="M 62.622287,28.921204 A 1.875,1.0825317547305484 180.0 0,0 62.073112,29.686669" />
|
||||||
|
<path d="M 62.073112,29.686669 A 1.875,1.0825317547305484 180.0 0,0 62.148112,29.989778" />
|
||||||
|
<path d="M 83.44835,19.732555 A 3.75,2.165063509461097 180.0 0,0 88.75165,19.732555" />
|
||||||
|
<line x1="89.881254" y1="19.080379" x2="88.75165" y2="19.732555" />
|
||||||
|
<path d="M 92.532904,19.080379 A 1.875,1.0825317547305484 180.0 0,0 89.881254,19.080379" />
|
||||||
|
<line x1="92.532904" y1="19.080379" x2="109.577713" y2="28.921204" />
|
||||||
|
<path d="M 62.148112,35.886925 A 1.875,1.0825317547305484 0.0 0,1 62.073112,35.583816" />
|
||||||
|
<path d="M 62.073112,35.583816 A 1.875,1.0825317547305484 0.0 0,1 62.148112,35.280707" />
|
||||||
|
<path d="M 109.577713,28.921204 A 1.875,1.0825317547305484 0.0 0,1 110.126888,29.686669" />
|
||||||
|
<path d="M 110.126888,29.686669 A 1.875,1.0825317547305484 0.0 0,1 110.051888,29.989778" />
|
||||||
|
<path d="M 110.051888,35.886925 A 1.875,1.0825317547305484 180.0 0,0 110.126888,35.583816" />
|
||||||
|
<path d="M 110.126888,35.583816 A 1.875,1.0825317547305484 180.0 0,0 110.051888,35.280707" />
|
||||||
|
<path d="M 79.667096,12.344282 A 1.875,1.0825317547305484 0.0 0,1 82.318746,12.344282" />
|
||||||
|
<line x1="62.622287" y1="22.185107" x2="79.667096" y2="12.344282" />
|
||||||
|
<line x1="82.318746" y1="12.344282" x2="83.44835" y2="12.996458" />
|
||||||
|
<path d="M 62.622287,22.185107 A 1.875,1.0825317547305484 180.0 0,0 62.073112,22.950572" />
|
||||||
|
<path d="M 83.44835,12.996458 A 3.75,2.165063509461097 180.0 0,0 88.75165,12.996458" />
|
||||||
|
<line x1="89.881254" y1="12.344282" x2="88.75165" y2="12.996458" />
|
||||||
|
<path d="M 92.532904,12.344282 A 1.875,1.0825317547305484 180.0 0,0 89.881254,12.344282" />
|
||||||
|
<line x1="92.532904" y1="12.344282" x2="109.577713" y2="22.185107" />
|
||||||
|
<path d="M 109.577713,22.185107 A 1.875,1.0825317547305484 0.0 0,1 110.126888,22.950572" />
|
||||||
|
<path d="M 84.416202,62.649146 A 2.3812499999999996,1.3748153285077964 0.0 0,1 86.611629,62.278581" />
|
||||||
|
<path d="M 86.611629,62.278581 A 2.3812499999999996,1.3748153285077964 0.0 0,1 88.425637,63.325899" />
|
||||||
|
<line x1="88.425637" y1="63.325899" x2="88.425637" y2="72.511485" />
|
||||||
|
<path d="M 88.425637,72.511485 A 2.3812499999999996,1.3748153285077964 0.0 0,1 88.48125,72.806874" />
|
||||||
|
<path d="M 88.48125,72.806874 A 2.3812500000000014,1.3748153285077973 0.0 1,1 83.71875,72.806874" />
|
||||||
|
<path d="M 83.71875,72.806874 A 2.3812499999999996,1.3748153285077964 0.0 0,1 86.611629,71.464167" />
|
||||||
|
<line x1="86.611629" y1="62.278581" x2="86.611629" y2="71.464167" />
|
||||||
|
<line x1="86.611629" y1="62.278581" x2="88.425637" y2="63.325899" />
|
||||||
|
<line x1="88.425637" y1="72.511485" x2="86.611629" y2="71.464167" />
|
||||||
|
<line x1="82.318746" y1="42.166819" x2="82.318746" y2="39.901041" />
|
||||||
|
<line x1="83.44835" y1="41.514643" x2="83.44835" y2="40.553218" />
|
||||||
|
<line x1="88.75165" y1="41.514643" x2="88.75165" y2="40.553218" />
|
||||||
|
<line x1="89.881254" y1="42.166819" x2="89.881254" y2="39.901041" />
|
||||||
|
<line x1="109.577713" y1="53.538575" x2="109.577713" y2="51.272797" />
|
||||||
|
<line x1="82.265713" y1="39.871601" x2="82.265713" y2="19.110997" />
|
||||||
|
<line x1="79.720129" y1="39.871601" x2="79.720129" y2="19.110997" />
|
||||||
|
<line x1="83.395317" y1="40.522599" x2="83.395317" y2="19.763174" />
|
||||||
|
<line x1="88.804683" y1="40.522599" x2="88.804683" y2="19.763174" />
|
||||||
|
<line x1="89.934287" y1="39.871601" x2="89.934287" y2="19.110997" />
|
||||||
|
<line x1="92.479871" y1="39.871601" x2="92.479871" y2="19.110997" />
|
||||||
|
<line x1="109.52468" y1="49.711248" x2="109.52468" y2="28.951822" />
|
||||||
|
<line x1="62.67532" y1="49.711248" x2="62.67532" y2="28.951822" />
|
||||||
|
<line x1="79.667096" y1="19.080379" x2="79.667096" y2="12.344282" />
|
||||||
|
<line x1="82.318746" y1="19.080379" x2="82.318746" y2="12.344282" />
|
||||||
|
<line x1="62.622287" y1="28.921204" x2="62.622287" y2="22.185107" />
|
||||||
|
<line x1="83.44835" y1="19.732555" x2="83.44835" y2="12.996458" />
|
||||||
|
<line x1="88.75165" y1="19.732555" x2="88.75165" y2="12.996458" />
|
||||||
|
<line x1="89.881254" y1="19.080379" x2="89.881254" y2="12.344282" />
|
||||||
|
<line x1="92.532904" y1="19.080379" x2="92.532904" y2="12.344282" />
|
||||||
|
<line x1="109.577713" y1="28.921204" x2="109.577713" y2="22.185107" />
|
||||||
|
<line x1="114.58158" y1="57.252614" x2="114.58158" y2="60.192002" />
|
||||||
|
<line x1="57.61842" y1="57.252614" x2="57.61842" y2="60.192002" />
|
||||||
|
<line x1="71.8125" y1="61.171798" x2="71.8125" y2="60.192002" />
|
||||||
|
<line x1="100.3875" y1="61.171798" x2="100.3875" y2="60.192002" />
|
||||||
|
<line x1="62.073112" y1="50.507332" x2="62.073112" y2="53.855642" />
|
||||||
|
<line x1="110.126888" y1="50.507332" x2="110.126888" y2="53.855642" />
|
||||||
|
<line x1="110.051888" y1="29.686669" x2="110.051888" y2="50.204223" />
|
||||||
|
<line x1="62.148112" y1="29.686669" x2="62.148112" y2="50.204223" />
|
||||||
|
<line x1="62.073112" y1="22.950572" x2="62.073112" y2="29.686669" />
|
||||||
|
<line x1="62.073112" y1="29.686669" x2="62.073112" y2="35.583816" />
|
||||||
|
<line x1="110.126888" y1="22.950572" x2="110.126888" y2="29.686669" />
|
||||||
|
<line x1="110.126888" y1="29.686669" x2="110.126888" y2="35.583816" />
|
||||||
|
<line x1="83.71875" y1="72.806874" x2="83.71875" y2="60.192002" />
|
||||||
|
<line x1="88.48125" y1="72.806874" x2="88.48125" y2="60.192002" />
|
||||||
|
<line x1="-114.3" y1="26.560002" x2="-114.3" y2="73.760002" />
|
||||||
|
<path d="M -114.3,26.560002 A 4.6,4.6 0.0 0,1 -109.7,21.960002" />
|
||||||
|
<path d="M -109.7,78.360002 A 4.6,4.6 0.0 0,1 -114.3,73.760002" />
|
||||||
|
<line x1="-109.7" y1="21.960002" x2="-62.5" y2="21.960002" />
|
||||||
|
<line x1="-62.5" y1="78.360002" x2="-109.7" y2="78.360002" />
|
||||||
|
<path d="M -62.5,21.960002 A 4.6,4.6 0.0 0,1 -57.9,26.560002" />
|
||||||
|
<path d="M -57.9,73.760002 A 4.6,4.6 0.0 0,1 -62.5,78.360002" />
|
||||||
|
<line x1="-57.9" y1="26.560002" x2="-57.9" y2="73.760002" />
|
||||||
|
<circle cx="-109.67" cy="26.590002" r="2.55" />
|
||||||
|
<circle cx="-62.53" cy="26.590002" r="2.55" />
|
||||||
|
<circle cx="-109.67" cy="73.730002" r="2.55" />
|
||||||
|
<circle cx="-62.53" cy="73.730002" r="2.55" />
|
||||||
|
<circle cx="-86.1" cy="50.160002" r="19.05" />
|
||||||
|
<path d="M -84.389737,52.835002 A 3.175,3.175 0.0 0,1 -87.810263,52.835002" />
|
||||||
|
<path d="M -87.810263,52.835002 A 3.175,3.175 122.59284353031869 1,1 -84.389737,52.835002" />
|
||||||
|
<line x1="-87.810263" y1="52.835002" x2="-84.389737" y2="52.835002" />
|
||||||
|
<line x1="-114.3" y1="-25.080001" x2="-114.3" y2="-29.880001" />
|
||||||
|
<line x1="-114.3" y1="-29.880001" x2="-114.3" y2="-36.080001" />
|
||||||
|
<path d="M -114.3,-25.080001 Q -114.285538,-25.080001 -114.271076,-25.080001 Q -114.227872,-25.080001 -114.184668,-25.080001 Q -114.113266,-25.080001 -114.041863,-25.080001 Q -113.94316,-25.080001 -113.844457,-25.080001 Q -113.719694,-25.080001 -113.594931,-25.080001 Q -113.445678,-25.080001 -113.296425,-25.080001 Q -113.124558,-25.080001 -112.952691,-25.080001 Q -112.760372,-25.080001 -112.568053,-25.080001 Q -112.3577,-25.080001 -112.147348,-25.080001 Q -111.921606,-25.080001 -111.695865,-25.080001 Q -111.457574,-25.080001 -111.219284,-25.080001 Q -110.97144,-25.080001 -110.723596,-25.080001 Q -110.216853,-25.080001 -109.7,-25.080001" />
|
||||||
|
<line x1="-109.7" y1="-25.080001" x2="-62.5" y2="-25.080001" />
|
||||||
|
<path d="M -62.5,-25.080001 Q -61.983147,-25.080001 -61.476404,-25.080001 Q -61.22856,-25.080001 -60.980716,-25.080001 Q -60.742426,-25.080001 -60.504135,-25.080001 Q -60.278394,-25.080001 -60.052652,-25.080001 Q -59.8423,-25.080001 -59.631947,-25.080001 Q -59.439628,-25.080001 -59.247309,-25.080001 Q -59.075442,-25.080001 -58.903575,-25.080001 Q -58.754322,-25.080001 -58.605069,-25.080001 Q -58.480306,-25.080001 -58.355543,-25.080001 Q -58.25684,-25.080001 -58.158137,-25.080001 Q -58.086734,-25.080001 -58.015332,-25.080001 Q -57.972128,-25.080001 -57.928924,-25.080001 Q -57.914462,-25.080001 -57.9,-25.080001" />
|
||||||
|
<line x1="-109.7" y1="-25.080001" x2="-109.7" y2="-29.880001" />
|
||||||
|
<path d="M -114.3,-29.880001 Q -114.285538,-29.880001 -114.271076,-29.880001 Q -114.227872,-29.880001 -114.184668,-29.880001 Q -114.113266,-29.880001 -114.041863,-29.880001 Q -113.94316,-29.880001 -113.844457,-29.880001 Q -113.719694,-29.880001 -113.594931,-29.880001 Q -113.445678,-29.880001 -113.296425,-29.880001 Q -113.124558,-29.880001 -112.952691,-29.880001 Q -112.760372,-29.880001 -112.568053,-29.880001 Q -112.3577,-29.880001 -112.147348,-29.880001 Q -111.921606,-29.880001 -111.695865,-29.880001 Q -111.457574,-29.880001 -111.219284,-29.880001 Q -110.97144,-29.880001 -110.723596,-29.880001 Q -110.216853,-29.880001 -109.7,-29.880001" />
|
||||||
|
<line x1="-109.7" y1="-29.880001" x2="-102.17" y2="-29.880001" />
|
||||||
|
<path d="M -114.3,-36.080001 Q -114.29214,-36.080001 -114.284281,-36.080001 Q -114.2608,-36.080001 -114.23732,-36.080001 Q -114.198514,-36.080001 -114.159708,-36.080001 Q -114.106065,-36.080001 -114.052422,-36.080001 Q -113.984616,-36.080001 -113.91681,-36.080001 Q -113.835695,-36.080001 -113.754579,-36.080001 Q -113.661173,-36.080001 -113.567767,-36.080001 Q -113.463246,-36.080001 -113.358725,-36.080001 Q -113.244402,-36.080001 -113.13008,-36.080001 Q -113.007395,-36.080001 -112.884709,-36.080001 Q -112.755204,-36.080001 -112.625698,-36.080001 Q -112.491,-36.080001 -112.356302,-36.080001 Q -112.080898,-36.080001 -111.8,-36.080001" />
|
||||||
|
<path d="M -57.9,-36.080001 Q -57.90786,-36.080001 -57.915719,-36.080001 Q -57.9392,-36.080001 -57.96268,-36.080001 Q -58.001486,-36.080001 -58.040292,-36.080001 Q -58.093935,-36.080001 -58.147578,-36.080001 Q -58.215384,-36.080001 -58.28319,-36.080001 Q -58.364305,-36.080001 -58.445421,-36.080001 Q -58.538827,-36.080001 -58.632233,-36.080001 Q -58.736754,-36.080001 -58.841275,-36.080001 Q -58.955598,-36.080001 -59.06992,-36.080001 Q -59.192605,-36.080001 -59.315291,-36.080001 Q -59.444796,-36.080001 -59.574302,-36.080001 Q -59.709,-36.080001 -59.843698,-36.080001 Q -60.119102,-36.080001 -60.4,-36.080001" />
|
||||||
|
<line x1="-60.4" y1="-36.080001" x2="-62.53" y2="-36.080001" />
|
||||||
|
<path d="M -67.53,-36.080001 Q -67.514281,-36.080001 -67.498561,-36.080001 Q -67.4516,-36.080001 -67.40464,-36.080001 Q -67.327028,-36.080001 -67.249417,-36.080001 Q -67.14213,-36.080001 -67.034844,-36.080001 Q -66.899233,-36.080001 -66.763621,-36.080001 Q -66.601389,-36.080001 -66.439157,-36.080001 Q -66.252346,-36.080001 -66.065534,-36.080001 Q -65.856491,-36.080001 -65.647449,-36.080001 Q -65.418805,-36.080001 -65.19016,-36.080001 Q -64.94479,-36.080001 -64.699419,-36.080001 Q -64.440407,-36.080001 -64.181395,-36.080001 Q -63.912,-36.080001 -63.642605,-36.080001 Q -63.091797,-36.080001 -62.53,-36.080001" />
|
||||||
|
<path d="M -70.03,-36.080001 Q -69.890044,-36.080001 -69.750089,-36.080001 Q -69.471736,-36.080001 -69.204302,-36.080001 Q -69.074796,-36.080001 -68.945291,-36.080001 Q -68.822605,-36.080001 -68.69992,-36.080001 Q -68.585598,-36.080001 -68.471275,-36.080001 Q -68.366754,-36.080001 -68.262233,-36.080001 Q -68.168827,-36.080001 -68.075421,-36.080001 Q -67.994305,-36.080001 -67.91319,-36.080001 Q -67.845384,-36.080001 -67.777578,-36.080001 Q -67.723935,-36.080001 -67.670292,-36.080001 Q -67.631486,-36.080001 -67.59268,-36.080001 Q -67.5692,-36.080001 -67.545719,-36.080001 Q -67.53786,-36.080001 -67.53,-36.080001" />
|
||||||
|
<line x1="-102.17" y1="-36.080001" x2="-70.03" y2="-36.080001" />
|
||||||
|
<path d="M -102.17,-36.080001 Q -102.309956,-36.080001 -102.449911,-36.080001 Q -102.728264,-36.080001 -102.995698,-36.080001 Q -103.125204,-36.080001 -103.254709,-36.080001 Q -103.377395,-36.080001 -103.50008,-36.080001 Q -103.614402,-36.080001 -103.728725,-36.080001 Q -103.833246,-36.080001 -103.937767,-36.080001 Q -104.031173,-36.080001 -104.124579,-36.080001 Q -104.205695,-36.080001 -104.28681,-36.080001 Q -104.354616,-36.080001 -104.422422,-36.080001 Q -104.476065,-36.080001 -104.529708,-36.080001 Q -104.568514,-36.080001 -104.60732,-36.080001 Q -104.6308,-36.080001 -104.654281,-36.080001 Q -104.66214,-36.080001 -104.67,-36.080001" />
|
||||||
|
<path d="M -109.67,-36.080001 L -109.110178,-36.080001 L -108.557395,-36.080001 L -108.018605,-36.080001 L -107.500581,-36.080001 L -107.00984,-36.080001 L -106.552551,-36.080001 L -106.134466,-36.080001 L -105.760843,-36.080001 L -105.436379,-36.080001 L -105.165156,-36.080001 L -104.950583,-36.080001 L -104.79536,-36.080001 L -104.701439,-36.080001 L -104.67,-36.080001" />
|
||||||
|
<line x1="-111.8" y1="-36.080001" x2="-109.67" y2="-36.080001" />
|
||||||
|
<line x1="-102.17" y1="-29.880001" x2="-102.17" y2="-36.080001" />
|
||||||
|
<line x1="-70.03" y1="-29.880001" x2="-70.03" y2="-36.080001" />
|
||||||
|
<line x1="-70.03" y1="-29.880001" x2="-62.5" y2="-29.880001" />
|
||||||
|
<line x1="-62.5" y1="-25.080001" x2="-62.5" y2="-29.880001" />
|
||||||
|
<line x1="-57.9" y1="-25.080001" x2="-57.9" y2="-29.880001" />
|
||||||
|
<path d="M -62.5,-29.880001 Q -61.983147,-29.880001 -61.476404,-29.880001 Q -61.22856,-29.880001 -60.980716,-29.880001 Q -60.742426,-29.880001 -60.504135,-29.880001 Q -60.278394,-29.880001 -60.052652,-29.880001 Q -59.8423,-29.880001 -59.631947,-29.880001 Q -59.439628,-29.880001 -59.247309,-29.880001 Q -59.075442,-29.880001 -58.903575,-29.880001 Q -58.754322,-29.880001 -58.605069,-29.880001 Q -58.480306,-29.880001 -58.355543,-29.880001 Q -58.25684,-29.880001 -58.158137,-29.880001 Q -58.086734,-29.880001 -58.015332,-29.880001 Q -57.972128,-29.880001 -57.928924,-29.880001 Q -57.914462,-29.880001 -57.9,-29.880001" />
|
||||||
|
<line x1="-57.9" y1="-29.880001" x2="-57.9" y2="-36.080001" />
|
||||||
|
<line x1="-67.05" y1="-25.080001" x2="-67.05" y2="-23.480001" />
|
||||||
|
<path d="M -105.15,-23.480001 L -104.672377,-23.480001 L -103.263457,-23.480001 L -100.99389,-23.480001 L -97.977481,-23.480001 L -94.365485,-23.480001 L -90.339024,-23.480001 L -86.1,-23.480001 L -81.860976,-23.480001 L -77.834515,-23.480001 L -74.222519,-23.480001 L -71.20611,-23.480001 L -68.936543,-23.480001 L -67.527623,-23.480001 L -67.05,-23.480001" />
|
||||||
|
<line x1="-104.67" y1="-29.880001" x2="-104.67" y2="-36.080001" />
|
||||||
|
<line x1="-67.53" y1="-29.880001" x2="-67.53" y2="-36.080001" />
|
||||||
|
<line x1="-114.2" y1="-36.080001" x2="-114.2" y2="-70.080001" />
|
||||||
|
<line x1="-104.57" y1="-36.080001" x2="-104.57" y2="-70.080001" />
|
||||||
|
<line x1="-67.63" y1="-36.080001" x2="-67.63" y2="-70.080001" />
|
||||||
|
<line x1="-58.0" y1="-36.080001" x2="-58.0" y2="-70.080001" />
|
||||||
|
<path d="M -57.9,-70.080001 Q -57.90786,-70.080001 -57.915719,-70.080001 Q -57.9392,-70.080001 -57.96268,-70.080001 Q -58.001486,-70.080001 -58.040292,-70.080001 Q -58.093935,-70.080001 -58.147578,-70.080001 Q -58.215384,-70.080001 -58.28319,-70.080001 Q -58.364305,-70.080001 -58.445421,-70.080001 Q -58.538827,-70.080001 -58.632233,-70.080001 Q -58.736754,-70.080001 -58.841275,-70.080001 Q -58.955598,-70.080001 -59.06992,-70.080001 Q -59.192605,-70.080001 -59.315291,-70.080001 Q -59.444796,-70.080001 -59.574302,-70.080001 Q -59.709,-70.080001 -59.843698,-70.080001 Q -60.119102,-70.080001 -60.4,-70.080001" />
|
||||||
|
<line x1="-60.4" y1="-70.080001" x2="-62.53" y2="-70.080001" />
|
||||||
|
<path d="M -67.53,-70.080001 Q -67.514281,-70.080001 -67.498561,-70.080001 Q -67.4516,-70.080001 -67.40464,-70.080001 Q -67.327028,-70.080001 -67.249417,-70.080001 Q -67.14213,-70.080001 -67.034844,-70.080001 Q -66.899233,-70.080001 -66.763621,-70.080001 Q -66.601389,-70.080001 -66.439157,-70.080001 Q -66.252346,-70.080001 -66.065534,-70.080001 Q -65.856491,-70.080001 -65.647449,-70.080001 Q -65.418805,-70.080001 -65.19016,-70.080001 Q -64.94479,-70.080001 -64.699419,-70.080001 Q -64.440407,-70.080001 -64.181395,-70.080001 Q -63.912,-70.080001 -63.642605,-70.080001 Q -63.091797,-70.080001 -62.53,-70.080001" />
|
||||||
|
<path d="M -70.03,-70.080001 Q -69.890044,-70.080001 -69.750089,-70.080001 Q -69.471736,-70.080001 -69.204302,-70.080001 Q -69.074796,-70.080001 -68.945291,-70.080001 Q -68.822605,-70.080001 -68.69992,-70.080001 Q -68.585598,-70.080001 -68.471275,-70.080001 Q -68.366754,-70.080001 -68.262233,-70.080001 Q -68.168827,-70.080001 -68.075421,-70.080001 Q -67.994305,-70.080001 -67.91319,-70.080001 Q -67.845384,-70.080001 -67.777578,-70.080001 Q -67.723935,-70.080001 -67.670292,-70.080001 Q -67.631486,-70.080001 -67.59268,-70.080001 Q -67.5692,-70.080001 -67.545719,-70.080001 Q -67.53786,-70.080001 -67.53,-70.080001" />
|
||||||
|
<line x1="-102.17" y1="-70.080001" x2="-70.03" y2="-70.080001" />
|
||||||
|
<path d="M -102.17,-70.080001 Q -102.309956,-70.080001 -102.449911,-70.080001 Q -102.728264,-70.080001 -102.995698,-70.080001 Q -103.125204,-70.080001 -103.254709,-70.080001 Q -103.377395,-70.080001 -103.50008,-70.080001 Q -103.614402,-70.080001 -103.728725,-70.080001 Q -103.833246,-70.080001 -103.937767,-70.080001 Q -104.031173,-70.080001 -104.124579,-70.080001 Q -104.205695,-70.080001 -104.28681,-70.080001 Q -104.354616,-70.080001 -104.422422,-70.080001 Q -104.476065,-70.080001 -104.529708,-70.080001 Q -104.568514,-70.080001 -104.60732,-70.080001 Q -104.6308,-70.080001 -104.654281,-70.080001 Q -104.66214,-70.080001 -104.67,-70.080001" />
|
||||||
|
<path d="M -109.67,-70.080001 L -109.110178,-70.080001 L -108.557395,-70.080001 L -108.018605,-70.080001 L -107.500581,-70.080001 L -107.00984,-70.080001 L -106.552551,-70.080001 L -106.134466,-70.080001 L -105.760843,-70.080001 L -105.436379,-70.080001 L -105.165156,-70.080001 L -104.950583,-70.080001 L -104.79536,-70.080001 L -104.701439,-70.080001 L -104.67,-70.080001" />
|
||||||
|
<line x1="-111.8" y1="-70.080001" x2="-109.67" y2="-70.080001" />
|
||||||
|
<path d="M -114.3,-70.080001 Q -114.29214,-70.080001 -114.284281,-70.080001 Q -114.2608,-70.080001 -114.23732,-70.080001 Q -114.198514,-70.080001 -114.159708,-70.080001 Q -114.106065,-70.080001 -114.052422,-70.080001 Q -113.984616,-70.080001 -113.91681,-70.080001 Q -113.835695,-70.080001 -113.754579,-70.080001 Q -113.661173,-70.080001 -113.567767,-70.080001 Q -113.463246,-70.080001 -113.358725,-70.080001 Q -113.244402,-70.080001 -113.13008,-70.080001 Q -113.007395,-70.080001 -112.884709,-70.080001 Q -112.755204,-70.080001 -112.625698,-70.080001 Q -112.491,-70.080001 -112.356302,-70.080001 Q -112.080898,-70.080001 -111.8,-70.080001" />
|
||||||
|
<line x1="-57.9" y1="-70.080001" x2="-57.9" y2="-81.080001" />
|
||||||
|
<path d="M -57.9,-81.080001 Q -57.90786,-81.080001 -57.915719,-81.080001 Q -57.9392,-81.080001 -57.96268,-81.080001 Q -58.001486,-81.080001 -58.040292,-81.080001 Q -58.093935,-81.080001 -58.147578,-81.080001 Q -58.215384,-81.080001 -58.28319,-81.080001 Q -58.364305,-81.080001 -58.445421,-81.080001 Q -58.538827,-81.080001 -58.632233,-81.080001 Q -58.736754,-81.080001 -58.841275,-81.080001 Q -58.955598,-81.080001 -59.06992,-81.080001 Q -59.192605,-81.080001 -59.315291,-81.080001 Q -59.444796,-81.080001 -59.574302,-81.080001 Q -59.709,-81.080001 -59.843698,-81.080001 Q -60.119102,-81.080001 -60.4,-81.080001" />
|
||||||
|
<line x1="-60.4" y1="-81.080001" x2="-62.53" y2="-81.080001" />
|
||||||
|
<path d="M -67.53,-81.080001 Q -67.514281,-81.080001 -67.498561,-81.080001 Q -67.4516,-81.080001 -67.40464,-81.080001 Q -67.327028,-81.080001 -67.249417,-81.080001 Q -67.14213,-81.080001 -67.034844,-81.080001 Q -66.899233,-81.080001 -66.763621,-81.080001 Q -66.601389,-81.080001 -66.439157,-81.080001 Q -66.252346,-81.080001 -66.065534,-81.080001 Q -65.856491,-81.080001 -65.647449,-81.080001 Q -65.418805,-81.080001 -65.19016,-81.080001 Q -64.94479,-81.080001 -64.699419,-81.080001 Q -64.440407,-81.080001 -64.181395,-81.080001 Q -63.912,-81.080001 -63.642605,-81.080001 Q -63.091797,-81.080001 -62.53,-81.080001" />
|
||||||
|
<line x1="-67.53" y1="-70.080001" x2="-67.53" y2="-81.080001" />
|
||||||
|
<path d="M -70.03,-81.080001 Q -69.890044,-81.080001 -69.750089,-81.080001 Q -69.471736,-81.080001 -69.204302,-81.080001 Q -69.074796,-81.080001 -68.945291,-81.080001 Q -68.822605,-81.080001 -68.69992,-81.080001 Q -68.585598,-81.080001 -68.471275,-81.080001 Q -68.366754,-81.080001 -68.262233,-81.080001 Q -68.168827,-81.080001 -68.075421,-81.080001 Q -67.994305,-81.080001 -67.91319,-81.080001 Q -67.845384,-81.080001 -67.777578,-81.080001 Q -67.723935,-81.080001 -67.670292,-81.080001 Q -67.631486,-81.080001 -67.59268,-81.080001 Q -67.5692,-81.080001 -67.545719,-81.080001 Q -67.53786,-81.080001 -67.53,-81.080001" />
|
||||||
|
<line x1="-102.17" y1="-81.080001" x2="-70.03" y2="-81.080001" />
|
||||||
|
<line x1="-104.67" y1="-70.080001" x2="-104.67" y2="-81.080001" />
|
||||||
|
<path d="M -102.17,-81.080001 Q -102.309956,-81.080001 -102.449911,-81.080001 Q -102.728264,-81.080001 -102.995698,-81.080001 Q -103.125204,-81.080001 -103.254709,-81.080001 Q -103.377395,-81.080001 -103.50008,-81.080001 Q -103.614402,-81.080001 -103.728725,-81.080001 Q -103.833246,-81.080001 -103.937767,-81.080001 Q -104.031173,-81.080001 -104.124579,-81.080001 Q -104.205695,-81.080001 -104.28681,-81.080001 Q -104.354616,-81.080001 -104.422422,-81.080001 Q -104.476065,-81.080001 -104.529708,-81.080001 Q -104.568514,-81.080001 -104.60732,-81.080001 Q -104.6308,-81.080001 -104.654281,-81.080001 Q -104.66214,-81.080001 -104.67,-81.080001" />
|
||||||
|
<path d="M -109.67,-81.080001 L -109.110178,-81.080001 L -108.557395,-81.080001 L -108.018605,-81.080001 L -107.500581,-81.080001 L -107.00984,-81.080001 L -106.552551,-81.080001 L -106.134466,-81.080001 L -105.760843,-81.080001 L -105.436379,-81.080001 L -105.165156,-81.080001 L -104.950583,-81.080001 L -104.79536,-81.080001 L -104.701439,-81.080001 L -104.67,-81.080001" />
|
||||||
|
<line x1="-111.8" y1="-81.080001" x2="-109.67" y2="-81.080001" />
|
||||||
|
<line x1="-114.3" y1="-70.080001" x2="-114.3" y2="-81.080001" />
|
||||||
|
<path d="M -114.3,-81.080001 Q -114.29214,-81.080001 -114.284281,-81.080001 Q -114.2608,-81.080001 -114.23732,-81.080001 Q -114.198514,-81.080001 -114.159708,-81.080001 Q -114.106065,-81.080001 -114.052422,-81.080001 Q -113.984616,-81.080001 -113.91681,-81.080001 Q -113.835695,-81.080001 -113.754579,-81.080001 Q -113.661173,-81.080001 -113.567767,-81.080001 Q -113.463246,-81.080001 -113.358725,-81.080001 Q -113.244402,-81.080001 -113.13008,-81.080001 Q -113.007395,-81.080001 -112.884709,-81.080001 Q -112.755204,-81.080001 -112.625698,-81.080001 Q -112.491,-81.080001 -112.356302,-81.080001 Q -112.080898,-81.080001 -111.8,-81.080001" />
|
||||||
|
<line x1="-82.925" y1="-23.480001" x2="-82.925" y2="-19.480001" />
|
||||||
|
<path d="M -89.275,-4.480001 L -89.195396,-4.480001 L -88.960576,-4.480001 L -88.582315,-4.480001 L -88.07958,-4.480001 L -87.477581,-4.480001 L -86.806504,-4.480001 L -86.1,-4.480001 L -85.393496,-4.480001 L -84.722419,-4.480001 L -84.12042,-4.480001 L -83.617685,-4.480001 L -83.239424,-4.480001 L -83.004604,-4.480001 L -82.925,-4.480001" />
|
||||||
|
<line x1="-111.8" y1="-29.880001" x2="-111.8" y2="-36.080001" />
|
||||||
|
<line x1="-109.67" y1="-29.880001" x2="-109.67" y2="-36.080001" />
|
||||||
|
<line x1="-60.4" y1="-29.880001" x2="-60.4" y2="-36.080001" />
|
||||||
|
<line x1="-62.53" y1="-29.880001" x2="-62.53" y2="-36.080001" />
|
||||||
|
<line x1="-111.8" y1="-36.080001" x2="-111.8" y2="-70.080001" />
|
||||||
|
<line x1="-109.67" y1="-36.080001" x2="-109.67" y2="-70.080001" />
|
||||||
|
<line x1="-102.17" y1="-36.080001" x2="-102.17" y2="-70.080001" />
|
||||||
|
<line x1="-70.03" y1="-36.080001" x2="-70.03" y2="-70.080001" />
|
||||||
|
<line x1="-62.53" y1="-36.080001" x2="-62.53" y2="-70.080001" />
|
||||||
|
<line x1="-60.4" y1="-36.080001" x2="-60.4" y2="-70.080001" />
|
||||||
|
<line x1="-60.4" y1="-70.080001" x2="-60.4" y2="-81.080001" />
|
||||||
|
<line x1="-62.53" y1="-70.080001" x2="-62.53" y2="-81.080001" />
|
||||||
|
<line x1="-70.03" y1="-70.080001" x2="-70.03" y2="-81.080001" />
|
||||||
|
<line x1="-102.17" y1="-70.080001" x2="-102.17" y2="-81.080001" />
|
||||||
|
<line x1="-109.67" y1="-70.080001" x2="-109.67" y2="-81.080001" />
|
||||||
|
<line x1="-111.8" y1="-70.080001" x2="-111.8" y2="-81.080001" />
|
||||||
|
<line x1="-105.15" y1="-23.480001" x2="-105.15" y2="-25.080001" />
|
||||||
|
<line x1="-89.275" y1="-4.480001" x2="-89.275" y2="-23.480001" />
|
||||||
|
<line x1="-82.925" y1="-4.480001" x2="-82.925" y2="-19.480001" />
|
||||||
|
<path d="M -28.2,30.096001 Q -28.185538,30.096001 -28.171076,30.096001 Q -28.127872,30.096001 -28.084668,30.096001 Q -28.013266,30.096001 -27.941863,30.096001 Q -27.84316,30.096001 -27.744457,30.096001 Q -27.619694,30.096001 -27.494931,30.096001 Q -27.345678,30.096001 -27.196425,30.096001 Q -27.024558,30.096001 -26.852691,30.096001 Q -26.660372,30.096001 -26.468053,30.096001 Q -26.2577,30.096001 -26.047348,30.096001 Q -25.821606,30.096001 -25.595865,30.096001 Q -25.357574,30.096001 -25.119284,30.096001 Q -24.87144,30.096001 -24.623596,30.096001 Q -24.116853,30.096001 -23.6,30.096001" />
|
||||||
|
<path d="M 23.6,30.096001 L 24.115037,30.096001 L 24.623596,30.096001 L 25.119284,30.096001 L 25.595865,30.096001 L 26.047348,30.096001 L 26.468053,30.096001 L 26.852691,30.096001 L 27.196425,30.096001 L 27.494931,30.096001 L 27.744457,30.096001 L 27.941863,30.096001 L 28.084668,30.096001 L 28.171076,30.096001 L 28.2,30.096001" />
|
||||||
|
<line x1="-23.6" y1="30.096001" x2="23.6" y2="30.096001" />
|
||||||
|
<path d="M 16.07,19.096001 Q 16.209956,19.096001 16.349911,19.096001 Q 16.628264,19.096001 16.895698,19.096001 Q 17.025204,19.096001 17.154709,19.096001 Q 17.277395,19.096001 17.40008,19.096001 Q 17.514402,19.096001 17.628725,19.096001 Q 17.733246,19.096001 17.837767,19.096001 Q 17.931173,19.096001 18.024579,19.096001 Q 18.105695,19.096001 18.18681,19.096001 Q 18.254616,19.096001 18.322422,19.096001 Q 18.376065,19.096001 18.429708,19.096001 Q 18.468514,19.096001 18.50732,19.096001 Q 18.5308,19.096001 18.554281,19.096001 Q 18.56214,19.096001 18.57,19.096001" />
|
||||||
|
<line x1="-16.07" y1="19.096001" x2="16.07" y2="19.096001" />
|
||||||
|
<path d="M -16.07,19.096001 Q -16.209956,19.096001 -16.349911,19.096001 Q -16.628264,19.096001 -16.895698,19.096001 Q -17.025204,19.096001 -17.154709,19.096001 Q -17.277395,19.096001 -17.40008,19.096001 Q -17.514402,19.096001 -17.628725,19.096001 Q -17.733246,19.096001 -17.837767,19.096001 Q -17.931173,19.096001 -18.024579,19.096001 Q -18.105695,19.096001 -18.18681,19.096001 Q -18.254616,19.096001 -18.322422,19.096001 Q -18.376065,19.096001 -18.429708,19.096001 Q -18.468514,19.096001 -18.50732,19.096001 Q -18.5308,19.096001 -18.554281,19.096001 Q -18.56214,19.096001 -18.57,19.096001" />
|
||||||
|
<path d="M 18.57,19.096001 Q 18.585719,19.096001 18.601439,19.096001 Q 18.6484,19.096001 18.69536,19.096001 Q 18.772972,19.096001 18.850583,19.096001 Q 18.95787,19.096001 19.065156,19.096001 Q 19.200767,19.096001 19.336379,19.096001 Q 19.498611,19.096001 19.660843,19.096001 Q 19.847654,19.096001 20.034466,19.096001 Q 20.243509,19.096001 20.452551,19.096001 Q 20.681195,19.096001 20.90984,19.096001 Q 21.15521,19.096001 21.400581,19.096001 Q 21.659593,19.096001 21.918605,19.096001 Q 22.188,19.096001 22.457395,19.096001 Q 23.008203,19.096001 23.57,19.096001" />
|
||||||
|
<line x1="25.7" y1="19.096001" x2="23.57" y2="19.096001" />
|
||||||
|
<path d="M -23.57,19.096001 L -23.010178,19.096001 L -22.457395,19.096001 L -21.918605,19.096001 L -21.400581,19.096001 L -20.90984,19.096001 L -20.452551,19.096001 L -20.034466,19.096001 L -19.660843,19.096001 L -19.336379,19.096001 L -19.065156,19.096001 L -18.850583,19.096001 L -18.69536,19.096001 L -18.601439,19.096001 L -18.57,19.096001" />
|
||||||
|
<path d="M 28.2,19.096001 Q 28.19214,19.096001 28.184281,19.096001 Q 28.1608,19.096001 28.13732,19.096001 Q 28.098514,19.096001 28.059708,19.096001 Q 28.006065,19.096001 27.952422,19.096001 Q 27.884616,19.096001 27.81681,19.096001 Q 27.735695,19.096001 27.654579,19.096001 Q 27.561173,19.096001 27.467767,19.096001 Q 27.363246,19.096001 27.258725,19.096001 Q 27.144402,19.096001 27.03008,19.096001 Q 26.907395,19.096001 26.784709,19.096001 Q 26.655204,19.096001 26.525698,19.096001 Q 26.391,19.096001 26.256302,19.096001 Q 25.980898,19.096001 25.7,19.096001" />
|
||||||
|
<line x1="-25.7" y1="19.096001" x2="-23.57" y2="19.096001" />
|
||||||
|
<path d="M -28.2,19.096001 Q -28.19214,19.096001 -28.184281,19.096001 Q -28.1608,19.096001 -28.13732,19.096001 Q -28.098514,19.096001 -28.059708,19.096001 Q -28.006065,19.096001 -27.952422,19.096001 Q -27.884616,19.096001 -27.81681,19.096001 Q -27.735695,19.096001 -27.654579,19.096001 Q -27.561173,19.096001 -27.467767,19.096001 Q -27.363246,19.096001 -27.258725,19.096001 Q -27.144402,19.096001 -27.03008,19.096001 Q -26.907395,19.096001 -26.784709,19.096001 Q -26.655204,19.096001 -26.525698,19.096001 Q -26.391,19.096001 -26.256302,19.096001 Q -25.980898,19.096001 -25.7,19.096001" />
|
||||||
|
<line x1="-28.2" y1="25.296001" x2="-28.2" y2="19.096001" />
|
||||||
|
<line x1="-28.2" y1="30.096001" x2="-28.2" y2="25.296001" />
|
||||||
|
<line x1="28.2" y1="30.096001" x2="28.2" y2="25.296001" />
|
||||||
|
<line x1="28.2" y1="25.296001" x2="28.2" y2="19.096001" />
|
||||||
|
<line x1="-23.6" y1="30.096001" x2="-23.6" y2="25.296001" />
|
||||||
|
<path d="M -28.2,25.296001 Q -28.185538,25.296001 -28.171076,25.296001 Q -28.127872,25.296001 -28.084668,25.296001 Q -28.013266,25.296001 -27.941863,25.296001 Q -27.84316,25.296001 -27.744457,25.296001 Q -27.619694,25.296001 -27.494931,25.296001 Q -27.345678,25.296001 -27.196425,25.296001 Q -27.024558,25.296001 -26.852691,25.296001 Q -26.660372,25.296001 -26.468053,25.296001 Q -26.2577,25.296001 -26.047348,25.296001 Q -25.821606,25.296001 -25.595865,25.296001 Q -25.357574,25.296001 -25.119284,25.296001 Q -24.87144,25.296001 -24.623596,25.296001 Q -24.116853,25.296001 -23.6,25.296001" />
|
||||||
|
<line x1="23.6" y1="30.096001" x2="23.6" y2="25.296001" />
|
||||||
|
<path d="M 23.6,25.296001 L 24.115037,25.296001 L 24.623596,25.296001 L 25.119284,25.296001 L 25.595865,25.296001 L 26.047348,25.296001 L 26.468053,25.296001 L 26.852691,25.296001 L 27.196425,25.296001 L 27.494931,25.296001 L 27.744457,25.296001 L 27.941863,25.296001 L 28.084668,25.296001 L 28.171076,25.296001 L 28.2,25.296001" />
|
||||||
|
<line x1="-23.6" y1="25.296001" x2="-16.07" y2="25.296001" />
|
||||||
|
<line x1="-16.07" y1="25.296001" x2="-16.07" y2="19.096001" />
|
||||||
|
<line x1="16.07" y1="25.296001" x2="16.07" y2="19.096001" />
|
||||||
|
<line x1="16.07" y1="25.296001" x2="23.6" y2="25.296001" />
|
||||||
|
<path d="M 0.0,31.696001 L 2.132923,31.696001 L 4.239024,31.696001 L 6.291816,31.696001 L 8.265485,31.696001 L 10.135211,31.696001 L 11.877481,31.696001 L 13.470384,31.696001 L 14.89389,31.696001 L 16.130096,31.696001 L 17.163457,31.696001 L 17.980977,31.696001 L 18.572377,31.696001 L 18.930218,31.696001 L 19.05,31.696001" />
|
||||||
|
<path d="M -19.05,31.696001 L -18.930218,31.696001 L -18.572377,31.696001 L -17.980977,31.696001 L -17.163457,31.696001 L -16.130096,31.696001 L -14.89389,31.696001 L -13.470384,31.696001 L -11.877481,31.696001 L -10.135211,31.696001 L -8.265485,31.696001 L -6.291816,31.696001 L -4.239024,31.696001 L -2.132923,31.696001 L -0.0,31.696001" />
|
||||||
|
<line x1="18.57" y1="25.296001" x2="18.57" y2="19.096001" />
|
||||||
|
<line x1="-18.57" y1="25.296001" x2="-18.57" y2="19.096001" />
|
||||||
|
<line x1="18.47" y1="19.096001" x2="18.47" y2="-14.903999" />
|
||||||
|
<line x1="28.1" y1="19.096001" x2="28.1" y2="-14.903999" />
|
||||||
|
<line x1="-28.1" y1="19.096001" x2="-28.1" y2="-14.903999" />
|
||||||
|
<line x1="-18.47" y1="19.096001" x2="-18.47" y2="-14.903999" />
|
||||||
|
<path d="M 16.07,-14.903999 Q 16.209956,-14.903999 16.349911,-14.903999 Q 16.628264,-14.903999 16.895698,-14.903999 Q 17.025204,-14.903999 17.154709,-14.903999 Q 17.277395,-14.903999 17.40008,-14.903999 Q 17.514402,-14.903999 17.628725,-14.903999 Q 17.733246,-14.903999 17.837767,-14.903999 Q 17.931173,-14.903999 18.024579,-14.903999 Q 18.105695,-14.903999 18.18681,-14.903999 Q 18.254616,-14.903999 18.322422,-14.903999 Q 18.376065,-14.903999 18.429708,-14.903999 Q 18.468514,-14.903999 18.50732,-14.903999 Q 18.5308,-14.903999 18.554281,-14.903999 Q 18.56214,-14.903999 18.57,-14.903999" />
|
||||||
|
<line x1="-16.07" y1="-14.903999" x2="16.07" y2="-14.903999" />
|
||||||
|
<path d="M -16.07,-14.903999 Q -16.209956,-14.903999 -16.349911,-14.903999 Q -16.628264,-14.903999 -16.895698,-14.903999 Q -17.025204,-14.903999 -17.154709,-14.903999 Q -17.277395,-14.903999 -17.40008,-14.903999 Q -17.514402,-14.903999 -17.628725,-14.903999 Q -17.733246,-14.903999 -17.837767,-14.903999 Q -17.931173,-14.903999 -18.024579,-14.903999 Q -18.105695,-14.903999 -18.18681,-14.903999 Q -18.254616,-14.903999 -18.322422,-14.903999 Q -18.376065,-14.903999 -18.429708,-14.903999 Q -18.468514,-14.903999 -18.50732,-14.903999 Q -18.5308,-14.903999 -18.554281,-14.903999 Q -18.56214,-14.903999 -18.57,-14.903999" />
|
||||||
|
<path d="M 18.57,-14.903999 Q 18.585719,-14.903999 18.601439,-14.903999 Q 18.6484,-14.903999 18.69536,-14.903999 Q 18.772972,-14.903999 18.850583,-14.903999 Q 18.95787,-14.903999 19.065156,-14.903999 Q 19.200767,-14.903999 19.336379,-14.903999 Q 19.498611,-14.903999 19.660843,-14.903999 Q 19.847654,-14.903999 20.034466,-14.903999 Q 20.243509,-14.903999 20.452551,-14.903999 Q 20.681195,-14.903999 20.90984,-14.903999 Q 21.15521,-14.903999 21.400581,-14.903999 Q 21.659593,-14.903999 21.918605,-14.903999 Q 22.188,-14.903999 22.457395,-14.903999 Q 23.008203,-14.903999 23.57,-14.903999" />
|
||||||
|
<line x1="25.7" y1="-14.903999" x2="23.57" y2="-14.903999" />
|
||||||
|
<path d="M -23.57,-14.903999 L -23.010178,-14.903999 L -22.457395,-14.903999 L -21.918605,-14.903999 L -21.400581,-14.903999 L -20.90984,-14.903999 L -20.452551,-14.903999 L -20.034466,-14.903999 L -19.660843,-14.903999 L -19.336379,-14.903999 L -19.065156,-14.903999 L -18.850583,-14.903999 L -18.69536,-14.903999 L -18.601439,-14.903999 L -18.57,-14.903999" />
|
||||||
|
<path d="M 28.2,-14.903999 Q 28.19214,-14.903999 28.184281,-14.903999 Q 28.1608,-14.903999 28.13732,-14.903999 Q 28.098514,-14.903999 28.059708,-14.903999 Q 28.006065,-14.903999 27.952422,-14.903999 Q 27.884616,-14.903999 27.81681,-14.903999 Q 27.735695,-14.903999 27.654579,-14.903999 Q 27.561173,-14.903999 27.467767,-14.903999 Q 27.363246,-14.903999 27.258725,-14.903999 Q 27.144402,-14.903999 27.03008,-14.903999 Q 26.907395,-14.903999 26.784709,-14.903999 Q 26.655204,-14.903999 26.525698,-14.903999 Q 26.391,-14.903999 26.256302,-14.903999 Q 25.980898,-14.903999 25.7,-14.903999" />
|
||||||
|
<line x1="-25.7" y1="-14.903999" x2="-23.57" y2="-14.903999" />
|
||||||
|
<path d="M -28.2,-14.903999 Q -28.19214,-14.903999 -28.184281,-14.903999 Q -28.1608,-14.903999 -28.13732,-14.903999 Q -28.098514,-14.903999 -28.059708,-14.903999 Q -28.006065,-14.903999 -27.952422,-14.903999 Q -27.884616,-14.903999 -27.81681,-14.903999 Q -27.735695,-14.903999 -27.654579,-14.903999 Q -27.561173,-14.903999 -27.467767,-14.903999 Q -27.363246,-14.903999 -27.258725,-14.903999 Q -27.144402,-14.903999 -27.03008,-14.903999 Q -26.907395,-14.903999 -26.784709,-14.903999 Q -26.655204,-14.903999 -26.525698,-14.903999 Q -26.391,-14.903999 -26.256302,-14.903999 Q -25.980898,-14.903999 -25.7,-14.903999" />
|
||||||
|
<line x1="18.57" y1="-14.903999" x2="18.57" y2="-25.903999" />
|
||||||
|
<path d="M 16.07,-25.903999 Q 16.209956,-25.903999 16.349911,-25.903999 Q 16.628264,-25.903999 16.895698,-25.903999 Q 17.025204,-25.903999 17.154709,-25.903999 Q 17.277395,-25.903999 17.40008,-25.903999 Q 17.514402,-25.903999 17.628725,-25.903999 Q 17.733246,-25.903999 17.837767,-25.903999 Q 17.931173,-25.903999 18.024579,-25.903999 Q 18.105695,-25.903999 18.18681,-25.903999 Q 18.254616,-25.903999 18.322422,-25.903999 Q 18.376065,-25.903999 18.429708,-25.903999 Q 18.468514,-25.903999 18.50732,-25.903999 Q 18.5308,-25.903999 18.554281,-25.903999 Q 18.56214,-25.903999 18.57,-25.903999" />
|
||||||
|
<line x1="-16.07" y1="-25.903999" x2="16.07" y2="-25.903999" />
|
||||||
|
<line x1="-18.57" y1="-14.903999" x2="-18.57" y2="-25.903999" />
|
||||||
|
<path d="M -16.07,-25.903999 Q -16.209956,-25.903999 -16.349911,-25.903999 Q -16.628264,-25.903999 -16.895698,-25.903999 Q -17.025204,-25.903999 -17.154709,-25.903999 Q -17.277395,-25.903999 -17.40008,-25.903999 Q -17.514402,-25.903999 -17.628725,-25.903999 Q -17.733246,-25.903999 -17.837767,-25.903999 Q -17.931173,-25.903999 -18.024579,-25.903999 Q -18.105695,-25.903999 -18.18681,-25.903999 Q -18.254616,-25.903999 -18.322422,-25.903999 Q -18.376065,-25.903999 -18.429708,-25.903999 Q -18.468514,-25.903999 -18.50732,-25.903999 Q -18.5308,-25.903999 -18.554281,-25.903999 Q -18.56214,-25.903999 -18.57,-25.903999" />
|
||||||
|
<path d="M 18.57,-25.903999 Q 18.585719,-25.903999 18.601439,-25.903999 Q 18.6484,-25.903999 18.69536,-25.903999 Q 18.772972,-25.903999 18.850583,-25.903999 Q 18.95787,-25.903999 19.065156,-25.903999 Q 19.200767,-25.903999 19.336379,-25.903999 Q 19.498611,-25.903999 19.660843,-25.903999 Q 19.847654,-25.903999 20.034466,-25.903999 Q 20.243509,-25.903999 20.452551,-25.903999 Q 20.681195,-25.903999 20.90984,-25.903999 Q 21.15521,-25.903999 21.400581,-25.903999 Q 21.659593,-25.903999 21.918605,-25.903999 Q 22.188,-25.903999 22.457395,-25.903999 Q 23.008203,-25.903999 23.57,-25.903999" />
|
||||||
|
<line x1="25.7" y1="-25.903999" x2="23.57" y2="-25.903999" />
|
||||||
|
<path d="M -23.57,-25.903999 L -23.010178,-25.903999 L -22.457395,-25.903999 L -21.918605,-25.903999 L -21.400581,-25.903999 L -20.90984,-25.903999 L -20.452551,-25.903999 L -20.034466,-25.903999 L -19.660843,-25.903999 L -19.336379,-25.903999 L -19.065156,-25.903999 L -18.850583,-25.903999 L -18.69536,-25.903999 L -18.601439,-25.903999 L -18.57,-25.903999" />
|
||||||
|
<line x1="28.2" y1="-14.903999" x2="28.2" y2="-25.903999" />
|
||||||
|
<path d="M 28.2,-25.903999 Q 28.19214,-25.903999 28.184281,-25.903999 Q 28.1608,-25.903999 28.13732,-25.903999 Q 28.098514,-25.903999 28.059708,-25.903999 Q 28.006065,-25.903999 27.952422,-25.903999 Q 27.884616,-25.903999 27.81681,-25.903999 Q 27.735695,-25.903999 27.654579,-25.903999 Q 27.561173,-25.903999 27.467767,-25.903999 Q 27.363246,-25.903999 27.258725,-25.903999 Q 27.144402,-25.903999 27.03008,-25.903999 Q 26.907395,-25.903999 26.784709,-25.903999 Q 26.655204,-25.903999 26.525698,-25.903999 Q 26.391,-25.903999 26.256302,-25.903999 Q 25.980898,-25.903999 25.7,-25.903999" />
|
||||||
|
<line x1="-25.7" y1="-25.903999" x2="-23.57" y2="-25.903999" />
|
||||||
|
<line x1="-28.2" y1="-14.903999" x2="-28.2" y2="-25.903999" />
|
||||||
|
<path d="M -28.2,-25.903999 Q -28.19214,-25.903999 -28.184281,-25.903999 Q -28.1608,-25.903999 -28.13732,-25.903999 Q -28.098514,-25.903999 -28.059708,-25.903999 Q -28.006065,-25.903999 -27.952422,-25.903999 Q -27.884616,-25.903999 -27.81681,-25.903999 Q -27.735695,-25.903999 -27.654579,-25.903999 Q -27.561173,-25.903999 -27.467767,-25.903999 Q -27.363246,-25.903999 -27.258725,-25.903999 Q -27.144402,-25.903999 -27.03008,-25.903999 Q -26.907395,-25.903999 -26.784709,-25.903999 Q -26.655204,-25.903999 -26.525698,-25.903999 Q -26.391,-25.903999 -26.256302,-25.903999 Q -25.980898,-25.903999 -25.7,-25.903999" />
|
||||||
|
<path d="M 0.0,35.696001 C 0.530549,35.696001 1.058689,35.696001 1.524884,35.696001 C 1.58996,35.696001 1.655037,35.696001 1.720114,35.696001 C 1.782255,35.696001 1.844397,35.696001 1.906538,35.696001 C 1.965426,35.696001 2.024313,35.696001 2.083201,35.696001 C 2.138534,35.696001 2.193866,35.696001 2.249199,35.696001 C 2.300693,35.696001 2.352187,35.696001 2.403681,35.696001 C 2.451074,35.696001 2.498466,35.696001 2.545858,35.696001 C 2.588905,35.696001 2.631953,35.696001 2.675,35.696001" />
|
||||||
|
<path d="M 2.675,35.696001 C 2.999597,35.696001 3.175154,35.696001 3.175,35.696001" />
|
||||||
|
<line x1="2.675" y1="35.696001" x2="2.675" y2="50.696001" />
|
||||||
|
<path d="M -3.175,50.696001 L -3.12154,50.696001 L -2.962961,50.696001 L -2.704603,50.696001 L -2.355167,50.696001 L -1.926419,50.696001 L -1.432798,50.696001 L -0.890926,50.696001 L -0.319053,50.696001 L 0.263565,50.696001 L 0.837307,50.696001 L 1.382852,50.696001 L 1.881829,50.696001 L 2.317435,50.696001 L 2.675,50.696001" />
|
||||||
|
<line x1="2.675" y1="35.696001" x2="2.675" y2="50.696001" />
|
||||||
|
<line x1="23.57" y1="25.296001" x2="23.57" y2="19.096001" />
|
||||||
|
<line x1="25.7" y1="25.296001" x2="25.7" y2="19.096001" />
|
||||||
|
<line x1="-23.57" y1="25.296001" x2="-23.57" y2="19.096001" />
|
||||||
|
<line x1="-25.7" y1="25.296001" x2="-25.7" y2="19.096001" />
|
||||||
|
<line x1="16.07" y1="19.096001" x2="16.07" y2="-14.903999" />
|
||||||
|
<line x1="23.57" y1="19.096001" x2="23.57" y2="-14.903999" />
|
||||||
|
<line x1="25.7" y1="19.096001" x2="25.7" y2="-14.903999" />
|
||||||
|
<line x1="-25.7" y1="19.096001" x2="-25.7" y2="-14.903999" />
|
||||||
|
<line x1="-23.57" y1="19.096001" x2="-23.57" y2="-14.903999" />
|
||||||
|
<line x1="-16.07" y1="19.096001" x2="-16.07" y2="-14.903999" />
|
||||||
|
<line x1="16.07" y1="-14.903999" x2="16.07" y2="-25.903999" />
|
||||||
|
<line x1="-16.07" y1="-14.903999" x2="-16.07" y2="-25.903999" />
|
||||||
|
<line x1="23.57" y1="-14.903999" x2="23.57" y2="-25.903999" />
|
||||||
|
<line x1="25.7" y1="-14.903999" x2="25.7" y2="-25.903999" />
|
||||||
|
<line x1="-23.57" y1="-14.903999" x2="-23.57" y2="-25.903999" />
|
||||||
|
<line x1="-25.7" y1="-14.903999" x2="-25.7" y2="-25.903999" />
|
||||||
|
<line x1="-19.05" y1="31.696001" x2="-19.05" y2="30.096001" />
|
||||||
|
<line x1="19.05" y1="31.696001" x2="19.05" y2="30.096001" />
|
||||||
|
<line x1="-3.175" y1="50.696001" x2="-3.175" y2="31.696001" />
|
||||||
|
<line x1="3.175" y1="35.696001" x2="3.175" y2="31.696001" />
|
||||||
|
<path d="M 133.5,89.25 L -133.5,89.25 L -133.5,89.0 L -133.75,89.0 L -133.75,-89.0 L -133.5,-89.0 L -133.5,-89.25 L 133.5,-89.25 L 133.5,-89.0 L 133.75,-89.0 L 133.75,89.0 L 133.5,89.0 L 133.5,89.25 M -133.25,88.75 L 133.25,88.75 L 133.25,-88.75 L -133.25,-88.75 L -133.25,88.75" />
|
||||||
|
<path d="M -89.25,99.0 L -89.25,89.0 L -88.75,89.0 L -88.75,99.0 L -89.25,99.0" />
|
||||||
|
<path d="M -44.75,99.0 L -44.75,89.0 L -44.25,89.0 L -44.25,99.0 L -44.75,99.0" />
|
||||||
|
<path d="M -0.25,99.0 L -0.25,89.0 L 0.25,89.0 L 0.25,99.0 L -0.25,99.0" />
|
||||||
|
<path d="M 44.25,99.0 L 44.25,89.0 L 44.75,89.0 L 44.75,99.0 L 44.25,99.0" />
|
||||||
|
<path d="M 88.75,99.0 L 88.75,89.0 L 89.25,89.0 L 89.25,99.0 L 88.75,99.0" />
|
||||||
|
<path d="M 143.5,44.75 L 133.5,44.75 L 133.5,44.25 L 143.5,44.25 L 143.5,44.75" />
|
||||||
|
<path d="M 143.5,0.25 L 133.5,0.25 L 133.5,-0.25 L 143.5,-0.25 L 143.5,0.25" />
|
||||||
|
<path d="M 143.5,-44.25 L 133.5,-44.25 L 133.5,-44.75 L 143.5,-44.75 L 143.5,-44.25" />
|
||||||
|
<path d="M 89.25,-99.0 L 89.25,-89.0 L 88.75,-89.0 L 88.75,-99.0 L 89.25,-99.0" />
|
||||||
|
<path d="M 44.75,-99.0 L 44.75,-89.0 L 44.25,-89.0 L 44.25,-99.0 L 44.75,-99.0" />
|
||||||
|
<path d="M 0.25,-99.0 L 0.25,-89.0 L -0.25,-89.0 L -0.25,-99.0 L 0.25,-99.0" />
|
||||||
|
<path d="M -44.25,-99.0 L -44.25,-89.0 L -44.75,-89.0 L -44.75,-99.0 L -44.25,-99.0" />
|
||||||
|
<path d="M -88.75,-99.0 L -88.75,-89.0 L -89.25,-89.0 L -89.25,-99.0 L -88.75,-99.0" />
|
||||||
|
<path d="M -143.5,-44.75 L -133.5,-44.75 L -133.5,-44.25 L -143.5,-44.25 L -143.5,-44.75" />
|
||||||
|
<path d="M -143.5,-0.25 L -133.5,-0.25 L -133.5,0.25 L -143.5,0.25 L -143.5,-0.25" />
|
||||||
|
<path d="M -143.5,44.25 L -133.5,44.25 L -133.5,44.75 L -143.5,44.75 L -143.5,44.25" />
|
||||||
|
<path d="M -141.18999,-64.200001 L -141.18999,-69.250001 L -140.31001,-69.250001 L -140.31001,-62.159994 L -140.88999,-62.159994 Q -141.120003,-62.980014 -141.429997,-63.21001 Q -141.73999,-63.440007 -142.76001,-63.569987 L -142.76001,-64.200001 L -141.18999,-64.200001" />
|
||||||
|
<path d="M 140.81001,-64.200001 L 140.81001,-69.250001 L 141.68999,-69.250001 L 141.68999,-62.159994 L 141.11001,-62.159994 Q 140.879997,-62.980014 140.570003,-63.21001 Q 140.26001,-63.440007 139.23999,-63.569987 L 139.23999,-64.200001 L 140.81001,-64.200001" />
|
||||||
|
<path d="M -143.279997,-20.119987 L -142.399984,-20.119987 Q -142.299984,-18.430014 -140.970003,-18.430014 Q -140.370003,-18.430014 -139.970003,-18.81001 Q -139.570003,-19.190007 -139.570003,-19.759994 Q -139.570003,-20.609994 -140.529997,-21.159994 L -141.449984,-21.680014 Q -142.56001,-22.309994 -142.970003,-22.945004 Q -143.379997,-23.580014 -143.43999,-24.750001 L -138.720003,-24.750001 L -138.720003,-23.880014 L -142.449984,-23.880014 Q -142.379997,-23.430014 -142.1,-23.100001 Q -141.820003,-22.769987 -141.170003,-22.419987 L -140.170003,-21.880014 Q -138.670003,-21.059994 -138.670003,-19.740007 Q -138.670003,-18.819987 -139.3,-18.239991 Q -139.929997,-17.659994 -140.93999,-17.659994 Q -143.21001,-17.659994 -143.279997,-20.119987" />
|
||||||
|
<path d="M 138.720003,-20.119987 L 139.600016,-20.119987 Q 139.700016,-18.430014 141.029997,-18.430014 Q 141.629997,-18.430014 142.029997,-18.81001 Q 142.429997,-19.190007 142.429997,-19.759994 Q 142.429997,-20.609994 141.470003,-21.159994 L 140.550016,-21.680014 Q 139.43999,-22.309994 139.029997,-22.945004 Q 138.620003,-23.580014 138.56001,-24.750001 L 143.279997,-24.750001 L 143.279997,-23.880014 L 139.550016,-23.880014 Q 139.620003,-23.430014 139.9,-23.100001 Q 140.179997,-22.769987 140.829997,-22.419987 L 141.829997,-21.880014 Q 143.329997,-21.059994 143.329997,-19.740007 Q 143.329997,-18.819987 142.7,-18.239991 Q 142.070003,-17.659994 141.06001,-17.659994 Q 138.78999,-17.659994 138.720003,-20.119987" />
|
||||||
|
<path d="M -142.429997,24.549999 Q -142.41001,25.240006 -142.125,25.654996 Q -141.83999,26.069986 -141.079997,26.069986 Q -140.499984,26.069986 -140.16499,25.744986 Q -139.829997,25.419986 -139.829997,24.859993 Q -139.829997,24.209993 -140.225,23.98999 Q -140.620003,23.769986 -141.570003,23.749999 L -141.570003,22.999999 L -141.46001,22.999999 L -141.08999,23.009993 Q -139.620003,23.009993 -139.620003,21.719986 Q -139.620003,21.049999 -140.009993,20.674999 Q -140.399984,20.299999 -141.08999,20.299999 Q -141.81001,20.299999 -142.170003,20.670003 Q -142.529997,21.040006 -142.579997,21.809993 L -143.46001,21.809993 Q -143.299984,19.519986 -141.120003,19.519986 Q -140.029997,19.519986 -139.375,20.119986 Q -138.720003,20.719986 -138.720003,21.730013 Q -138.720003,22.409993 -139.0,22.804996 Q -139.279997,23.199999 -139.920003,23.419986 Q -138.929997,23.809993 -138.929997,24.890006 Q -138.929997,25.799999 -139.504997,26.320003 Q -140.079997,26.840006 -141.08999,26.840006 Q -143.26001,26.840006 -143.31001,24.549999 L -142.429997,24.549999" />
|
||||||
|
<path d="M 139.570003,24.549999 Q 139.58999,25.240006 139.875,25.654996 Q 140.16001,26.069986 140.920003,26.069986 Q 141.500016,26.069986 141.83501,25.744986 Q 142.170003,25.419986 142.170003,24.859993 Q 142.170003,24.209993 141.775,23.98999 Q 141.379997,23.769986 140.429997,23.749999 L 140.429997,22.999999 L 140.53999,22.999999 L 140.91001,23.009993 Q 142.379997,23.009993 142.379997,21.719986 Q 142.379997,21.049999 141.990007,20.674999 Q 141.600016,20.299999 140.91001,20.299999 Q 140.18999,20.299999 139.829997,20.670003 Q 139.470003,21.040006 139.420003,21.809993 L 138.53999,21.809993 Q 138.700016,19.519986 140.879997,19.519986 Q 141.970003,19.519986 142.625,20.119986 Q 143.279997,20.719986 143.279997,21.730013 Q 143.279997,22.409993 143.0,22.804996 Q 142.720003,23.199999 142.079997,23.419986 Q 143.070003,23.809993 143.070003,24.890006 Q 143.070003,25.799999 142.495003,26.320003 Q 141.920003,26.840006 140.91001,26.840006 Q 138.73999,26.840006 138.68999,24.549999 L 139.570003,24.549999" />
|
||||||
|
<path d="M -140.51001,65.949999 L -140.51001,64.249999 L -139.629997,64.249999 L -139.629997,65.949999 L -138.579997,65.949999 L -138.579997,66.740006 L -139.629997,66.740006 L -139.629997,71.340006 L -140.279997,71.340006 L -143.499984,66.880013 L -143.499984,65.949999 L -140.51001,65.949999 M -140.51001,66.740006 L -142.729997,66.740006 L -140.51001,69.840006 L -140.51001,66.740006" />
|
||||||
|
<path d="M 141.48999,65.949999 L 141.48999,64.249999 L 142.370003,64.249999 L 142.370003,65.949999 L 143.420003,65.949999 L 143.420003,66.740006 L 142.370003,66.740006 L 142.370003,71.340006 L 141.720003,71.340006 L 138.500016,66.880013 L 138.500016,65.949999 L 141.48999,65.949999 M 141.48999,66.740006 L 139.270003,66.740006 L 141.48999,69.840006 L 141.48999,66.740006" />
|
||||||
|
<path d="M -112.51499,-95.680014 L -109.03501,-95.680014 L -109.03501,-94.859994 L -112.51499,-94.859994 L -112.51499,-92.530014 L -108.554997,-92.530014 L -108.554997,-91.709994 L -113.445003,-91.709994 L -113.445003,-99.000001 L -112.51499,-99.000001 L -112.51499,-95.680014" />
|
||||||
|
<path d="M -112.51499,97.319986 L -109.03501,97.319986 L -109.03501,98.140006 L -112.51499,98.140006 L -112.51499,100.469986 L -108.554997,100.469986 L -108.554997,101.290006 L -113.445003,101.290006 L -113.445003,93.999999 L -112.51499,93.999999 L -112.51499,97.319986" />
|
||||||
|
<path d="M -68.184993,-95.680014 L -64.215007,-95.680014 L -64.215007,-94.859994 L -68.184993,-94.859994 L -68.184993,-92.530014 L -64.065007,-92.530014 L -64.065007,-91.709994 L -69.115007,-91.709994 L -69.115007,-99.000001 L -63.884993,-99.000001 L -63.884993,-98.180014 L -68.184993,-98.180014 L -68.184993,-95.680014" />
|
||||||
|
<path d="M -68.184993,97.319986 L -64.215007,97.319986 L -64.215007,98.140006 L -68.184993,98.140006 L -68.184993,100.469986 L -64.065007,100.469986 L -64.065007,101.290006 L -69.115007,101.290006 L -69.115007,93.999999 L -63.884993,93.999999 L -63.884993,94.819986 L -68.184993,94.819986 L -68.184993,97.319986" />
|
||||||
|
<path d="M -24.940007,-99.000001 L -22.130013,-99.000001 Q -20.75,-99.000001 -19.954997,-98.025001 Q -19.159993,-97.050001 -19.159993,-95.350001 Q -19.159993,-93.650001 -19.95,-92.679997 Q -20.740007,-91.709994 -22.130013,-91.709994 L -24.940007,-91.709994 L -24.940007,-99.000001 M -24.009993,-98.180014 L -24.009993,-92.530014 L -22.290007,-92.530014 Q -21.209993,-92.530014 -20.65,-93.250001 Q -20.090007,-93.969987 -20.090007,-95.359994 Q -20.090007,-96.740007 -20.65,-97.46001 Q -21.209993,-98.180014 -22.290007,-98.180014 L -24.009993,-98.180014" />
|
||||||
|
<path d="M -24.940007,93.999999 L -22.130013,93.999999 Q -20.75,93.999999 -19.954997,94.974999 Q -19.159993,95.949999 -19.159993,97.649999 Q -19.159993,99.349999 -19.95,100.320003 Q -20.740007,101.290006 -22.130013,101.290006 L -24.940007,101.290006 L -24.940007,93.999999 M -24.009993,94.819986 L -24.009993,100.469986 L -22.290007,100.469986 Q -21.209993,100.469986 -20.65,99.749999 Q -20.090007,99.030013 -20.090007,97.640006 Q -20.090007,96.259993 -20.65,95.53999 Q -21.209993,94.819986 -22.290007,94.819986 L -24.009993,94.819986" />
|
||||||
|
<path d="M 25.245003,-93.969987 Q 24.81499,-91.590007 22.43501,-91.590007 Q 21.674984,-91.590007 21.069987,-91.850001 Q 20.46499,-92.109994 20.109993,-92.504997 Q 19.754997,-92.900001 19.51499,-93.429997 Q 19.274984,-93.959994 19.18999,-94.450001 Q 19.104997,-94.940007 19.104997,-95.440007 Q 19.104997,-95.930014 19.18999,-96.41001 Q 19.274984,-96.890007 19.509993,-97.415007 Q 19.745003,-97.940007 20.1,-98.329997 Q 20.454997,-98.719987 21.05,-98.975001 Q 21.645003,-99.230014 22.395003,-99.230014 Q 25.06499,-99.230014 25.395003,-96.340007 L 24.43501,-96.340007 Q 24.26499,-97.400001 23.784993,-97.904997 Q 23.304997,-98.409994 22.404997,-98.409994 Q 21.31499,-98.409994 20.675,-97.604997 Q 20.03501,-96.800001 20.03501,-95.430014 Q 20.03501,-94.030014 20.65,-93.220004 Q 21.26499,-92.409994 22.324984,-92.409994 Q 23.195003,-92.409994 23.665007,-92.800001 Q 24.13501,-93.190007 24.295003,-93.969987 L 25.245003,-93.969987" />
|
||||||
|
<path d="M 25.245003,99.030013 Q 24.81499,101.409993 22.43501,101.409993 Q 21.674984,101.409993 21.069987,101.149999 Q 20.46499,100.890006 20.109993,100.495003 Q 19.754997,100.099999 19.51499,99.570003 Q 19.274984,99.040006 19.18999,98.549999 Q 19.104997,98.059993 19.104997,97.559993 Q 19.104997,97.069986 19.18999,96.58999 Q 19.274984,96.109993 19.509993,95.584993 Q 19.745003,95.059993 20.1,94.670003 Q 20.454997,94.280013 21.05,94.024999 Q 21.645003,93.769986 22.395003,93.769986 Q 25.06499,93.769986 25.395003,96.659993 L 24.43501,96.659993 Q 24.26499,95.599999 23.784993,95.095003 Q 23.304997,94.590006 22.404997,94.590006 Q 21.31499,94.590006 20.675,95.395003 Q 20.03501,96.199999 20.03501,97.569986 Q 20.03501,98.969986 20.65,99.779996 Q 21.26499,100.590006 22.324984,100.590006 Q 23.195003,100.590006 23.665007,100.199999 Q 24.13501,99.809993 24.295003,99.030013 L 25.245003,99.030013" />
|
||||||
|
<path d="M 67.520003,-99.000001 Q 68.48999,-99.000001 69.079997,-98.429997 Q 69.670003,-97.859994 69.670003,-96.919987 Q 69.670003,-96.259994 69.354997,-95.839991 Q 69.03999,-95.419987 68.33999,-95.150001 C 69.013319,-94.836676 69.349984,-94.306674 69.349984,-93.559994 C 69.347379,-93.145108 69.227588,-92.75989 68.924984,-92.364991 C 68.774984,-92.168322 68.549984,-92.009989 68.249984,-91.889991 C 67.949984,-91.769993 67.596653,-91.709994 67.18999,-91.709994 L 64.229997,-91.709994 L 64.229997,-99.000001 L 67.520003,-99.000001 M 66.96001,-92.530014 Q 68.420003,-92.530014 68.420003,-93.690007 Q 68.420003,-94.850001 66.96001,-94.850001 L 65.16001,-94.850001 L 65.16001,-92.530014 L 66.96001,-92.530014 M 67.429997,-98.180014 L 65.16001,-98.180014 L 65.16001,-95.669987 L 67.429997,-95.669987 Q 68.06001,-95.669987 68.4,-96.014991 Q 68.73999,-96.359994 68.73999,-96.930014 Q 68.73999,-97.230014 68.63999,-97.490007 Q 68.53999,-97.750001 68.229997,-97.965007 Q 67.920003,-98.180014 67.429997,-98.180014" />
|
||||||
|
<path d="M 67.520003,93.999999 Q 68.48999,93.999999 69.079997,94.570003 Q 69.670003,95.140006 69.670003,96.080013 Q 69.670003,96.740006 69.354997,97.160009 Q 69.03999,97.580013 68.33999,97.849999 C 69.013319,98.163324 69.349984,98.693326 69.349984,99.440006 C 69.347379,99.854892 69.227588,100.24011 68.924984,100.635009 C 68.774984,100.831678 68.549984,100.990011 68.249984,101.110009 C 67.949984,101.230007 67.596653,101.290006 67.18999,101.290006 L 64.229997,101.290006 L 64.229997,93.999999 L 67.520003,93.999999 M 66.96001,100.469986 Q 68.420003,100.469986 68.420003,99.309993 Q 68.420003,98.149999 66.96001,98.149999 L 65.16001,98.149999 L 65.16001,100.469986 L 66.96001,100.469986 M 67.429997,94.819986 L 65.16001,94.819986 L 65.16001,97.330013 L 67.429997,97.330013 Q 68.06001,97.330013 68.4,96.985009 Q 68.73999,96.640006 68.73999,96.069986 Q 68.73999,95.769986 68.63999,95.509993 Q 68.53999,95.249999 68.229997,95.034993 Q 67.920003,94.819986 67.429997,94.819986" />
|
||||||
|
<path d="M 112.63999,-96.809994 L 113.38999,-99.000001 L 114.429997,-99.000001 L 111.870003,-91.709994 L 110.670003,-91.709994 L 108.070003,-99.000001 L 109.06001,-99.000001 L 109.829997,-96.809994 L 112.63999,-96.809994 M 112.379997,-96.030014 L 110.06001,-96.030014 L 111.26001,-92.709994 L 112.379997,-96.030014" />
|
||||||
|
<path d="M 112.63999,96.190006 L 113.38999,93.999999 L 114.429997,93.999999 L 111.870003,101.290006 L 110.670003,101.290006 L 108.070003,93.999999 L 109.06001,93.999999 L 109.829997,96.190006 L 112.63999,96.190006 M 112.379997,96.969986 L 110.06001,96.969986 L 111.26001,100.290006 L 112.379997,96.969986" />
|
||||||
|
<path d="M -0.25,-89.0 L 0.25,-89.0 L 0.25,-74.416667 L 44.25,-74.416667 L 44.25,-89.0 L 44.75,-89.0 L 44.75,-74.416667 L 88.75,-74.416667 L 88.75,-89.0 L 89.25,-89.0 L 89.25,-74.416667 L 133.5,-74.416667 L 133.5,-73.916667 L 44.75,-73.916667 L 44.75,-59.583333 L 133.5,-59.583333 L 133.5,-59.083333 L 44.75,-59.083333 L 44.75,-44.75 L 133.5,-44.75 L 133.5,-44.25 L 0.0,-44.25 L 0.0,-44.5 L -0.25,-44.5 L -0.25,-89.0 M 0.25,-73.916667 L 0.25,-59.583333 L 44.25,-59.583333 L 44.25,-73.916667 L 0.25,-73.916667 M 0.25,-59.083333 L 0.25,-44.75 L 44.25,-44.75 L 44.25,-59.083333 L 0.25,-59.083333" />
|
||||||
|
<path d="M 2.0,-49.404997 L 2.936665,-49.404997 Q 3.396669,-49.404997 3.66167,-49.079997 Q 3.926671,-48.754997 3.926671,-48.18833 Q 3.926671,-47.621663 3.663336,-47.298329 Q 3.4,-46.974995 2.936665,-46.974995 L 2.0,-46.974995 L 2.0,-49.404997 M 2.310004,-49.131668 L 2.310004,-47.248334 L 2.883333,-47.248334 C 3.361951,-47.246599 3.618055,-47.575616 3.616667,-48.191661 C 3.618055,-48.802917 3.361951,-49.133751 2.883333,-49.131668 L 2.310004,-49.131668" />
|
||||||
|
<path d="M 4.563346,-48.298334 L 5.886675,-48.298334 L 5.886675,-48.024995 L 4.563346,-48.024995 L 4.563346,-47.248334 L 5.936675,-47.248334 L 5.936675,-46.974995 L 4.253342,-46.974995 L 4.253342,-49.404997 L 5.99668,-49.404997 L 5.99668,-49.131668 L 4.563346,-49.131668 L 4.563346,-48.298334" />
|
||||||
|
<path d="M 7.326682,-48.374995 C 7.624461,-48.452772 7.773351,-48.583885 7.773351,-48.768332 C 7.772135,-48.871586 7.745123,-48.957295 7.661681,-49.051666 C 7.580668,-49.14326 7.401029,-49.210064 7.153342,-49.208328 C 7.022237,-49.208328 6.909462,-49.191106 6.815017,-49.156662 C 6.720573,-49.122219 6.649461,-49.076109 6.601682,-49.018332 C 6.505255,-48.901739 6.467546,-48.78715 6.466678,-48.648334 L 6.466678,-48.631668 L 6.173351,-48.631668 C 6.175014,-48.896772 6.271678,-49.109337 6.393343,-49.229997 C 6.455565,-49.291108 6.53001,-49.341108 6.616678,-49.379997 C 6.79175,-49.459337 6.949389,-49.480106 7.133344,-49.481668 C 7.419802,-49.484098 7.661336,-49.404794 7.79668,-49.304997 C 7.865567,-49.253883 7.922233,-49.194437 7.966678,-49.12666 C 8.057303,-48.990237 8.081608,-48.868091 8.083344,-48.73833 C 8.083344,-48.589444 8.038345,-48.461666 7.948345,-48.354997 C 7.858346,-48.248327 7.731124,-48.17277 7.566678,-48.128326 L 6.956684,-47.965001 Q 6.736675,-47.904997 6.64668,-47.824995 Q 6.556684,-47.744992 6.556684,-47.604997 Q 6.556684,-47.421663 6.705013,-47.308328 Q 6.853342,-47.194992 7.100011,-47.194992 Q 7.390017,-47.194992 7.54668,-47.323329 Q 7.703342,-47.451666 7.706684,-47.68833 L 8.000011,-47.68833 Q 7.99668,-47.331668 7.763346,-47.133333 Q 7.530013,-46.934999 7.110015,-46.934999 Q 6.710015,-46.934999 6.478348,-47.126666 Q 6.24668,-47.318332 6.24668,-47.648334 Q 6.24668,-48.091661 6.723351,-48.215001 L 7.326682,-48.374995" />
|
||||||
|
<path d="M 8.850011,-46.974995 L 8.536675,-46.974995 L 8.536675,-49.404997 L 8.850011,-49.404997 L 8.850011,-46.974995" />
|
||||||
|
<path d="M 11.133355,-48.461659 C 11.133355,-48.67944 11.061688,-48.85833 10.918354,-48.998329 C 10.77502,-49.138328 10.592242,-49.208328 10.37002,-49.208328 C 10.236686,-49.208328 10.117797,-49.18555 10.013352,-49.139996 C 9.801683,-49.049061 9.689469,-48.90815 9.606684,-48.721663 C 9.525288,-48.533963 9.501412,-48.368255 9.500022,-48.198334 C 9.500022,-47.900555 9.5778,-47.661109 9.733355,-47.479997 C 9.888911,-47.298884 10.095577,-47.208328 10.353353,-47.208328 C 10.537793,-47.208328 10.692792,-47.253328 10.818349,-47.343327 C 10.943906,-47.433326 11.023351,-47.556104 11.056684,-47.711659 L 11.373351,-47.711659 C 11.331127,-47.464994 11.220571,-47.273884 11.041683,-47.13833 C 10.862795,-47.002776 10.634462,-46.934999 10.356684,-46.934999 C 10.178906,-46.934999 10.019463,-46.964442 9.878353,-47.023329 C 9.737243,-47.082216 9.625022,-47.156104 9.541688,-47.244992 C 9.458355,-47.333881 9.388911,-47.437216 9.333355,-47.554997 C 9.219814,-47.791948 9.192448,-48.000272 9.190017,-48.215001 C 9.190017,-48.58833 9.29335,-48.892773 9.500016,-49.128331 C 9.706682,-49.363889 9.974461,-49.481668 10.303353,-49.481668 C 10.625575,-49.481668 10.902243,-49.352776 11.133355,-49.094992 L 11.210015,-49.418332 L 11.406684,-49.418332 L 11.406684,-48.121663 L 10.393349,-48.121663 L 10.393349,-48.394992 L 11.133355,-48.394992 L 11.133355,-48.461659" />
|
||||||
|
<path d="M 13.706684,-46.974995 L 13.413346,-46.974995 L 13.413346,-48.961659 L 12.143349,-46.974995 L 11.806684,-46.974995 L 11.806684,-49.404997 L 12.100022,-49.404997 L 12.100022,-47.434999 L 13.356684,-49.404997 L 13.706684,-49.404997 L 13.706684,-46.974995" />
|
||||||
|
<path d="M 14.483355,-48.298334 L 15.806684,-48.298334 L 15.806684,-48.024995 L 14.483355,-48.024995 L 14.483355,-47.248334 L 15.856684,-47.248334 L 15.856684,-46.974995 L 14.173351,-46.974995 L 14.173351,-49.404997 L 15.916688,-49.404997 L 15.916688,-49.131668 L 14.483355,-49.131668 L 14.483355,-48.298334" />
|
||||||
|
<path d="M 16.283355,-49.404997 L 17.22002,-49.404997 Q 17.680024,-49.404997 17.945025,-49.079997 Q 18.210026,-48.754997 18.210026,-48.18833 Q 18.210026,-47.621663 17.946691,-47.298329 Q 17.683355,-46.974995 17.22002,-46.974995 L 16.283355,-46.974995 L 16.283355,-49.404997 M 16.593359,-49.131668 L 16.593359,-47.248334 L 17.166688,-47.248334 C 17.645306,-47.246599 17.90141,-47.575616 17.900022,-48.191661 C 17.90141,-48.802917 17.645306,-49.133751 17.166688,-49.131668 L 16.593359,-49.131668" />
|
||||||
|
<path d="M 20.540028,-49.404997 Q 20.863357,-49.404997 21.060026,-49.214996 Q 21.256695,-49.024995 21.256695,-48.711659 Q 21.256695,-48.491661 21.151693,-48.35166 Q 21.046691,-48.211659 20.813357,-48.121663 C 21.0378,-48.017222 21.150022,-47.840554 21.150022,-47.591661 C 21.149154,-47.453366 21.109223,-47.32496 21.008355,-47.193327 C 20.910612,-47.064471 20.698875,-46.972738 20.430024,-46.974995 L 19.443359,-46.974995 L 19.443359,-49.404997 L 20.540028,-49.404997 M 20.353364,-47.248334 Q 20.840028,-47.248334 20.840028,-47.634999 Q 20.840028,-48.021663 20.353364,-48.021663 L 19.753364,-48.021663 L 19.753364,-47.248334 L 20.353364,-47.248334 M 20.510026,-49.131668 L 19.753364,-49.131668 L 19.753364,-48.294992 L 20.510026,-48.294992 Q 20.72003,-48.294992 20.833361,-48.409993 Q 20.946691,-48.524995 20.946691,-48.715001 Q 20.946691,-48.815001 20.913357,-48.901666 Q 20.880024,-48.98833 20.776693,-49.059999 Q 20.673362,-49.131668 20.510026,-49.131668" />
|
||||||
|
<path d="M 22.603364,-48.451666 L 23.516699,-46.974995 L 23.146691,-46.974995 L 22.453364,-48.158328 L 21.740028,-46.974995 L 21.356695,-46.974995 L 22.293359,-48.451666 L 22.293359,-49.404997 L 22.603364,-49.404997 L 22.603364,-48.451666" />
|
||||||
|
<path d="M 24.180035,-49.058328 L 23.833366,-49.058328 L 23.833366,-49.404997 L 24.180035,-49.404997 L 24.180035,-49.058328" />
|
||||||
|
<path d="M 24.180035,-47.658328 L 23.833366,-47.658328 L 23.833366,-48.004997 L 24.180035,-48.004997 L 24.180035,-47.658328" />
|
||||||
|
<path d="M 2.0,-53.744995 L 2.0,-57.389998 L 2.375,-57.389998 L 2.375,-57.055005 Q 2.670003,-57.505005 3.204997,-57.505005 Q 3.725,-57.505005 4.035002,-57.1125 Q 4.345003,-56.719995 4.345003,-56.069995 Q 4.345003,-55.435002 4.045003,-55.064998 Q 3.745003,-54.694995 3.225,-54.694995 Q 2.7,-54.694995 2.415007,-55.124992 L 2.415007,-53.744995 L 2.0,-53.744995 M 3.145003,-55.085002 Q 3.495003,-55.085002 3.702498,-55.364998 Q 3.909994,-55.644995 3.909994,-56.114998 Q 3.909994,-56.560002 3.697494,-56.8375 Q 3.484994,-57.114998 3.145003,-57.114998 Q 2.815007,-57.114998 2.615007,-56.8375 Q 2.415007,-56.560002 2.415007,-56.1 Q 2.415007,-55.639998 2.615007,-55.3625 Q 2.815007,-55.085002 3.145003,-55.085002" />
|
||||||
|
<path d="M 6.854997,-57.389998 L 6.854997,-54.769995 L 6.440007,-54.769995 L 6.440007,-56.255005 Q 6.440007,-56.655005 6.245003,-56.897502 Q 6.05,-57.139998 5.725,-57.139998 Q 5.475,-57.139998 5.330005,-57.0 Q 5.18501,-56.860002 5.18501,-56.624992 L 5.18501,-54.769995 L 4.770003,-54.769995 L 4.770003,-56.789998 Q 4.770003,-57.114998 4.997502,-57.310002 Q 5.225,-57.505005 5.604997,-57.505005 Q 5.895003,-57.505005 6.095003,-57.395003 Q 6.295003,-57.285002 6.479997,-57.024992 L 6.479997,-57.389998 L 6.854997,-57.389998" />
|
||||||
|
<path d="M 8.904981,-53.744995 L 8.484977,-53.744995 L 8.484977,-57.389998 L 8.904981,-57.389998 L 8.904981,-53.744995" />
|
||||||
|
<path d="M 11.699984,-53.744995 L 11.284977,-53.744995 L 11.284977,-55.099992 Q 11.019987,-54.694995 10.479981,-54.694995 Q 9.969987,-54.694995 9.662484,-55.072493 Q 9.354981,-55.449992 9.354981,-56.074992 Q 9.354981,-56.739998 9.659977,-57.122502 Q 9.964974,-57.505005 10.494987,-57.505005 Q 10.764974,-57.505005 10.962476,-57.397502 Q 11.159977,-57.289998 11.329981,-57.044995 L 11.329981,-57.389998 L 11.699984,-57.389998 L 11.699984,-53.744995 M 10.549984,-55.085002 Q 10.884977,-55.085002 11.084977,-55.3625 Q 11.284977,-55.639998 11.284977,-56.110002 Q 11.284977,-56.564998 11.084977,-56.839998 Q 10.884977,-57.114998 10.554981,-57.114998 Q 10.209977,-57.114998 9.999976,-56.8375 Q 9.789974,-56.560002 9.789974,-56.099992 Q 9.789974,-55.644995 9.999976,-55.364998 Q 10.209977,-55.085002 10.549984,-55.085002" />
|
||||||
|
<path d="M 13.294987,-54.864998 L 13.294987,-57.389998 L 13.734977,-57.389998 L 13.734977,-53.844995 L 13.444987,-53.844995 Q 13.329981,-54.255005 13.174984,-54.370003 Q 13.019987,-54.485002 12.509977,-54.549992 L 12.509977,-54.864998 L 13.294987,-54.864998" />
|
||||||
|
<path d="M 15.029981,-55.074992 L 15.469987,-55.074992 Q 15.519987,-54.230005 16.184977,-54.230005 Q 16.484977,-54.230005 16.684977,-54.420003 Q 16.884977,-54.610002 16.884977,-54.894995 Q 16.884977,-55.319995 16.404981,-55.594995 L 15.944987,-55.855005 Q 15.389974,-56.169995 15.184977,-56.4875 Q 14.979981,-56.805005 14.949984,-57.389998 L 17.309977,-57.389998 L 17.309977,-56.955005 L 15.444987,-56.955005 Q 15.479981,-56.730005 15.619979,-56.564998 Q 15.759977,-56.399992 16.084977,-56.224992 L 16.584977,-55.955005 Q 17.334977,-55.544995 17.334977,-54.885002 Q 17.334977,-54.424992 17.019979,-54.134993 Q 16.704981,-53.844995 16.199984,-53.844995 Q 15.064974,-53.844995 15.029981,-55.074992" />
|
||||||
|
<path d="M 18.234977,-54.989998 Q 18.244971,-54.644995 18.387476,-54.4375 Q 18.529981,-54.230005 18.909977,-54.230005 Q 19.199984,-54.230005 19.367481,-54.392505 Q 19.534977,-54.555005 19.534977,-54.835002 Q 19.534977,-55.160002 19.337476,-55.270003 Q 19.139974,-55.380005 18.664974,-55.389998 L 18.664974,-55.764998 L 18.719971,-55.764998 L 18.904981,-55.760002 Q 19.639974,-55.760002 19.639974,-56.405005 Q 19.639974,-56.739998 19.444979,-56.927498 Q 19.249984,-57.114998 18.904981,-57.114998 Q 18.544971,-57.114998 18.364974,-56.929997 Q 18.184977,-56.744995 18.159977,-56.360002 L 17.719971,-56.360002 Q 17.799984,-57.505005 18.889974,-57.505005 Q 19.434977,-57.505005 19.762476,-57.205005 Q 20.089974,-56.905005 20.089974,-56.399992 Q 20.089974,-56.060002 19.949976,-55.8625 Q 19.809977,-55.664998 19.489974,-55.555005 Q 19.984977,-55.360002 19.984977,-54.819995 Q 19.984977,-54.364998 19.697477,-54.104997 Q 19.409977,-53.844995 18.904981,-53.844995 Q 17.819971,-53.844995 17.794971,-54.989998 L 18.234977,-54.989998" />
|
||||||
|
<path d="M 22.834977,-53.744995 L 22.419971,-53.744995 L 22.419971,-55.099992 Q 22.154981,-54.694995 21.614974,-54.694995 Q 21.104981,-54.694995 20.797477,-55.072493 Q 20.489974,-55.449992 20.489974,-56.074992 Q 20.489974,-56.739998 20.794971,-57.122502 Q 21.099968,-57.505005 21.629981,-57.505005 Q 21.899968,-57.505005 22.097469,-57.397502 Q 22.294971,-57.289998 22.464974,-57.044995 L 22.464974,-57.389998 L 22.834977,-57.389998 L 22.834977,-53.744995 M 21.684977,-55.085002 Q 22.019971,-55.085002 22.219971,-55.3625 Q 22.419971,-55.639998 22.419971,-56.110002 Q 22.419971,-56.564998 22.219971,-56.839998 Q 22.019971,-57.114998 21.689974,-57.114998 Q 21.344971,-57.114998 21.134969,-56.8375 Q 20.924968,-56.560002 20.924968,-56.099992 Q 20.924968,-55.644995 21.134969,-55.364998 Q 21.344971,-55.085002 21.684977,-55.085002" />
|
||||||
|
<path d="M 7.86499,-54.769995 L 7.449984,-54.769995 L 7.449984,-57.389998 L 7.86499,-57.389998 L 7.86499,-54.769995" />
|
||||||
|
<path d="M 7.86499,-53.744995 L 7.444987,-53.744995 L 7.444987,-54.269995 L 7.86499,-54.269995 L 7.86499,-53.744995" />
|
||||||
|
<path d="M 2.0,-64.256668 L 2.936665,-64.256668 Q 3.396669,-64.256668 3.66167,-63.931668 Q 3.926671,-63.606668 3.926671,-63.040001 Q 3.926671,-62.473334 3.663336,-62.15 Q 3.4,-61.826666 2.936665,-61.826666 L 2.0,-61.826666 L 2.0,-64.256668 M 2.310004,-63.983339 L 2.310004,-62.100005 L 2.883333,-62.100005 C 3.361951,-62.09827 3.618055,-62.427287 3.616667,-63.043332 C 3.618055,-63.654588 3.361951,-63.985422 2.883333,-63.983339 L 2.310004,-63.983339" />
|
||||||
|
<path d="M 5.566667,-63.526666 L 5.816667,-64.256668 L 6.163336,-64.256668 L 5.310004,-61.826666 L 4.910004,-61.826666 L 4.043338,-64.256668 L 4.37334,-64.256668 L 4.630002,-63.526666 L 5.566667,-63.526666 M 5.480002,-63.266672 L 4.706673,-63.266672 L 5.106673,-62.159999 L 5.480002,-63.266672" />
|
||||||
|
<path d="M 7.356673,-62.100005 L 8.153331,-62.100005 L 8.153331,-61.826666 L 6.246669,-61.826666 L 6.246669,-62.100005 L 7.046669,-62.100005 L 7.046669,-64.256668 L 7.356673,-64.256668 L 7.356673,-62.100005" />
|
||||||
|
<path d="M 8.769998,-63.150005 L 10.093327,-63.150005 L 10.093327,-62.876666 L 8.769998,-62.876666 L 8.769998,-62.100005 L 10.143327,-62.100005 L 10.143327,-61.826666 L 8.459994,-61.826666 L 8.459994,-64.256668 L 10.203331,-64.256668 L 10.203331,-63.983339 L 8.769998,-63.983339 L 8.769998,-63.150005" />
|
||||||
|
<path d="M 10.916667,-62.509999 L 10.569998,-62.509999 L 10.569998,-62.856668 L 10.916667,-62.856668 L 10.916667,-62.509999" />
|
||||||
|
<path d="M 10.916667,-63.909999 L 10.569998,-63.909999 L 10.569998,-64.256668 L 10.916667,-64.256668 L 10.916667,-63.909999" />
|
||||||
|
<path d="M 2.079997,-69.858325 L 2.520003,-69.858325 Q 2.570003,-69.013338 3.234994,-69.013338 Q 3.534994,-69.013338 3.734994,-69.203337 Q 3.934994,-69.393335 3.934994,-69.678328 Q 3.934994,-70.103328 3.454997,-70.378328 L 2.995003,-70.638338 Q 2.43999,-70.953328 2.234994,-71.270833 Q 2.029997,-71.588338 2.0,-72.173332 L 4.359994,-72.173332 L 4.359994,-71.738338 L 2.495003,-71.738338 Q 2.529997,-71.513338 2.669995,-71.348332 Q 2.809994,-71.183325 3.134994,-71.008325 L 3.634994,-70.738338 Q 4.384994,-70.328328 4.384994,-69.668335 Q 4.384994,-69.208325 4.069995,-68.918327 Q 3.754997,-68.628328 3.25,-68.628328 Q 2.11499,-68.628328 2.079997,-69.858325" />
|
||||||
|
<path d="M 4.825,-70.458325 Q 4.825,-71.353328 5.119995,-71.820833 Q 5.41499,-72.288338 5.984994,-72.288338 Q 6.55,-72.288338 6.847494,-71.825838 Q 7.144987,-71.363338 7.144987,-70.488338 Q 7.144987,-68.628328 5.984994,-68.628328 Q 5.819987,-68.628328 5.672494,-68.668327 Q 5.525,-68.708325 5.364998,-68.828328 Q 5.204997,-68.948332 5.089998,-69.140828 Q 4.975,-69.333325 4.9,-69.673332 Q 4.825,-70.013338 4.825,-70.458325 M 6.694987,-70.448332 Q 6.694987,-71.203328 6.519987,-71.56333 Q 6.344987,-71.923332 5.975,-71.923332 Q 5.275,-71.923332 5.275,-70.463338 Q 5.275,-69.018335 5.984994,-69.018335 Q 6.694987,-69.018335 6.694987,-70.448332" />
|
||||||
|
<path d="M 7.63999,-69.858325 L 8.079997,-69.858325 Q 8.129997,-69.013338 8.794987,-69.013338 Q 9.094987,-69.013338 9.294987,-69.203337 Q 9.494987,-69.393335 9.494987,-69.678328 Q 9.494987,-70.103328 9.01499,-70.378328 L 8.554997,-70.638338 Q 7.999984,-70.953328 7.794987,-71.270833 Q 7.58999,-71.588338 7.559994,-72.173332 L 9.919987,-72.173332 L 9.919987,-71.738338 L 8.054997,-71.738338 Q 8.08999,-71.513338 8.229989,-71.348332 Q 8.369987,-71.183325 8.694987,-71.008325 L 9.194987,-70.738338 Q 9.944987,-70.328328 9.944987,-69.668335 Q 9.944987,-69.208325 9.629989,-68.918327 Q 9.31499,-68.628328 8.809994,-68.628328 Q 7.674984,-68.628328 7.63999,-69.858325" />
|
||||||
|
<path d="M 12.549984,-68.628328 L 10.719987,-68.628328 L 10.454981,-70.558325 L 10.859994,-70.558325 Q 11.01499,-70.373332 11.159985,-70.30083 Q 11.304981,-70.228328 11.509994,-70.228328 Q 11.86499,-70.228328 12.074992,-70.455827 Q 12.284994,-70.683325 12.284994,-71.078328 Q 12.284994,-71.458325 12.07749,-71.678328 Q 11.869987,-71.898332 11.509994,-71.898332 Q 10.93999,-71.898332 10.784994,-71.303328 L 10.344987,-71.303328 Q 10.359994,-71.388338 10.369987,-71.433333 Q 10.379981,-71.478328 10.422485,-71.595833 Q 10.46499,-71.713338 10.512492,-71.790837 Q 10.559994,-71.868335 10.65249,-71.970833 Q 10.744987,-72.073332 10.857487,-72.135832 Q 10.969987,-72.198332 11.142489,-72.243335 Q 11.31499,-72.288338 11.519987,-72.288338 Q 12.054981,-72.288338 12.394987,-71.933333 Q 12.734994,-71.578328 12.734994,-71.018335 Q 12.734994,-70.493335 12.417489,-70.165837 Q 12.099984,-69.838338 11.58999,-69.838338 Q 11.229981,-69.838338 10.934994,-70.053328 L 11.074984,-69.063338 L 12.549984,-69.063338 L 12.549984,-68.628328" />
|
||||||
|
<path d="M 14.384977,-70.613338 L 13.179981,-70.613338 L 13.179981,-70.973332 L 14.384977,-70.973332 L 14.384977,-70.613338" />
|
||||||
|
<path d="M 14.829997,-70.458325 Q 14.829997,-71.353328 15.124992,-71.820833 Q 15.419987,-72.288338 15.98999,-72.288338 Q 16.554997,-72.288338 16.85249,-71.825838 Q 17.149984,-71.363338 17.149984,-70.488338 Q 17.149984,-68.628328 15.98999,-68.628328 Q 15.824984,-68.628328 15.67749,-68.668327 Q 15.529997,-68.708325 15.369995,-68.828328 Q 15.209994,-68.948332 15.094995,-69.140828 Q 14.979997,-69.333325 14.904997,-69.673332 Q 14.829997,-70.013338 14.829997,-70.458325 M 16.699984,-70.448332 Q 16.699984,-71.203328 16.524984,-71.56333 Q 16.349984,-71.923332 15.979997,-71.923332 Q 15.279997,-71.923332 15.279997,-70.463338 Q 15.279997,-69.018335 15.98999,-69.018335 Q 16.699984,-69.018335 16.699984,-70.448332" />
|
||||||
|
<path d="M 19.774984,-68.628328 L 17.944987,-68.628328 L 17.679981,-70.558325 L 18.084994,-70.558325 Q 18.23999,-70.373332 18.384985,-70.30083 Q 18.529981,-70.228328 18.734994,-70.228328 Q 19.08999,-70.228328 19.299992,-70.455827 Q 19.509994,-70.683325 19.509994,-71.078328 Q 19.509994,-71.458325 19.30249,-71.678328 Q 19.094987,-71.898332 18.734994,-71.898332 Q 18.16499,-71.898332 18.009994,-71.303328 L 17.569987,-71.303328 Q 17.584994,-71.388338 17.594987,-71.433333 Q 17.604981,-71.478328 17.647485,-71.595833 Q 17.68999,-71.713338 17.737492,-71.790837 Q 17.784994,-71.868335 17.87749,-71.970833 Q 17.969987,-72.073332 18.082487,-72.135832 Q 18.194987,-72.198332 18.367489,-72.243335 Q 18.53999,-72.288338 18.744987,-72.288338 Q 19.279981,-72.288338 19.619987,-71.933333 Q 19.959994,-71.578328 19.959994,-71.018335 Q 19.959994,-70.493335 19.642489,-70.165837 Q 19.324984,-69.838338 18.81499,-69.838338 Q 18.454981,-69.838338 18.159994,-70.053328 L 18.299984,-69.063338 L 19.774984,-69.063338 L 19.774984,-68.628328" />
|
||||||
|
<path d="M 21.609977,-70.613338 L 20.404981,-70.613338 L 20.404981,-70.973332 L 21.609977,-70.973332 L 21.609977,-70.613338" />
|
||||||
|
<path d="M 22.08999,-69.858325 L 22.529997,-69.858325 Q 22.579997,-69.013338 23.244987,-69.013338 Q 23.544987,-69.013338 23.744987,-69.203337 Q 23.944987,-69.393335 23.944987,-69.678328 Q 23.944987,-70.103328 23.46499,-70.378328 L 23.004997,-70.638338 Q 22.449984,-70.953328 22.244987,-71.270833 Q 22.03999,-71.588338 22.009994,-72.173332 L 24.369987,-72.173332 L 24.369987,-71.738338 L 22.504997,-71.738338 Q 22.53999,-71.513338 22.679989,-71.348332 Q 22.819987,-71.183325 23.144987,-71.008325 L 23.644987,-70.738338 Q 24.394987,-70.328328 24.394987,-69.668335 Q 24.394987,-69.208325 24.079989,-68.918327 Q 23.76499,-68.628328 23.259994,-68.628328 Q 22.124984,-68.628328 22.08999,-69.858325" />
|
||||||
|
<path d="M 25.294987,-69.773332 Q 25.304981,-69.428328 25.447485,-69.220833 Q 25.58999,-69.013338 25.969987,-69.013338 Q 26.259994,-69.013338 26.42749,-69.175838 Q 26.594987,-69.338338 26.594987,-69.618335 Q 26.594987,-69.943335 26.397485,-70.053337 Q 26.199984,-70.163338 25.724984,-70.173332 L 25.724984,-70.548332 L 25.779981,-70.548332 L 25.96499,-70.543335 Q 26.699984,-70.543335 26.699984,-71.188338 Q 26.699984,-71.523332 26.504989,-71.710832 Q 26.309994,-71.898332 25.96499,-71.898332 Q 25.604981,-71.898332 25.424984,-71.71333 Q 25.244987,-71.528328 25.219987,-71.143335 L 24.779981,-71.143335 Q 24.859994,-72.288338 25.949984,-72.288338 Q 26.494987,-72.288338 26.822485,-71.988338 Q 27.149984,-71.688338 27.149984,-71.183325 Q 27.149984,-70.843335 27.009985,-70.645833 Q 26.869987,-70.448332 26.549984,-70.338338 Q 27.044987,-70.143335 27.044987,-69.603328 Q 27.044987,-69.148332 26.757487,-68.88833 Q 26.469987,-68.628328 25.96499,-68.628328 Q 24.879981,-68.628328 24.854981,-69.773332 L 25.294987,-69.773332" />
|
||||||
|
<path d="M 3.153331,-78.041661 C 3.45111,-78.119439 3.6,-78.250552 3.6,-78.434999 C 3.598784,-78.538252 3.571772,-78.623962 3.48833,-78.718332 C 3.407317,-78.809926 3.227678,-78.876731 2.979991,-78.874995 C 2.848886,-78.874995 2.736111,-78.857773 2.641667,-78.823329 C 2.547222,-78.788885 2.47611,-78.742775 2.428331,-78.684999 C 2.331905,-78.568405 2.294195,-78.453817 2.293327,-78.315001 L 2.293327,-78.298334 L 2.0,-78.298334 C 2.002221,-78.431668 2.023886,-78.549445 2.064996,-78.651666 C 2.147736,-78.859059 2.26947,-78.965934 2.443327,-79.046663 C 2.618399,-79.126003 2.776038,-79.146772 2.959994,-79.148334 C 3.246451,-79.150765 3.487986,-79.071461 3.623329,-78.971663 C 3.692217,-78.920549 3.748882,-78.861104 3.793327,-78.793327 C 3.883952,-78.656904 3.908257,-78.534757 3.909994,-78.404997 C 3.909994,-78.256111 3.864994,-78.128333 3.774995,-78.021663 C 3.684995,-77.914994 3.557773,-77.839437 3.393327,-77.794992 L 2.783333,-77.631668 Q 2.563325,-77.571663 2.473329,-77.491661 Q 2.383333,-77.411659 2.383333,-77.271663 Q 2.383333,-77.08833 2.531662,-76.974995 Q 2.679991,-76.861659 2.92666,-76.861659 Q 3.216667,-76.861659 3.373329,-76.989996 Q 3.529991,-77.118332 3.533333,-77.354997 L 3.82666,-77.354997 Q 3.823329,-76.998334 3.589996,-76.8 Q 3.356662,-76.601666 2.936665,-76.601666 Q 2.536665,-76.601666 2.304997,-76.793332 Q 2.073329,-76.984999 2.073329,-77.315001 Q 2.073329,-77.758328 2.55,-77.881668 L 3.153331,-78.041661" />
|
||||||
|
<path d="M 6.209994,-77.394992 C 6.114435,-76.866108 5.802214,-76.601666 5.273329,-76.601666 C 5.104434,-76.601666 4.952765,-76.630554 4.818321,-76.68833 C 4.683878,-76.746106 4.577212,-76.818884 4.498324,-76.906662 C 4.419436,-76.994441 4.353324,-77.097219 4.299989,-77.214996 C 4.190888,-77.452285 4.165756,-77.661041 4.163325,-77.884999 C 4.165583,-78.10469 4.191617,-78.308089 4.298324,-78.543332 C 4.350548,-78.659999 4.416104,-78.761664 4.494993,-78.848329 C 4.573881,-78.934993 4.679436,-79.006661 4.811659,-79.06333 C 4.943882,-79.12 5.093327,-79.148334 5.259994,-79.148334 C 5.853324,-79.148334 6.186657,-78.827223 6.259994,-78.184999 L 5.939996,-78.184999 C 5.902214,-78.420553 5.82999,-78.594441 5.723324,-78.706662 C 5.616658,-78.818884 5.463325,-78.874995 5.263325,-78.874995 C 5.021101,-78.874995 4.828879,-78.78555 4.686659,-78.606662 C 4.544439,-78.427774 4.473329,-78.186109 4.473329,-77.881668 C 4.473329,-77.570557 4.541661,-77.325 4.678326,-77.144998 C 4.81499,-76.964996 5.0011,-76.874995 5.236654,-76.874995 C 5.626455,-76.874993 5.819093,-77.048336 5.893327,-77.394992 L 6.209994,-77.394992" />
|
||||||
|
<path d="M 7.899989,-78.341661 L 8.149989,-79.071663 L 8.496658,-79.071663 L 7.643327,-76.641661 L 7.243327,-76.641661 L 6.37666,-79.071663 L 6.706662,-79.071663 L 6.963325,-78.341661 L 7.899989,-78.341661 M 7.813325,-78.081668 L 7.039996,-78.081668 L 7.439996,-76.974995 L 7.813325,-78.081668" />
|
||||||
|
<path d="M 9.039996,-76.641661 L 8.729991,-76.641661 L 8.729991,-79.071663 L 10.239996,-79.071663 L 10.239996,-78.798334 L 9.039996,-78.798334 L 9.039996,-76.641661" />
|
||||||
|
<path d="M 10.833333,-77.965001 L 12.156662,-77.965001 L 12.156662,-77.691661 L 10.833333,-77.691661 L 10.833333,-76.915001 L 12.206662,-76.915001 L 12.206662,-76.641661 L 10.523329,-76.641661 L 10.523329,-79.071663 L 12.266667,-79.071663 L 12.266667,-78.798334 L 10.833333,-78.798334 L 10.833333,-77.965001" />
|
||||||
|
<path d="M 12.980002,-77.324995 L 12.633333,-77.324995 L 12.633333,-77.671663 L 12.980002,-77.671663 L 12.980002,-77.324995" />
|
||||||
|
<path d="M 12.980002,-78.724995 L 12.633333,-78.724995 L 12.633333,-79.071663 L 12.980002,-79.071663 L 12.980002,-78.724995" />
|
||||||
|
<path d="M 2.78501,-84.539168 L 2.78501,-87.064168 L 3.225,-87.064168 L 3.225,-83.519165 L 2.93501,-83.519165 Q 2.820003,-83.929175 2.665007,-84.044173 Q 2.51001,-84.159172 2.0,-84.224162 L 2.0,-84.539168 L 2.78501,-84.539168" />
|
||||||
|
<path d="M 6.815007,-84.539168 L 6.815007,-87.064168 L 7.254997,-87.064168 L 7.254997,-83.519165 L 6.965007,-83.519165 Q 6.85,-83.929175 6.695003,-84.044173 Q 6.540007,-84.159172 6.029997,-84.224162 L 6.029997,-84.539168 L 6.815007,-84.539168" />
|
||||||
|
<path d="M 5.190007,-86.544165 L 4.670003,-86.544165 L 4.670003,-87.064168 L 5.190007,-87.064168 L 5.190007,-86.544165" />
|
||||||
|
<path d="M 5.190007,-84.444165 L 4.670003,-84.444165 L 4.670003,-84.964168 L 5.190007,-84.964168 L 5.190007,-84.444165" />
|
||||||
|
<path d="M 52.2,-47.24165 L 51.319987,-47.24165 L 51.319987,-53.201644 L 47.509993,-47.24165 L 46.5,-47.24165 L 46.5,-54.531657 L 47.380013,-54.531657 L 47.380013,-48.621663 L 51.15,-54.531657 L 52.2,-54.531657 L 52.2,-47.24165" />
|
||||||
|
<path d="M 54.069987,-52.19165 L 57.930013,-52.19165 Q 57.930013,-49.14165 55.6,-49.14165 Q 54.509993,-49.14165 53.854997,-49.91665 Q 53.2,-50.69165 53.2,-51.981657 Q 53.2,-53.271663 53.840007,-54.016667 Q 54.480013,-54.76167 55.580013,-54.76167 Q 56.480013,-54.76167 57.070003,-54.281657 Q 57.659993,-53.801644 57.819987,-52.94165 L 56.980013,-52.94165 Q 56.630013,-53.99165 55.609993,-53.99165 Q 54.9,-53.99165 54.495003,-53.50166 Q 54.090007,-53.01167 54.069987,-52.19165 M 57.040007,-51.51167 L 54.090007,-51.51167 Q 54.140007,-50.781657 54.55,-50.346663 Q 54.959993,-49.91167 55.58999,-49.91167 Q 56.219987,-49.91167 56.629997,-50.371663 Q 57.040007,-50.831657 57.040007,-51.51167" />
|
||||||
|
<path d="M 58.75,-49.29165 L 58.75,-54.531657 L 59.590007,-54.531657 L 59.590007,-51.24165 Q 59.590007,-50.671663 59.95,-50.271663 Q 60.309993,-49.871663 60.819987,-49.871663 Q 61.280013,-49.871663 61.53501,-50.146663 Q 61.790007,-50.421663 61.790007,-50.921663 L 61.790007,-54.531657 L 62.630013,-54.531657 L 62.630013,-51.24165 Q 62.630013,-50.671663 62.990007,-50.271663 Q 63.35,-49.871663 63.859993,-49.871663 Q 64.319987,-49.871663 64.575,-50.146663 Q 64.830013,-50.421663 64.830013,-50.921663 L 64.830013,-54.531657 L 65.669987,-54.531657 L 65.669987,-50.601644 Q 65.669987,-49.89165 65.275,-49.51665 Q 64.880013,-49.14165 64.15,-49.14165 Q 63.630013,-49.14165 63.270003,-49.321647 Q 62.909993,-49.501644 62.540007,-49.94165 Q 62.1,-49.14165 61.130013,-49.14165 Q 60.609993,-49.14165 60.234993,-49.35166 Q 59.859993,-49.56167 59.519987,-50.031657 L 59.519987,-49.29165 L 58.75,-49.29165" />
|
||||||
|
<path d="M 66.75,-50.84165 L 67.589974,-50.84165 Q 67.639974,-50.36167 67.939974,-50.13667 Q 68.239974,-49.91167 68.819987,-49.91167 Q 69.37998,-49.91167 69.684977,-50.11167 Q 69.989974,-50.31167 69.989974,-50.69165 L 69.989974,-50.91167 Q 69.989974,-51.171663 69.799984,-51.30166 Q 69.609993,-51.431657 69.119987,-51.49165 Q 68.759993,-51.54165 68.619987,-51.561654 Q 68.47998,-51.581657 68.17998,-51.631657 Q 67.87998,-51.681657 67.759977,-51.71665 Q 67.639974,-51.751644 67.414974,-51.826644 Q 67.189974,-51.901644 67.094987,-51.981657 Q 67.0,-52.06167 66.859993,-52.18667 Q 66.719987,-52.31167 66.66499,-52.456657 Q 66.609993,-52.601644 66.56499,-52.79165 Q 66.519987,-52.981657 66.519987,-53.21167 Q 66.519987,-53.921663 66.984993,-54.341667 Q 67.45,-54.76167 68.239974,-54.76167 Q 69.17998,-54.76167 70.019987,-53.99165 Q 70.069987,-54.39165 70.274984,-54.57666 Q 70.47998,-54.76167 70.87998,-54.76167 Q 71.1,-54.76167 71.45,-54.671663 L 71.45,-54.04165 Q 71.359993,-54.06167 71.269987,-54.06167 Q 70.819987,-54.06167 70.819987,-53.651644 L 70.819987,-50.571663 Q 70.819987,-49.871663 70.319987,-49.506657 Q 69.819987,-49.14165 68.85,-49.14165 Q 66.809993,-49.14165 66.75,-50.84165 M 69.989974,-52.881657 Q 69.989974,-53.301644 69.549984,-53.66665 Q 69.109993,-54.031657 68.419987,-54.031657 Q 67.92998,-54.031657 67.659977,-53.811654 Q 67.389974,-53.59165 67.389974,-53.19165 Q 67.389974,-52.79165 67.689974,-52.56665 Q 67.989974,-52.34165 68.359977,-52.281657 Q 68.72998,-52.221663 69.234977,-52.141667 Q 69.739974,-52.06167 69.989974,-51.94165 L 69.989974,-52.881657" />
|
||||||
|
<path d="M 74.65,-49.901644 L 75.530013,-49.901644 Q 75.630013,-48.21167 76.959993,-48.21167 Q 77.559993,-48.21167 77.959993,-48.591667 Q 78.359993,-48.971663 78.359993,-49.54165 Q 78.359993,-50.39165 77.4,-50.94165 L 76.480013,-51.46167 Q 75.369987,-52.09165 74.959993,-52.72666 Q 74.55,-53.36167 74.490007,-54.531657 L 79.209993,-54.531657 L 79.209993,-53.66167 L 75.480013,-53.66167 Q 75.55,-53.21167 75.829997,-52.881657 Q 76.109993,-52.551644 76.759993,-52.201644 L 77.759993,-51.66167 Q 79.259993,-50.84165 79.259993,-49.521663 Q 79.259993,-48.601644 78.629997,-48.021647 Q 78.0,-47.44165 76.990007,-47.44165 Q 74.719987,-47.44165 74.65,-49.901644" />
|
||||||
|
<path d="M 81.059993,-49.731657 Q 81.07998,-49.04165 81.36499,-48.62666 Q 81.65,-48.21167 82.409993,-48.21167 Q 82.990007,-48.21167 83.325,-48.53667 Q 83.659993,-48.86167 83.659993,-49.421663 Q 83.659993,-50.071663 83.26499,-50.291667 Q 82.869987,-50.51167 81.919987,-50.531657 L 81.919987,-51.281657 L 82.02998,-51.281657 L 82.4,-51.271663 Q 83.869987,-51.271663 83.869987,-52.56167 Q 83.869987,-53.231657 83.479997,-53.606657 Q 83.090007,-53.981657 82.4,-53.981657 Q 81.67998,-53.981657 81.319987,-53.611654 Q 80.959993,-53.24165 80.909993,-52.471663 L 80.02998,-52.471663 Q 80.190007,-54.76167 82.369987,-54.76167 Q 83.459993,-54.76167 84.11499,-54.16167 Q 84.769987,-53.56167 84.769987,-52.551644 Q 84.769987,-51.871663 84.48999,-51.47666 Q 84.209993,-51.081657 83.569987,-50.86167 Q 84.559993,-50.471663 84.559993,-49.39165 Q 84.559993,-48.481657 83.984993,-47.961654 Q 83.409993,-47.44165 82.4,-47.44165 Q 80.22998,-47.44165 80.17998,-49.731657 L 81.059993,-49.731657" />
|
||||||
|
<path d="M 91.709993,-51.44165 Q 93.05,-51.79165 93.05,-52.621663 Q 93.05,-52.851644 92.975,-53.056657 Q 92.9,-53.26167 92.71499,-53.471663 Q 92.52998,-53.681657 92.13999,-53.811654 Q 91.75,-53.94165 91.189974,-53.94165 Q 90.6,-53.94165 90.175,-53.786654 Q 89.75,-53.631657 89.534993,-53.371663 Q 89.319987,-53.11167 89.224984,-52.841667 Q 89.12998,-52.571663 89.12998,-52.26167 L 89.12998,-52.21167 L 88.25,-52.21167 Q 88.259993,-52.81167 88.444987,-53.271663 Q 88.62998,-53.731657 88.909977,-54.006657 Q 89.189974,-54.281657 89.57998,-54.456657 Q 89.969987,-54.631657 90.344987,-54.696663 Q 90.719987,-54.76167 91.12998,-54.76167 Q 91.77998,-54.76167 92.294987,-54.61167 Q 92.809993,-54.46167 93.119987,-54.231657 Q 93.42998,-54.001644 93.62998,-53.696647 Q 93.82998,-53.39165 93.90498,-53.106657 Q 93.97998,-52.821663 93.97998,-52.531657 Q 93.97998,-51.86167 93.574984,-51.381657 Q 93.169987,-50.901644 92.42998,-50.701644 L 90.6,-50.21167 Q 89.939974,-50.031657 89.669987,-49.79165 Q 89.4,-49.551644 89.4,-49.131657 Q 89.4,-48.581657 89.844987,-48.24165 Q 90.289974,-47.901644 91.02998,-47.901644 Q 91.9,-47.901644 92.369987,-48.286654 Q 92.839974,-48.671663 92.85,-49.381657 L 93.72998,-49.381657 Q 93.719987,-48.31167 93.019987,-47.716667 Q 92.319987,-47.121663 91.059993,-47.121663 Q 89.859993,-47.121663 89.16499,-47.696663 Q 88.469987,-48.271663 88.469987,-49.26167 Q 88.469987,-50.59165 89.9,-50.96167 L 91.709993,-51.44165" />
|
||||||
|
<path d="M 96.989974,-49.29165 L 96.12998,-49.29165 L 96.12998,-47.851644 L 95.299967,-47.851644 L 95.299967,-49.29165 L 94.589974,-49.29165 L 94.589974,-49.971663 L 95.299967,-49.971663 L 95.299967,-53.931657 Q 95.299967,-54.331657 95.559977,-54.546663 Q 95.819987,-54.76167 96.309961,-54.76167 Q 96.599967,-54.76167 96.989974,-54.69165 L 96.989974,-53.99165 Q 96.839974,-54.031657 96.589974,-54.031657 Q 96.319987,-54.031657 96.224984,-53.936654 Q 96.12998,-53.84165 96.12998,-53.56167 L 96.12998,-49.971663 L 96.989974,-49.971663 L 96.989974,-49.29165" />
|
||||||
|
<path d="M 98.259961,-52.19165 L 102.119987,-52.19165 Q 102.119987,-49.14165 99.789974,-49.14165 Q 98.699967,-49.14165 98.044971,-49.91665 Q 97.389974,-50.69165 97.389974,-51.981657 Q 97.389974,-53.271663 98.02998,-54.016667 Q 98.669987,-54.76167 99.769987,-54.76167 Q 100.669987,-54.76167 101.259977,-54.281657 Q 101.849967,-53.801644 102.009961,-52.94165 L 101.169987,-52.94165 Q 100.819987,-53.99165 99.799967,-53.99165 Q 99.089974,-53.99165 98.684977,-53.50166 Q 98.27998,-53.01167 98.259961,-52.19165 M 101.22998,-51.51167 L 98.27998,-51.51167 Q 98.32998,-50.781657 98.739974,-50.346663 Q 99.149967,-49.91167 99.779964,-49.91167 Q 100.409961,-49.91167 100.819971,-50.371663 Q 101.22998,-50.831657 101.22998,-51.51167" />
|
||||||
|
<path d="M 103.019987,-56.71167 L 103.859993,-56.71167 L 103.859993,-53.981657 Q 104.5,-54.76167 105.469987,-54.76167 Q 106.47998,-54.76167 107.094987,-54.006657 Q 107.709993,-53.251644 107.709993,-52.001644 Q 107.709993,-50.681657 107.104997,-49.911654 Q 106.5,-49.14165 105.459993,-49.14165 Q 104.37998,-49.14165 103.789974,-50.081657 L 103.789974,-49.29165 L 103.019987,-49.29165 L 103.019987,-56.71167 M 105.319987,-49.921663 Q 106.009993,-49.921663 106.424984,-50.481657 Q 106.839974,-51.04165 106.839974,-51.981657 Q 106.839974,-52.871663 106.419987,-53.42666 Q 106.0,-53.981657 105.319987,-53.981657 Q 104.659993,-53.981657 104.259993,-53.42666 Q 103.859993,-52.871663 103.859993,-51.95166 Q 103.859993,-51.031657 104.259993,-50.47666 Q 104.659993,-49.921663 105.319987,-49.921663" />
|
||||||
|
<path d="M 108.590007,-56.71167 L 109.430013,-56.71167 L 109.430013,-53.981657 Q 110.07002,-54.76167 111.040007,-54.76167 Q 112.05,-54.76167 112.665007,-54.006657 Q 113.280013,-53.251644 113.280013,-52.001644 Q 113.280013,-50.681657 112.675016,-49.911654 Q 112.07002,-49.14165 111.030013,-49.14165 Q 109.95,-49.14165 109.359993,-50.081657 L 109.359993,-49.29165 L 108.590007,-49.29165 L 108.590007,-56.71167 M 110.890007,-49.921663 Q 111.580013,-49.921663 111.995003,-50.481657 Q 112.409993,-51.04165 112.409993,-51.981657 Q 112.409993,-52.871663 111.990007,-53.42666 Q 111.57002,-53.981657 110.890007,-53.981657 Q 110.230013,-53.981657 109.830013,-53.42666 Q 109.430013,-52.871663 109.430013,-51.95166 Q 109.430013,-51.031657 109.830013,-50.47666 Q 110.230013,-49.921663 110.890007,-49.921663" />
|
||||||
|
<path d="M 114.75,-52.19165 L 118.610026,-52.19165 Q 118.610026,-49.14165 116.280013,-49.14165 Q 115.190007,-49.14165 114.53501,-49.91665 Q 113.880013,-50.69165 113.880013,-51.981657 Q 113.880013,-53.271663 114.52002,-54.016667 Q 115.160026,-54.76167 116.260026,-54.76167 Q 117.160026,-54.76167 117.750016,-54.281657 Q 118.340007,-53.801644 118.5,-52.94165 L 117.660026,-52.94165 Q 117.310026,-53.99165 116.290007,-53.99165 Q 115.580013,-53.99165 115.175016,-53.50166 Q 114.77002,-53.01167 114.75,-52.19165 M 117.72002,-51.51167 L 114.77002,-51.51167 Q 114.82002,-50.781657 115.230013,-50.346663 Q 115.640007,-49.91167 116.270003,-49.91167 Q 116.9,-49.91167 117.31001,-50.371663 Q 117.72002,-50.831657 117.72002,-51.51167" />
|
||||||
|
<path d="M 122.010026,-50.021663 L 122.010026,-49.171663 Q 121.800033,-49.14165 121.690039,-49.14165 Q 121.290039,-49.14165 120.955046,-49.396647 Q 120.620052,-49.651644 120.260026,-50.24165 L 120.260026,-49.29165 L 119.490039,-49.29165 L 119.490039,-54.531657 L 120.330046,-54.531657 L 120.330046,-51.81167 Q 120.330046,-50.781657 120.750049,-50.411654 Q 121.170052,-50.04165 122.010026,-50.021663" />
|
||||||
|
<path d="M 51.169987,-62.98999 L 51.169987,-68.11001 Q 51.169987,-68.849984 50.669987,-69.269987 Q 50.169987,-69.68999 49.290007,-69.68999 Q 48.440007,-69.68999 47.93501,-69.294987 Q 47.430013,-68.899984 47.430013,-68.11001 L 47.430013,-62.98999 L 46.5,-62.98999 L 46.5,-68.11001 Q 46.5,-69.220003 47.245003,-69.865007 Q 47.990007,-70.51001 49.290007,-70.51001 Q 50.569987,-70.51001 51.334994,-69.86001 Q 52.1,-69.21001 52.1,-68.11001 L 52.1,-62.98999 L 51.169987,-62.98999" />
|
||||||
|
<path d="M 53.5,-65.03999 L 53.5,-70.279997 L 54.340007,-70.279997 L 54.340007,-67.38999 Q 54.340007,-66.58999 54.729997,-66.104997 Q 55.119987,-65.620003 55.759994,-65.620003 Q 56.259994,-65.620003 56.55,-65.9 Q 56.840007,-66.179997 56.840007,-66.649984 L 56.840007,-70.279997 L 57.669987,-70.279997 L 57.669987,-66.320003 Q 57.669987,-65.670003 57.219987,-65.279997 Q 56.769987,-64.88999 56.009994,-64.88999 Q 55.430013,-64.88999 55.025,-65.129997 Q 54.619987,-65.370003 54.269987,-65.920003 L 54.269987,-65.03999 L 53.5,-65.03999" />
|
||||||
|
<path d="M 62.889974,-65.03999 L 62.029981,-65.03999 L 62.029981,-63.599984 L 61.199968,-63.599984 L 61.199968,-65.03999 L 60.489974,-65.03999 L 60.489974,-65.720003 L 61.199968,-65.720003 L 61.199968,-69.679997 Q 61.199968,-70.079997 61.459977,-70.295003 Q 61.719987,-70.51001 62.209961,-70.51001 Q 62.499968,-70.51001 62.889974,-70.43999 L 62.889974,-69.73999 Q 62.739974,-69.779997 62.489974,-69.779997 Q 62.219987,-69.779997 62.124984,-69.684993 Q 62.029981,-69.58999 62.029981,-69.31001 L 62.029981,-65.720003 L 62.889974,-65.720003 L 62.889974,-65.03999" />
|
||||||
|
<path d="M 64.259961,-68.720003 L 63.379981,-68.720003 Q 63.439974,-70.51001 65.469987,-70.51001 Q 66.459961,-70.51001 67.044971,-70.06001 Q 67.629981,-69.61001 67.629981,-68.849984 Q 67.629981,-68.270003 67.279981,-67.925 Q 66.929981,-67.579997 66.149968,-67.38999 L 65.349968,-67.199984 Q 64.839974,-67.079997 64.609977,-66.904997 Q 64.379981,-66.729997 64.379981,-66.449984 Q 64.379981,-66.08999 64.679981,-65.875 Q 64.979981,-65.66001 65.489974,-65.66001 Q 66.509961,-65.66001 66.539974,-66.499984 L 67.419987,-66.499984 Q 67.409961,-65.729997 66.919971,-65.309993 Q 66.429981,-64.88999 65.519971,-64.88999 Q 64.609961,-64.88999 64.059961,-65.325 Q 63.509961,-65.76001 63.509961,-66.48999 Q 63.509961,-67.11001 63.874968,-67.445003 Q 64.239974,-67.779997 65.169987,-67.999984 L 65.949968,-68.18999 Q 66.379981,-68.28999 66.569971,-68.459993 Q 66.759961,-68.629997 66.759961,-68.920003 Q 66.759961,-69.28999 66.424968,-69.51499 Q 66.089974,-69.73999 65.539974,-69.73999 Q 64.849968,-69.73999 64.584977,-69.46499 Q 64.319987,-69.18999 64.259961,-68.720003" />
|
||||||
|
<path d="M 73.569987,-65.03999 L 73.569987,-70.279997 L 74.409994,-70.279997 L 74.409994,-66.98999 Q 74.409994,-66.420003 74.769987,-66.020003 Q 75.129981,-65.620003 75.639974,-65.620003 Q 76.1,-65.620003 76.354997,-65.895003 Q 76.609994,-66.170003 76.609994,-66.670003 L 76.609994,-70.279997 L 77.45,-70.279997 L 77.45,-66.98999 Q 77.45,-66.420003 77.809994,-66.020003 Q 78.169987,-65.620003 78.679981,-65.620003 Q 79.139974,-65.620003 79.394987,-65.895003 Q 79.65,-66.170003 79.65,-66.670003 L 79.65,-70.279997 L 80.489974,-70.279997 L 80.489974,-66.349984 Q 80.489974,-65.63999 80.094987,-65.26499 Q 79.7,-64.88999 78.969987,-64.88999 Q 78.45,-64.88999 78.08999,-65.069987 Q 77.729981,-65.249984 77.359994,-65.68999 Q 76.919987,-64.88999 75.95,-64.88999 Q 75.429981,-64.88999 75.054981,-65.1 Q 74.679981,-65.31001 74.339974,-65.779997 L 74.339974,-65.03999 L 73.569987,-65.03999" />
|
||||||
|
<path d="M 81.589974,-65.03999 L 81.589974,-70.279997 L 82.429981,-70.279997 L 82.429981,-66.98999 Q 82.429981,-66.420003 82.789974,-66.020003 Q 83.149968,-65.620003 83.659961,-65.620003 Q 84.119987,-65.620003 84.374984,-65.895003 Q 84.629981,-66.170003 84.629981,-66.670003 L 84.629981,-70.279997 L 85.469987,-70.279997 L 85.469987,-66.98999 Q 85.469987,-66.420003 85.829981,-66.020003 Q 86.189974,-65.620003 86.699968,-65.620003 Q 87.159961,-65.620003 87.414974,-65.895003 Q 87.669987,-66.170003 87.669987,-66.670003 L 87.669987,-70.279997 L 88.509961,-70.279997 L 88.509961,-66.349984 Q 88.509961,-65.63999 88.114974,-65.26499 Q 87.719987,-64.88999 86.989974,-64.88999 Q 86.469987,-64.88999 86.109977,-65.069987 Q 85.749968,-65.249984 85.379981,-65.68999 Q 84.939974,-64.88999 83.969987,-64.88999 Q 83.449968,-64.88999 83.074968,-65.1 Q 82.699968,-65.31001 82.359961,-65.779997 L 82.359961,-65.03999 L 81.589974,-65.03999" />
|
||||||
|
<path d="M 69.809994,-65.03999 L 68.769987,-65.03999 L 68.769987,-66.079997 L 69.809994,-66.079997 L 69.809994,-65.03999" />
|
||||||
|
<path d="M 69.809994,-69.23999 L 68.769987,-69.23999 L 68.769987,-70.279997 L 69.809994,-70.279997 L 69.809994,-69.23999" />
|
||||||
|
<path d="M 59.689974,-62.98999 L 58.849968,-62.98999 L 58.849968,-64.03999 L 59.689974,-64.03999 L 59.689974,-62.98999" />
|
||||||
|
<path d="M 59.689974,-65.03999 L 58.859961,-65.03999 L 58.859961,-70.279997 L 59.689974,-70.279997 L 59.689974,-65.03999" />
|
||||||
|
<path d="M 46.5,-79.071663 L 47.436665,-79.071663 Q 47.896669,-79.071663 48.16167,-78.746663 Q 48.426671,-78.421663 48.426671,-77.854997 Q 48.426671,-77.28833 48.163336,-76.964996 Q 47.9,-76.641661 47.436665,-76.641661 L 46.5,-76.641661 L 46.5,-79.071663 M 46.810004,-78.798334 L 46.810004,-76.915001 L 47.383333,-76.915001 C 47.861951,-76.913266 48.118055,-77.242282 48.116667,-77.858328 C 48.118055,-78.469584 47.861951,-78.800418 47.383333,-78.798334 L 46.810004,-78.798334" />
|
||||||
|
<path d="M 50.780002,-78.994992 Q 50.693338,-78.934999 50.663336,-78.841667 Q 50.633333,-78.748334 50.633333,-78.611659 Q 50.633333,-78.584999 50.635004,-78.528331 Q 50.636675,-78.471663 50.636675,-78.441661 Q 50.636675,-78.344992 50.626671,-78.274995 Q 50.616667,-78.204997 50.585004,-78.124995 Q 50.519173,-77.964996 50.303342,-77.871663 Q 50.506673,-77.771663 50.596674,-77.634999 Q 50.686675,-77.498334 50.686675,-77.291661 Q 50.686675,-76.978326 50.495009,-76.809993 Q 50.303342,-76.641661 49.946669,-76.641661 L 48.826671,-76.641661 L 48.826671,-79.071663 L 49.136675,-79.071663 L 49.136675,-78.024995 L 49.936675,-78.024995 C 50.213613,-78.026383 50.335287,-78.159168 50.336675,-78.458328 L 50.336675,-78.674995 Q 50.336675,-78.918332 50.403342,-79.071663 L 50.780002,-79.071663 L 50.780002,-78.994992 M 49.886675,-76.915001 C 50.035561,-76.915001 50.152226,-76.945 50.23667,-77.004997 C 50.321114,-77.064994 50.363336,-77.174995 50.363336,-77.334999 C 50.361601,-77.625624 50.217288,-77.749929 49.886675,-77.751666 L 49.136675,-77.751666 L 49.136675,-76.915001 L 49.886675,-76.915001" />
|
||||||
|
<path d="M 52.480002,-78.341661 L 52.730002,-79.071663 L 53.076671,-79.071663 L 52.22334,-76.641661 L 51.82334,-76.641661 L 50.956673,-79.071663 L 51.286675,-79.071663 L 51.543338,-78.341661 L 52.480002,-78.341661 M 52.393338,-78.081668 L 51.620009,-78.081668 L 52.020009,-76.974995 L 52.393338,-78.081668" />
|
||||||
|
<path d="M 55.483333,-79.071663 L 56.1,-76.641661 L 55.753342,-76.641661 L 55.306673,-78.615001 L 54.753342,-76.641661 L 54.420009,-76.641661 L 53.880002,-78.615001 L 53.42334,-76.641661 L 53.076671,-76.641661 L 53.7,-79.071663 L 54.040007,-79.071663 L 54.583333,-77.074995 L 55.143338,-79.071663 L 55.483333,-79.071663" />
|
||||||
|
<path d="M 56.730002,-76.641661 L 56.416667,-76.641661 L 56.416667,-79.071663 L 56.730002,-79.071663 L 56.730002,-76.641661" />
|
||||||
|
<path d="M 59.110004,-76.641661 L 58.816667,-76.641661 L 58.816667,-78.628326 L 57.546669,-76.641661 L 57.210004,-76.641661 L 57.210004,-79.071663 L 57.503342,-79.071663 L 57.503342,-77.101666 L 58.760004,-79.071663 L 59.110004,-79.071663 L 59.110004,-76.641661" />
|
||||||
|
<path d="M 61.420009,-78.128326 C 61.420009,-78.346106 61.348342,-78.524996 61.205008,-78.664996 C 61.061674,-78.804995 60.878895,-78.874995 60.656673,-78.874995 C 60.52334,-78.874995 60.404451,-78.852217 60.300006,-78.806662 C 60.088336,-78.715727 59.976123,-78.574816 59.893338,-78.38833 C 59.811942,-78.200629 59.788066,-78.034921 59.786675,-77.865001 C 59.786675,-77.567222 59.864453,-77.327776 60.020009,-77.146663 C 60.175564,-76.965551 60.38223,-76.874995 60.640007,-76.874995 C 60.824447,-76.874995 60.979445,-76.919994 61.105002,-77.009993 C 61.230559,-77.099993 61.310004,-77.22277 61.343338,-77.378326 L 61.660004,-77.378326 C 61.617781,-77.131661 61.507225,-76.940551 61.328337,-76.804997 C 61.149449,-76.669443 60.921116,-76.601666 60.643338,-76.601666 C 60.46556,-76.601666 60.306116,-76.631109 60.165007,-76.689996 C 60.023897,-76.748882 59.911675,-76.82277 59.828342,-76.911659 C 59.745009,-77.000548 59.675564,-77.103883 59.620009,-77.221663 C 59.506467,-77.458615 59.479101,-77.666938 59.476671,-77.881668 C 59.476671,-78.254997 59.580004,-78.55944 59.78667,-78.794998 C 59.993336,-79.030556 60.261115,-79.148334 60.590007,-79.148334 C 60.912229,-79.148334 61.188896,-79.019443 61.420009,-78.761659 L 61.496669,-79.084999 L 61.693338,-79.084999 L 61.693338,-77.78833 L 60.680002,-77.78833 L 60.680002,-78.061659 L 61.420009,-78.061659 L 61.420009,-78.128326" />
|
||||||
|
<path d="M 64.893338,-76.641661 L 64.6,-76.641661 L 64.6,-78.628326 L 63.330002,-76.641661 L 62.993338,-76.641661 L 62.993338,-79.071663 L 63.286675,-79.071663 L 63.286675,-77.101666 L 64.543338,-79.071663 L 64.893338,-79.071663 L 64.893338,-76.641661" />
|
||||||
|
<path d="M 66.983333,-76.641661 L 66.983333,-78.348334 Q 66.983333,-78.594992 66.816667,-78.734993 Q 66.65,-78.874995 66.356673,-78.874995 Q 66.07334,-78.874995 65.905008,-78.743327 Q 65.736675,-78.611659 65.736675,-78.348334 L 65.736675,-76.641661 L 65.426671,-76.641661 L 65.426671,-78.348334 Q 65.426671,-78.718332 65.675006,-78.933333 Q 65.92334,-79.148334 66.356673,-79.148334 Q 66.783333,-79.148334 67.038336,-78.931668 Q 67.293338,-78.715001 67.293338,-78.348334 L 67.293338,-76.641661 L 66.983333,-76.641661" />
|
||||||
|
<path d="M 69.136675,-79.071663 L 69.820009,-77.034999 L 69.820009,-79.071663 L 70.113336,-79.071663 L 70.113336,-76.641661 L 69.683333,-76.641661 L 68.976671,-78.758328 L 68.256673,-76.641661 L 67.826671,-76.641661 L 67.826671,-79.071663 L 68.120009,-79.071663 L 68.120009,-77.034999 L 68.810004,-79.071663 L 69.136675,-79.071663" />
|
||||||
|
<path d="M 71.743338,-79.071663 Q 72.066667,-79.071663 72.263336,-78.881662 Q 72.460004,-78.691661 72.460004,-78.378326 Q 72.460004,-78.158328 72.355002,-78.018327 Q 72.25,-77.878326 72.016667,-77.78833 C 72.24111,-77.683889 72.353331,-77.507221 72.353331,-77.258328 C 72.352463,-77.120032 72.312533,-76.991627 72.211665,-76.859993 C 72.113922,-76.731138 71.902184,-76.639404 71.633333,-76.641661 L 70.646669,-76.641661 L 70.646669,-79.071663 L 71.743338,-79.071663 M 71.556673,-76.915001 Q 72.043338,-76.915001 72.043338,-77.301666 Q 72.043338,-77.68833 71.556673,-77.68833 L 70.956673,-77.68833 L 70.956673,-76.915001 L 71.556673,-76.915001 M 71.713336,-78.798334 L 70.956673,-78.798334 L 70.956673,-77.961659 L 71.713336,-77.961659 Q 71.92334,-77.961659 72.03667,-78.07666 Q 72.15,-78.191661 72.15,-78.381668 Q 72.15,-78.481668 72.116667,-78.568332 Q 72.083333,-78.654997 71.980002,-78.726666 Q 71.876671,-78.798334 71.713336,-78.798334" />
|
||||||
|
<path d="M 73.086675,-77.965001 L 74.410004,-77.965001 L 74.410004,-77.691661 L 73.086675,-77.691661 L 73.086675,-76.915001 L 74.460004,-76.915001 L 74.460004,-76.641661 L 72.776671,-76.641661 L 72.776671,-79.071663 L 74.520009,-79.071663 L 74.520009,-78.798334 L 73.086675,-78.798334 L 73.086675,-77.965001" />
|
||||||
|
<path d="M 76.840007,-78.994992 Q 76.753342,-78.934999 76.72334,-78.841667 Q 76.693338,-78.748334 76.693338,-78.611659 Q 76.693338,-78.584999 76.695009,-78.528331 Q 76.69668,-78.471663 76.69668,-78.441661 Q 76.69668,-78.344992 76.686675,-78.274995 Q 76.676671,-78.204997 76.645009,-78.124995 Q 76.579178,-77.964996 76.363346,-77.871663 Q 76.566678,-77.771663 76.656679,-77.634999 Q 76.74668,-77.498334 76.74668,-77.291661 Q 76.74668,-76.978326 76.555013,-76.809993 Q 76.363346,-76.641661 76.006673,-76.641661 L 74.886675,-76.641661 L 74.886675,-79.071663 L 75.19668,-79.071663 L 75.19668,-78.024995 L 75.99668,-78.024995 C 76.273618,-78.026383 76.395292,-78.159168 76.39668,-78.458328 L 76.39668,-78.674995 Q 76.39668,-78.918332 76.463346,-79.071663 L 76.840007,-79.071663 L 76.840007,-78.994992 M 75.94668,-76.915001 C 76.095566,-76.915001 76.212231,-76.945 76.296674,-77.004997 C 76.381118,-77.064994 76.42334,-77.174995 76.42334,-77.334999 C 76.421605,-77.625624 76.277292,-77.749929 75.94668,-77.751666 L 75.19668,-77.751666 L 75.19668,-76.915001 L 75.94668,-76.915001" />
|
||||||
|
<path d="M 77.59668,-77.324995 L 77.250011,-77.324995 L 77.250011,-77.671663 L 77.59668,-77.671663 L 77.59668,-77.324995" />
|
||||||
|
<path d="M 77.59668,-78.724995 L 77.250011,-78.724995 L 77.250011,-79.071663 L 77.59668,-79.071663 L 77.59668,-78.724995" />
|
||||||
|
<path d="M 48.145003,-87.114168 Q 48.629997,-87.114168 48.925,-86.829167 Q 49.220003,-86.544165 49.220003,-86.074162 Q 49.220003,-85.744165 49.0625,-85.534163 Q 48.904997,-85.324162 48.554997,-85.189168 C 48.891661,-85.032506 49.059994,-84.767505 49.059994,-84.394165 C 49.058691,-84.186722 48.998796,-83.994113 48.847494,-83.796663 C 48.772494,-83.698329 48.659994,-83.619162 48.509994,-83.559163 C 48.359994,-83.499164 48.183328,-83.469165 47.979997,-83.469165 L 46.5,-83.469165 L 46.5,-87.114168 L 48.145003,-87.114168 M 47.865007,-83.879175 Q 48.595003,-83.879175 48.595003,-84.459172 Q 48.595003,-85.039168 47.865007,-85.039168 L 46.965007,-85.039168 L 46.965007,-83.879175 L 47.865007,-83.879175 M 48.1,-86.704175 L 46.965007,-86.704175 L 46.965007,-85.449162 L 48.1,-85.449162 Q 48.415007,-85.449162 48.585002,-85.621663 Q 48.754997,-85.794165 48.754997,-86.079175 Q 48.754997,-86.229175 48.704997,-86.359172 Q 48.654997,-86.489168 48.5,-86.596672 Q 48.345003,-86.704175 48.1,-86.704175" />
|
||||||
|
<path d="M 49.820003,-87.114168 L 51.225,-87.114168 Q 51.915007,-87.114168 52.312508,-86.626668 Q 52.71001,-86.139168 52.71001,-85.289168 Q 52.71001,-84.439168 52.315007,-83.954167 Q 51.920003,-83.469165 51.225,-83.469165 L 49.820003,-83.469165 L 49.820003,-87.114168 M 50.28501,-86.704175 L 50.28501,-83.879175 L 51.145003,-83.879175 C 51.86293,-83.876572 52.247085,-84.370097 52.245003,-85.294165 C 52.245003,-85.754169 52.151671,-86.104172 51.965007,-86.344173 C 51.778342,-86.584174 51.505008,-86.704175 51.145003,-86.704175 L 50.28501,-86.704175" />
|
||||||
|
<path d="M 54.345003,-85.554175 L 53.140007,-85.554175 L 53.140007,-85.914168 L 54.345003,-85.914168 L 54.345003,-85.554175" />
|
||||||
|
<path d="M 55.87002,-84.589168 L 55.87002,-87.114168 L 56.31001,-87.114168 L 56.31001,-83.569165 L 56.02002,-83.569165 Q 55.905013,-83.979175 55.750016,-84.094173 Q 55.59502,-84.209172 55.08501,-84.274162 L 55.08501,-84.589168 L 55.87002,-84.589168" />
|
||||||
|
<path d="M 92.153331,-78.041661 C 92.45111,-78.119439 92.6,-78.250552 92.6,-78.434999 C 92.598784,-78.538252 92.571772,-78.623962 92.48833,-78.718332 C 92.407317,-78.809926 92.227678,-78.876731 91.979991,-78.874995 C 91.848886,-78.874995 91.736111,-78.857773 91.641667,-78.823329 C 91.547222,-78.788885 91.47611,-78.742775 91.428331,-78.684999 C 91.331905,-78.568405 91.294195,-78.453817 91.293327,-78.315001 L 91.293327,-78.298334 L 91.0,-78.298334 C 91.002221,-78.431668 91.023886,-78.549445 91.064996,-78.651666 C 91.147736,-78.859059 91.26947,-78.965934 91.443327,-79.046663 C 91.618399,-79.126003 91.776038,-79.146772 91.959994,-79.148334 C 92.246451,-79.150765 92.487986,-79.071461 92.623329,-78.971663 C 92.692217,-78.920549 92.748882,-78.861104 92.793327,-78.793327 C 92.883952,-78.656904 92.908257,-78.534757 92.909994,-78.404997 C 92.909994,-78.256111 92.864994,-78.128333 92.774995,-78.021663 C 92.684995,-77.914994 92.557773,-77.839437 92.393327,-77.794992 L 91.783333,-77.631668 Q 91.563325,-77.571663 91.473329,-77.491661 Q 91.383333,-77.411659 91.383333,-77.271663 Q 91.383333,-77.08833 91.531662,-76.974995 Q 91.679991,-76.861659 91.92666,-76.861659 Q 92.216667,-76.861659 92.373329,-76.989996 Q 92.529991,-77.118332 92.533333,-77.354997 L 92.82666,-77.354997 Q 92.823329,-76.998334 92.589996,-76.8 Q 92.356662,-76.601666 91.936665,-76.601666 Q 91.536665,-76.601666 91.304997,-76.793332 Q 91.073329,-76.984999 91.073329,-77.315001 Q 91.073329,-77.758328 91.55,-77.881668 L 92.153331,-78.041661" />
|
||||||
|
<path d="M 94.839996,-77.965001 L 94.839996,-79.071663 L 95.149989,-79.071663 L 95.149989,-76.641661 L 94.839996,-76.641661 L 94.839996,-77.691661 L 93.589996,-77.691661 L 93.589996,-76.641661 L 93.279991,-76.641661 L 93.279991,-79.071663 L 93.589996,-79.071663 L 93.589996,-77.965001 L 94.839996,-77.965001" />
|
||||||
|
<path d="M 95.919998,-77.965001 L 97.243327,-77.965001 L 97.243327,-77.691661 L 95.919998,-77.691661 L 95.919998,-76.915001 L 97.293327,-76.915001 L 97.293327,-76.641661 L 95.609994,-76.641661 L 95.609994,-79.071663 L 97.353331,-79.071663 L 97.353331,-78.798334 L 95.919998,-78.798334 L 95.919998,-77.965001" />
|
||||||
|
<path d="M 97.956673,-77.965001 L 99.280002,-77.965001 L 99.280002,-77.691661 L 97.956673,-77.691661 L 97.956673,-76.915001 L 99.330002,-76.915001 L 99.330002,-76.641661 L 97.646669,-76.641661 L 97.646669,-79.071663 L 99.390007,-79.071663 L 99.390007,-78.798334 L 97.956673,-78.798334 L 97.956673,-77.965001" />
|
||||||
|
<path d="M 100.666678,-76.915001 L 101.463336,-76.915001 L 101.463336,-76.641661 L 99.556673,-76.641661 L 99.556673,-76.915001 L 100.356673,-76.915001 L 100.356673,-79.071663 L 100.666678,-79.071663 L 100.666678,-76.915001" />
|
||||||
|
<path d="M 102.176671,-78.724995 L 101.830002,-78.724995 L 101.830002,-79.071663 L 102.176671,-79.071663 L 102.176671,-78.724995" />
|
||||||
|
<path d="M 102.176671,-77.324995 L 101.830002,-77.324995 L 101.830002,-77.671663 L 102.176671,-77.671663 L 102.176671,-77.324995" />
|
||||||
|
<path d="M 91.78501,-84.539168 L 91.78501,-87.064168 L 92.225,-87.064168 L 92.225,-83.519165 L 91.93501,-83.519165 Q 91.820003,-83.929175 91.665007,-84.044173 Q 91.51001,-84.159172 91.0,-84.224162 L 91.0,-84.539168 L 91.78501,-84.539168" />
|
||||||
|
<path d="M -45.9,22.210002 L -55.9,22.210002 L -55.9,21.710002 L -45.9,21.710002 L -45.9,22.210002" />
|
||||||
|
<path d="M -45.9,78.610002 L -55.9,78.610002 L -55.9,78.110002 L -45.9,78.110002 L -45.9,78.610002" />
|
||||||
|
<path d="M -46.9,75.360002 A 10.137937550497139,10.137937550497139 -170.5376777919744 0,0 -47.9,78.360002 A 10.137937550497139,10.137937550497139 -9.46232220802563 0,0 -48.9,75.360002 L -48.15,75.641252 L -48.15,55.392249 L -47.65,55.392249 L -47.65,75.641252 L -46.9,75.360002" />
|
||||||
|
<path d="M -48.9,24.960002 A 10.137937550497139,10.137937550497139 9.462322208025604 0,0 -47.9,21.960002 A 10.137937550497139,10.137937550497139 170.53767779197437 0,0 -46.9,24.960002 L -47.65,24.678752 L -47.65,44.927754 L -48.15,44.927754 L -48.15,24.678752 L -48.9,24.960002" />
|
||||||
|
<path d="M -49.181003,48.471252 L -49.181003,47.190254 L -47.830006,47.004749 L -47.830006,47.288259 Q -47.959501,47.396756 -48.010252,47.498253 Q -48.061003,47.599749 -48.061003,47.743259 Q -48.061003,47.991756 -47.901755,48.138757 Q -47.742506,48.285759 -47.466003,48.285759 Q -47.200006,48.285759 -47.046003,48.140506 Q -46.892001,47.995254 -46.892001,47.743259 Q -46.892001,47.344256 -47.308503,47.235759 L -47.308503,46.927754 C -47.268832,46.934757 -47.238498,46.94059 -47.2175,46.945254 C -47.196502,46.949918 -47.158586,46.962167 -47.10375,46.982003 C -47.048914,47.001838 -47.003414,47.02284 -46.967248,47.045007 C -46.931082,47.067175 -46.889083,47.099841 -46.84125,47.143006 C -46.793417,47.186171 -46.754918,47.234004 -46.725751,47.286504 C -46.669424,47.389498 -46.616991,47.56093 -46.618997,47.750254 C -46.618997,47.999918 -46.701831,48.204084 -46.8675,48.362754 C -47.033169,48.521424 -47.246669,48.600759 -47.507999,48.600759 C -47.752999,48.600759 -47.951915,48.526674 -48.104748,48.378505 C -48.25758,48.230336 -48.333997,48.037253 -48.333997,47.799256 C -48.333997,47.631252 -48.283832,47.478419 -48.183503,47.340759 L -48.876497,47.438752 L -48.876497,48.471252 L -49.181003,48.471252" />
|
||||||
|
<path d="M -48.533503,50.494256 Q -48.837999,50.448752 -49.009501,50.261504 Q -49.181003,50.074256 -49.181003,49.790747 Q -49.181003,49.668247 -49.153005,49.558001 Q -49.125006,49.447754 -49.035751,49.323505 Q -48.946497,49.199256 -48.802999,49.110002 Q -48.659501,49.020747 -48.409249,48.961252 Q -48.158997,48.901756 -47.830006,48.901756 Q -46.618997,48.901756 -46.618997,49.734749 Q -46.618997,50.088247 -46.855247,50.317502 Q -47.091497,50.546756 -47.455499,50.546756 Q -47.802001,50.546756 -48.0225,50.333253 Q -48.242999,50.119749 -48.242999,49.787249 Q -48.242999,49.419749 -47.966497,49.216756 C -48.26983,49.219088 -48.50258,49.267504 -48.664748,49.362003 C -48.826915,49.456502 -48.907999,49.592418 -48.907999,49.769749 C -48.909822,49.982589 -48.769677,50.136746 -48.533503,50.186252 L -48.533503,50.494256 M -47.970006,49.748752 Q -47.970006,49.972754 -47.824753,50.102255 Q -47.679501,50.231756 -47.431003,50.231756 Q -47.196497,50.231756 -47.044249,50.091756 Q -46.892001,49.951756 -46.892001,49.738247 Q -46.892001,49.521252 -47.05125,49.377754 Q -47.210499,49.234256 -47.448503,49.234256 Q -47.679501,49.234256 -47.824753,49.377754 Q -47.970006,49.521252 -47.970006,49.748752" />
|
||||||
|
<path d="M -47.063503,51.341252 L -47.063503,50.977249 L -46.699501,50.977249 L -46.699501,51.341252 L -47.063503,51.341252" />
|
||||||
|
<path d="M -47.294501,52.716745 L -46.699501,52.716745 L -46.699501,53.024749 L -47.294501,53.024749 L -47.294501,53.392249 L -47.571003,53.392249 L -47.571003,53.024749 L -49.181003,53.024749 L -49.181003,52.797249 L -47.620006,51.670254 L -47.294501,51.670254 L -47.294501,52.716745 M -47.571003,52.716745 L -47.571003,51.939749 L -48.656003,52.716745 L -47.571003,52.716745" />
|
||||||
|
<path d="M -114.05,9.960002 L -114.05,19.960002 L -114.55,19.960002 L -114.55,9.960002 L -114.05,9.960002" />
|
||||||
|
<path d="M -57.65,9.960002 L -57.65,19.960002 L -58.15,19.960002 L -58.15,9.960002 L -57.65,9.960002" />
|
||||||
|
<path d="M -60.9,10.960002 A 10.137937550497139,10.137937550497139 99.46232220802563 0,0 -57.9,11.960002 A 10.137937550497139,10.137937550497139 -99.46232220802563 0,0 -60.9,12.960002 L -60.61875,12.210002 L -80.867752,12.210002 L -80.867752,11.710002 L -60.61875,11.710002 L -60.9,10.960002" />
|
||||||
|
<path d="M -111.3,12.960002 A 10.137937550497139,10.137937550497139 -80.53767779197439 0,0 -114.3,11.960002 A 10.137937550497139,10.137937550497139 80.53767779197437 0,0 -111.3,10.960002 L -111.58125,11.710002 L -91.332248,11.710002 L -91.332248,12.210002 L -111.58125,12.210002 L -111.3,12.960002" />
|
||||||
|
<path d="M -87.78875,13.241005 L -89.069748,13.241005 L -89.255252,11.890007 L -88.971743,11.890007 Q -88.863245,12.019503 -88.761749,12.070254 Q -88.660252,12.121005 -88.516743,12.121005 Q -88.268245,12.121005 -88.121244,11.961756 Q -87.974243,11.802507 -87.974243,11.526005 Q -87.974243,11.260007 -88.119495,11.106005 Q -88.264748,10.952003 -88.516743,10.952003 Q -88.915745,10.952003 -89.024243,11.368505 L -89.332248,11.368505 C -89.325245,11.328834 -89.319411,11.298499 -89.314748,11.277502 C -89.310084,11.256504 -89.297834,11.218587 -89.277999,11.163752 C -89.258163,11.108916 -89.237162,11.063415 -89.214994,11.027249 C -89.192827,10.991083 -89.160161,10.949084 -89.116995,10.901252 C -89.07383,10.853419 -89.025998,10.814919 -88.973498,10.785753 C -88.870503,10.729425 -88.699072,10.676992 -88.509748,10.678998 C -88.260084,10.678998 -88.055917,10.761833 -87.897248,10.927502 C -87.738578,11.093171 -87.659243,11.30667 -87.659243,11.568001 C -87.659243,11.813001 -87.733328,12.011917 -87.881497,12.164749 C -88.029665,12.317582 -88.222748,12.393998 -88.460745,12.393998 C -88.62875,12.393998 -88.781583,12.343834 -88.919243,12.243505 L -88.82125,12.936498 L -87.78875,12.936498 L -87.78875,13.241005" />
|
||||||
|
<path d="M -85.765745,12.593505 Q -85.81125,12.898001 -85.998498,13.069503 Q -86.185745,13.241005 -86.469255,13.241005 Q -86.591755,13.241005 -86.702001,13.213006 Q -86.812248,13.185007 -86.936497,13.095753 Q -87.060745,13.006498 -87.15,12.863001 Q -87.239255,12.719503 -87.29875,12.469251 Q -87.358245,12.218998 -87.358245,11.890007 Q -87.358245,10.678998 -86.525252,10.678998 Q -86.171755,10.678998 -85.9425,10.915248 Q -85.713245,11.151498 -85.713245,11.515501 Q -85.713245,11.862003 -85.926749,12.082502 Q -86.140252,12.303001 -86.472752,12.303001 Q -86.840252,12.303001 -87.043245,12.026498 C -87.040914,12.329832 -86.992498,12.562582 -86.897999,12.724749 C -86.8035,12.886917 -86.667584,12.968001 -86.490252,12.968001 C -86.277413,12.969824 -86.123256,12.829679 -86.07375,12.593505 L -85.765745,12.593505 M -86.51125,12.030007 Q -86.287248,12.030007 -86.157747,11.884755 Q -86.028245,11.739503 -86.028245,11.491005 Q -86.028245,11.256498 -86.168245,11.104251 Q -86.308245,10.952003 -86.521755,10.952003 Q -86.73875,10.952003 -86.882248,11.111252 Q -87.025745,11.270501 -87.025745,11.508505 Q -87.025745,11.739503 -86.882248,11.884755 Q -86.73875,12.030007 -86.51125,12.030007" />
|
||||||
|
<path d="M -84.91875,11.123505 L -85.282752,11.123505 L -85.282752,10.759503 L -84.91875,10.759503 L -84.91875,11.123505" />
|
||||||
|
<path d="M -83.543257,11.354503 L -83.543257,10.759503 L -83.235252,10.759503 L -83.235252,11.354503 L -82.867752,11.354503 L -82.867752,11.631005 L -83.235252,11.631005 L -83.235252,13.241005 L -83.462752,13.241005 L -84.589748,11.680007 L -84.589748,11.354503 L -83.543257,11.354503 M -83.543257,11.631005 L -84.320252,11.631005 L -83.543257,12.716005 L -83.543257,11.631005" />
|
||||||
|
<path d="M -89.525,2.519999 L -89.525,-2.480001 L -89.025,-2.480001 L -89.025,2.519999 L -89.525,2.519999" />
|
||||||
|
<path d="M -83.175,2.519999 L -83.175,-2.480001 L -82.675,-2.480001 L -82.675,2.519999 L -83.175,2.519999" />
|
||||||
|
<path d="M -79.925,1.519999 A 10.137937550497139,10.137937550497139 -80.53767779197439 0,0 -82.925,0.519999 A 10.137937550497139,10.137937550497139 80.53767779197437 0,0 -79.925,-0.480001 L -80.20625,0.269999 L -76.925,0.269999 L -76.925,0.769999 L -80.20625,0.769999 L -79.925,1.519999" />
|
||||||
|
<path d="M -92.275,-0.480001 A 10.137937550497139,10.137937550497139 99.46232220802561 0,0 -89.275,0.519999 A 10.137937550497139,10.137937550497139 -99.46232220802564 0,0 -92.275,1.519999 L -91.99375,0.769999 L -95.275,0.769999 L -95.275,0.269999 L -91.99375,0.269999 L -92.275,-0.480001" />
|
||||||
|
<path d="M -86.728245,1.153503 Q -86.77375,1.457998 -86.960998,1.6295 Q -87.148245,1.801003 -87.431755,1.801003 Q -87.554255,1.801003 -87.664501,1.773004 Q -87.774748,1.745005 -87.898997,1.65575 Q -88.023245,1.566496 -88.1125,1.422998 Q -88.201755,1.2795 -88.26125,1.029248 Q -88.320745,0.778996 -88.320745,0.450005 Q -88.320745,-0.761004 -87.487752,-0.761004 Q -87.134255,-0.761004 -86.905,-0.524754 Q -86.675745,-0.288504 -86.675745,0.075498 Q -86.675745,0.422 -86.889249,0.642499 Q -87.102752,0.862998 -87.435252,0.862998 Q -87.802752,0.862998 -88.005745,0.586496 C -88.003414,0.889829 -87.954998,1.122579 -87.860499,1.284747 C -87.766,1.446914 -87.630084,1.527998 -87.452752,1.527998 C -87.239913,1.529821 -87.085756,1.389676 -87.03625,1.153503 L -86.728245,1.153503 M -87.47375,0.590005 Q -87.249748,0.590005 -87.120247,0.444753 Q -86.990745,0.2995 -86.990745,0.051003 Q -86.990745,-0.183504 -87.130745,-0.335752 Q -87.270745,-0.488 -87.484255,-0.488 Q -87.70125,-0.488 -87.844748,-0.328751 Q -87.988245,-0.169502 -87.988245,0.068503 Q -87.988245,0.2995 -87.844748,0.444753 Q -87.70125,0.590005 -87.47375,0.590005" />
|
||||||
|
<path d="M -85.88125,-0.316497 L -86.245252,-0.316497 L -86.245252,-0.6805 L -85.88125,-0.6805 L -85.88125,-0.316497" />
|
||||||
|
<path d="M -85.177752,0.9995 Q -85.170757,1.241003 -85.071003,1.386249 Q -84.97125,1.531496 -84.705252,1.531496 Q -84.502248,1.531496 -84.385,1.417746 Q -84.267752,1.303996 -84.267752,1.107998 Q -84.267752,0.880498 -84.406003,0.803497 Q -84.544255,0.726496 -84.876755,0.7195 L -84.876755,0.457 L -84.838257,0.457 L -84.70875,0.460498 Q -84.194255,0.460498 -84.194255,0.008996 Q -84.194255,-0.2255 -84.330751,-0.35675 Q -84.467248,-0.488 -84.70875,-0.488 Q -84.960757,-0.488 -85.086755,-0.358499 Q -85.212752,-0.228997 -85.230252,0.040498 L -85.538257,0.040498 Q -85.482248,-0.761004 -84.719255,-0.761004 Q -84.337752,-0.761004 -84.108503,-0.551004 Q -83.879255,-0.341004 -83.879255,0.012505 Q -83.879255,0.250498 -83.977253,0.388749 Q -84.075252,0.527 -84.299255,0.603996 Q -83.952752,0.740498 -83.952752,1.118503 Q -83.952752,1.437 -84.154002,1.619001 Q -84.355252,1.801003 -84.70875,1.801003 Q -85.468257,1.801003 -85.485757,0.9995 L -85.177752,0.9995" />
|
||||||
|
<path d="M 40.2,-25.653999 L 30.2,-25.653999 L 30.2,-26.153999 L 40.2,-26.153999 L 40.2,-25.653999" />
|
||||||
|
<path d="M 40.2,50.946001 L 30.2,50.946001 L 30.2,50.446001 L 40.2,50.446001 L 40.2,50.946001" />
|
||||||
|
<path d="M 39.2,47.696001 A 10.137937550497139,10.137937550497139 -170.5376777919744 0,0 38.2,50.696001 A 10.137937550497139,10.137937550497139 -9.46232220802563 0,0 37.2,47.696001 L 37.95,47.977251 L 37.95,17.596752 L 38.45,17.596752 L 38.45,47.977251 L 39.2,47.696001" />
|
||||||
|
<path d="M 37.2,-22.903999 A 10.137937550497139,10.137937550497139 9.462322208025604 0,0 38.2,-25.903999 A 10.137937550497139,10.137937550497139 170.53767779197437 0,0 39.2,-22.903999 L 38.45,-23.185249 L 38.45,7.19525 L 37.95,7.19525 L 37.95,-23.185249 L 37.2,-22.903999" />
|
||||||
|
<path d="M 36.918997,10.854252 L 36.918997,9.19525 L 37.223504,9.19525 L 37.223504,10.535754 Q 37.857001,10.091248 38.3225,9.874252 Q 38.787999,9.657257 39.400499,9.517257 L 39.400499,9.846248 Q 38.805499,9.94775 38.271749,10.191001 Q 37.737999,10.434252 37.177999,10.854252 L 36.918997,10.854252" />
|
||||||
|
<path d="M 37.566497,12.723254 Q 37.262001,12.67775 37.090499,12.490502 Q 36.918997,12.303254 36.918997,12.019745 Q 36.918997,11.897245 36.946996,11.786999 Q 36.974994,11.676752 37.064249,11.552503 Q 37.153504,11.428254 37.297001,11.339 Q 37.440499,11.249745 37.690751,11.19025 Q 37.941004,11.130754 38.269994,11.130754 Q 39.481004,11.130754 39.481004,11.963748 Q 39.481004,12.317245 39.244754,12.5465 Q 39.008504,12.775754 38.644501,12.775754 Q 38.297999,12.775754 38.0775,12.562251 Q 37.857001,12.348748 37.857001,12.016248 Q 37.857001,11.648748 38.133504,11.445754 C 37.83017,11.448086 37.59742,11.496502 37.435252,11.591001 C 37.273085,11.6855 37.192001,11.821416 37.192001,11.998748 C 37.190178,12.211587 37.330323,12.365744 37.566497,12.41525 L 37.566497,12.723254 M 38.129994,11.97775 Q 38.129994,12.201752 38.275247,12.331253 Q 38.420499,12.460754 38.668997,12.460754 Q 38.903504,12.460754 39.055751,12.320754 Q 39.207999,12.180754 39.207999,11.967245 Q 39.207999,11.75025 39.04875,11.606752 Q 38.889501,11.463254 38.651497,11.463254 Q 38.420499,11.463254 38.275247,11.606752 Q 38.129994,11.75025 38.129994,11.97775" />
|
||||||
|
<path d="M 39.036497,13.57025 L 39.036497,13.206248 L 39.400499,13.206248 L 39.400499,13.57025 L 39.036497,13.57025" />
|
||||||
|
<path d="M 37.566497,15.544252 Q 37.262001,15.498748 37.090499,15.3115 Q 36.918997,15.124252 36.918997,14.840743 Q 36.918997,14.718243 36.946996,14.607996 Q 36.974994,14.49775 37.064249,14.373501 Q 37.153504,14.249252 37.297001,14.159998 Q 37.440499,14.070743 37.690751,14.011248 Q 37.941004,13.951752 38.269994,13.951752 Q 39.481004,13.951752 39.481004,14.784745 Q 39.481004,15.138243 39.244754,15.367498 Q 39.008504,15.596752 38.644501,15.596752 Q 38.297999,15.596752 38.0775,15.383249 Q 37.857001,15.169745 37.857001,14.837245 Q 37.857001,14.469745 38.133504,14.266752 C 37.83017,14.269084 37.59742,14.317499 37.435252,14.411999 C 37.273085,14.506498 37.192001,14.642413 37.192001,14.819745 C 37.190178,15.032585 37.330323,15.186742 37.566497,15.236248 L 37.566497,15.544252 M 38.129994,14.798748 Q 38.129994,15.02275 38.275247,15.152251 Q 38.420499,15.281752 38.668997,15.281752 Q 38.903504,15.281752 39.055751,15.141752 Q 39.207999,15.001752 39.207999,14.788243 Q 39.207999,14.571248 39.04875,14.42775 Q 38.889501,14.284252 38.651497,14.284252 Q 38.420499,14.284252 38.275247,14.42775 Q 38.129994,14.571248 38.129994,14.798748" />
|
||||||
|
<path d="M -95.663994,84.052999 Q -95.325,84.391994 -95.325,84.950001 Q -95.325,85.568009 -95.675996,85.901007 Q -96.026992,86.234005 -96.675,86.234005 L -98.480996,86.234005 L -98.480996,81.860001 L -97.922988,81.860001 L -97.922988,83.714005 L -96.549004,83.714005 Q -96.002988,83.714005 -95.663994,84.052999 M -97.922988,84.205997 L -97.922988,85.741994 L -96.759004,85.741994 Q -96.356992,85.741994 -96.131992,85.537999 Q -95.906992,85.334005 -95.906992,84.974005 Q -95.906992,84.614005 -96.131992,84.410001 Q -96.356992,84.205997 -96.759004,84.205997 L -97.922988,84.205997" />
|
||||||
|
<path d="M -94.160996,86.234005 L -94.665,86.234005 L -94.665,81.860001 L -94.160996,81.860001 L -94.160996,86.234005" />
|
||||||
|
<path d="M -93.482988,84.074005 L -92.979004,84.074005 Q -92.949004,84.361994 -92.769004,84.496994 Q -92.589004,84.631994 -92.240996,84.631994 Q -91.905,84.631994 -91.722002,84.511994 Q -91.539004,84.391994 -91.539004,84.164005 L -91.539004,84.031994 Q -91.539004,83.875997 -91.652998,83.797999 Q -91.766992,83.720001 -92.060996,83.684005 Q -92.276992,83.654005 -92.360996,83.642003 Q -92.445,83.630001 -92.625,83.600001 Q -92.805,83.570001 -92.877002,83.549005 Q -92.949004,83.528009 -93.084004,83.483009 Q -93.219004,83.438009 -93.275996,83.390001 Q -93.332988,83.341994 -93.416992,83.266994 Q -93.500996,83.191994 -93.533994,83.105001 Q -93.566992,83.018009 -93.593994,82.904005 Q -93.620996,82.790001 -93.620996,82.651994 Q -93.620996,82.225997 -93.341992,81.973996 Q -93.062988,81.721994 -92.589004,81.721994 Q -92.025,81.721994 -91.520996,82.184005 Q -91.490996,81.944005 -91.367998,81.832999 Q -91.245,81.721994 -91.005,81.721994 Q -90.872988,81.721994 -90.662988,81.775997 L -90.662988,82.154005 Q -90.716992,82.141994 -90.770996,82.141994 Q -91.040996,82.141994 -91.040996,82.388009 L -91.040996,84.235997 Q -91.040996,84.655997 -91.340996,84.875001 Q -91.640996,85.094005 -92.222988,85.094005 Q -93.446992,85.094005 -93.482988,84.074005 M -91.802998,82.379005 Q -92.066992,82.160001 -92.480996,82.160001 Q -92.775,82.160001 -92.937002,82.292003 Q -93.099004,82.424005 -93.099004,82.664005 Q -93.099004,82.904005 -92.919004,83.039005 Q -92.739004,83.174005 -92.517002,83.210001 Q -92.295,83.245997 -91.992002,83.293996 Q -91.689004,83.341994 -91.539004,83.414005 L -91.539004,82.850001 Q -91.539004,82.598009 -91.802998,82.379005" />
|
||||||
|
<path d="M -90.212988,85.004005 L -90.212988,81.860001 L -89.708984,81.860001 L -89.708984,83.594005 Q -89.708984,84.074005 -89.47499,84.365001 Q -89.240996,84.655997 -88.856992,84.655997 Q -88.556992,84.655997 -88.382988,84.487999 Q -88.208984,84.320001 -88.208984,84.038009 L -88.208984,81.860001 L -87.710996,81.860001 L -87.710996,84.235997 Q -87.710996,84.625997 -87.980996,84.860001 Q -88.250996,85.094005 -88.706992,85.094005 Q -89.05498,85.094005 -89.297988,84.950001 Q -89.540996,84.805997 -89.750996,84.475997 L -89.750996,85.004005 L -90.212988,85.004005" />
|
||||||
|
<path d="M -83.589004,81.860001 L -82.070996,86.234005 L -82.665,86.234005 L -83.876992,82.531994 L -85.160996,86.234005 L -85.760996,86.234005 L -84.189004,81.860001 L -83.589004,81.860001" />
|
||||||
|
<path d="M -81.080996,85.004005 L -81.579004,85.004005 L -81.579004,81.860001 L -81.080996,81.860001 L -81.080996,85.004005" />
|
||||||
|
<path d="M -81.080996,86.234005 L -81.585,86.234005 L -81.585,85.604005 L -81.080996,85.604005 L -81.080996,86.234005" />
|
||||||
|
<path d="M -80.007012,83.264005 L -77.690996,83.264005 Q -77.690996,85.094005 -79.089004,85.094005 Q -79.743008,85.094005 -80.136006,84.629005 Q -80.529004,84.164005 -80.529004,83.390001 Q -80.529004,82.615997 -80.145,82.168996 Q -79.760996,81.721994 -79.100996,81.721994 Q -78.560996,81.721994 -78.207002,82.010001 Q -77.853008,82.298009 -77.757012,82.814005 L -78.260996,82.814005 Q -78.470996,82.184005 -79.083008,82.184005 Q -79.509004,82.184005 -79.752002,82.477999 Q -79.995,82.771994 -80.007012,83.264005 M -78.225,83.671994 L -79.995,83.671994 Q -79.965,84.110001 -79.719004,84.370997 Q -79.473008,84.631994 -79.09501,84.631994 Q -78.717012,84.631994 -78.471006,84.355997 Q -78.225,84.080001 -78.225,83.671994" />
|
||||||
|
<path d="M -74.222988,81.860001 L -73.299004,85.004005 L -73.862988,85.004005 L -74.486992,82.555997 L -75.105,85.004005 L -75.716992,85.004005 L -76.316992,82.555997 L -76.959004,85.004005 L -77.510996,85.004005 L -76.599004,81.860001 L -76.035,81.860001 L -75.429004,84.325997 L -74.792988,81.860001 L -74.222988,81.860001" />
|
||||||
|
<path d="M -104.26499,-85.588009 L -102.177002,-85.588009 L -102.177002,-85.095997 L -104.26499,-85.095997 L -104.26499,-83.698009 L -101.888994,-83.698009 L -101.888994,-83.205997 L -104.822998,-83.205997 L -104.822998,-87.580001 L -104.26499,-87.580001 L -104.26499,-85.588009" />
|
||||||
|
<path d="M -99.957002,-84.874005 L -99.957002,-84.364005 Q -100.082998,-84.345997 -100.148994,-84.345997 Q -100.388994,-84.345997 -100.58999,-84.498995 Q -100.790986,-84.651993 -101.007002,-85.005997 L -101.007002,-84.435997 L -101.468994,-84.435997 L -101.468994,-87.580001 L -100.96499,-87.580001 L -100.96499,-85.948009 Q -100.96499,-85.330001 -100.712988,-85.107999 Q -100.460986,-84.885997 -99.957002,-84.874005" />
|
||||||
|
<path d="M -97.236006,-84.792999 Q -97.611006,-84.345997 -98.289014,-84.345997 Q -98.949014,-84.345997 -99.327012,-84.792999 Q -99.70501,-85.240001 -99.70501,-86.032003 Q -99.70501,-86.824005 -99.33001,-87.271007 Q -98.95501,-87.718009 -98.283018,-87.718009 Q -97.623018,-87.718009 -97.242012,-87.274005 Q -96.861006,-86.830001 -96.861006,-86.055997 Q -96.861006,-85.240001 -97.236006,-84.792999 M -98.943018,-85.135001 Q -98.703018,-84.808009 -98.283018,-84.808009 Q -97.857002,-84.808009 -97.62001,-85.138009 Q -97.383018,-85.468009 -97.383018,-86.050001 Q -97.383018,-86.601993 -97.626016,-86.928995 Q -97.869014,-87.255997 -98.283018,-87.255997 Q -98.703018,-87.255997 -98.943018,-86.928995 Q -99.183018,-86.601993 -99.183018,-86.031993 Q -99.183018,-85.461993 -98.943018,-85.135001" />
|
||||||
|
<path d="M -96.381006,-84.435997 L -96.381006,-87.580001 L -95.877002,-87.580001 L -95.877002,-85.845997 Q -95.877002,-85.365997 -95.643008,-85.075001 Q -95.409014,-84.784005 -95.02501,-84.784005 Q -94.72501,-84.784005 -94.551006,-84.952003 Q -94.377002,-85.120001 -94.377002,-85.401993 L -94.377002,-87.580001 L -93.879014,-87.580001 L -93.879014,-85.204005 Q -93.879014,-84.814005 -94.149014,-84.580001 Q -94.419014,-84.345997 -94.87501,-84.345997 Q -95.222998,-84.345997 -95.466006,-84.490001 Q -95.709014,-84.634005 -95.919014,-84.964005 L -95.919014,-84.435997 L -96.381006,-84.435997" />
|
||||||
|
<path d="M -91.989014,-84.435997 L -92.50501,-84.435997 L -92.50501,-83.571993 L -93.003018,-83.571993 L -93.003018,-84.435997 L -93.429014,-84.435997 L -93.429014,-84.844005 L -93.003018,-84.844005 L -93.003018,-87.220001 Q -93.003018,-87.460001 -92.847012,-87.589005 Q -92.691006,-87.718009 -92.397021,-87.718009 Q -92.223018,-87.718009 -91.989014,-87.675997 L -91.989014,-87.255997 Q -92.079014,-87.280001 -92.229014,-87.280001 Q -92.391006,-87.280001 -92.448008,-87.222999 Q -92.50501,-87.165997 -92.50501,-86.998009 L -92.50501,-84.844005 L -91.989014,-84.844005 L -91.989014,-84.435997" />
|
||||||
|
<path d="M -89.331006,-85.588009 L -86.949014,-85.588009 L -86.949014,-85.095997 L -89.331006,-85.095997 L -89.331006,-83.698009 L -86.859014,-83.698009 L -86.859014,-83.205997 L -89.889014,-83.205997 L -89.889014,-87.580001 L -86.751006,-87.580001 L -86.751006,-87.088009 L -89.331006,-87.088009 L -89.331006,-85.588009" />
|
||||||
|
<path d="M -85.707002,-83.205997 L -86.211006,-83.205997 L -86.211006,-87.580001 L -85.707002,-87.580001 L -85.707002,-83.205997" />
|
||||||
|
<path d="M -84.64501,-86.175997 L -82.328994,-86.175997 Q -82.328994,-84.345997 -83.727002,-84.345997 Q -84.381006,-84.345997 -84.774004,-84.810997 Q -85.167002,-85.275997 -85.167002,-86.050001 Q -85.167002,-86.824005 -84.782998,-87.271007 Q -84.398994,-87.718009 -83.738994,-87.718009 Q -83.198994,-87.718009 -82.845,-87.430001 Q -82.491006,-87.141993 -82.39501,-86.625997 L -82.898994,-86.625997 Q -83.108994,-87.255997 -83.721006,-87.255997 Q -84.147002,-87.255997 -84.39,-86.962003 Q -84.632998,-86.668009 -84.64501,-86.175997 M -82.862998,-85.768009 L -84.632998,-85.768009 Q -84.602998,-85.330001 -84.357002,-85.069005 Q -84.111006,-84.808009 -83.733008,-84.808009 Q -83.35501,-84.808009 -83.109004,-85.084005 Q -82.862998,-85.360001 -82.862998,-85.768009" />
|
||||||
|
<path d="M -80.498994,-87.580001 L -79.292998,-84.435997 L -79.857002,-84.435997 L -80.74499,-86.985997 L -81.58499,-84.435997 L -82.148994,-84.435997 L -81.04499,-87.580001 L -80.498994,-87.580001" />
|
||||||
|
<path d="M -78.97499,-85.365997 L -78.471006,-85.365997 Q -78.441006,-85.078009 -78.261006,-84.943009 Q -78.081006,-84.808009 -77.732998,-84.808009 Q -77.397002,-84.808009 -77.214004,-84.928009 Q -77.031006,-85.048009 -77.031006,-85.275997 L -77.031006,-85.408009 Q -77.031006,-85.564005 -77.145,-85.642003 Q -77.258994,-85.720001 -77.552998,-85.755997 Q -77.768994,-85.785997 -77.852998,-85.797999 Q -77.937002,-85.810001 -78.117002,-85.840001 Q -78.297002,-85.870001 -78.369004,-85.890997 Q -78.441006,-85.911993 -78.576006,-85.956993 Q -78.711006,-86.001993 -78.767998,-86.050001 Q -78.82499,-86.098009 -78.908994,-86.173009 Q -78.992998,-86.248009 -79.025996,-86.335001 Q -79.058994,-86.421993 -79.085996,-86.535997 Q -79.112998,-86.650001 -79.112998,-86.788009 Q -79.112998,-87.214005 -78.833994,-87.466007 Q -78.55499,-87.718009 -78.081006,-87.718009 Q -77.517002,-87.718009 -77.012998,-87.255997 Q -76.982998,-87.495997 -76.86,-87.607003 Q -76.737002,-87.718009 -76.497002,-87.718009 Q -76.36499,-87.718009 -76.15499,-87.664005 L -76.15499,-87.285997 Q -76.208994,-87.298009 -76.262998,-87.298009 Q -76.532998,-87.298009 -76.532998,-87.051993 L -76.532998,-85.204005 Q -76.532998,-84.784005 -76.832998,-84.565001 Q -77.132998,-84.345997 -77.71499,-84.345997 Q -78.938994,-84.345997 -78.97499,-85.365997 M -77.295,-87.060997 Q -77.558994,-87.280001 -77.972998,-87.280001 Q -78.267002,-87.280001 -78.429004,-87.147999 Q -78.591006,-87.015997 -78.591006,-86.775997 Q -78.591006,-86.535997 -78.411006,-86.400997 Q -78.231006,-86.265997 -78.009004,-86.230001 Q -77.787002,-86.194005 -77.484004,-86.146007 Q -77.181006,-86.098009 -77.031006,-86.025997 L -77.031006,-86.590001 Q -77.031006,-86.841993 -77.295,-87.060997" />
|
||||||
|
<path d="M -74.546982,-84.435997 L -75.062979,-84.435997 L -75.062979,-83.571993 L -75.560986,-83.571993 L -75.560986,-84.435997 L -75.986982,-84.435997 L -75.986982,-84.844005 L -75.560986,-84.844005 L -75.560986,-87.220001 Q -75.560986,-87.460001 -75.40498,-87.589005 Q -75.248975,-87.718009 -74.95499,-87.718009 Q -74.780986,-87.718009 -74.546982,-87.675997 L -74.546982,-87.255997 Q -74.636982,-87.280001 -74.786982,-87.280001 Q -74.948975,-87.280001 -75.005977,-87.222999 Q -75.062979,-87.165997 -75.062979,-86.998009 L -75.062979,-84.844005 L -74.546982,-84.844005 L -74.546982,-84.435997" />
|
||||||
|
<path d="M -73.57499,-83.205997 L -74.078994,-83.205997 L -74.078994,-83.835997 L -73.57499,-83.835997 L -73.57499,-83.205997" />
|
||||||
|
<path d="M -73.57499,-84.435997 L -74.072998,-84.435997 L -74.072998,-87.580001 L -73.57499,-87.580001 L -73.57499,-84.435997" />
|
||||||
|
<path d="M -70.553994,-84.792999 Q -70.928994,-84.345997 -71.607002,-84.345997 Q -72.267002,-84.345997 -72.645,-84.792999 Q -73.022998,-85.240001 -73.022998,-86.032003 Q -73.022998,-86.824005 -72.647998,-87.271007 Q -72.272998,-87.718009 -71.601006,-87.718009 Q -70.941006,-87.718009 -70.56,-87.274005 Q -70.178994,-86.830001 -70.178994,-86.055997 Q -70.178994,-85.240001 -70.553994,-84.792999 M -72.261006,-85.135001 Q -72.021006,-84.808009 -71.601006,-84.808009 Q -71.17499,-84.808009 -70.937998,-85.138009 Q -70.701006,-85.468009 -70.701006,-86.050001 Q -70.701006,-86.601993 -70.944004,-86.928995 Q -71.187002,-87.255997 -71.601006,-87.255997 Q -72.021006,-87.255997 -72.261006,-86.928995 Q -72.501006,-86.601993 -72.501006,-86.031993 Q -72.501006,-85.461993 -72.261006,-85.135001" />
|
||||||
|
<path d="M -69.698994,-84.435997 L -69.698994,-87.580001 L -69.19499,-87.580001 L -69.19499,-85.845997 Q -69.19499,-85.365997 -68.960996,-85.075001 Q -68.727002,-84.784005 -68.342998,-84.784005 Q -68.042998,-84.784005 -67.868994,-84.952003 Q -67.69499,-85.120001 -67.69499,-85.401993 L -67.69499,-87.580001 L -67.197002,-87.580001 L -67.197002,-85.204005 Q -67.197002,-84.814005 -67.467002,-84.580001 Q -67.737002,-84.345997 -68.192998,-84.345997 Q -68.540986,-84.345997 -68.783994,-84.490001 Q -69.027002,-84.634005 -69.237002,-84.964005 L -69.237002,-84.435997 L -69.698994,-84.435997" />
|
||||||
|
<path d="M -15.747002,-31.971997 Q -15.980996,-32.049995 -16.317012,-32.049995 Q -16.670996,-32.049995 -16.925996,-31.956997 Q -17.180996,-31.863999 -17.31,-31.708003 Q -17.439004,-31.552007 -17.496006,-31.390005 Q -17.553008,-31.228003 -17.553008,-31.042007 L -17.553008,-31.012007 L -18.080996,-31.012007 Q -18.075,-31.372007 -17.964004,-31.648003 Q -17.853008,-31.923999 -17.68501,-32.088999 Q -17.517012,-32.253999 -17.283008,-32.358999 Q -17.049004,-32.463999 -16.824004,-32.503003 Q -16.599004,-32.542007 -16.353008,-32.542007 Q -15.963008,-32.542007 -15.654004,-32.452007 Q -15.345,-32.362007 -15.159004,-32.223999 Q -14.973008,-32.085991 -14.853008,-31.902993 Q -14.733008,-31.719995 -14.688008,-31.548999 Q -14.643008,-31.378003 -14.643008,-31.203999 Q -14.643008,-30.802007 -14.886006,-30.513999 Q -15.129004,-30.225991 -15.573008,-30.105991 L -16.670996,-29.812007 Q -17.067012,-29.703999 -17.229004,-29.559995 Q -17.390996,-29.415991 -17.390996,-29.163999 Q -17.390996,-28.833999 -17.124004,-28.629995 Q -16.857012,-28.425991 -16.413008,-28.425991 Q -15.890996,-28.425991 -15.609004,-28.656997 Q -15.327012,-28.888003 -15.320996,-29.313999 L -14.793008,-29.313999 Q -14.799004,-28.672007 -15.219004,-28.315005 Q -15.639004,-27.958003 -16.395,-27.958003 Q -17.115,-27.958003 -17.532002,-28.303003 Q -17.949004,-28.648003 -17.949004,-29.242007 Q -17.949004,-30.039995 -17.090996,-30.262007 L -16.005,-30.549995 Q -15.200996,-30.759995 -15.200996,-31.258003 Q -15.200996,-31.395991 -15.245996,-31.518999 Q -15.290996,-31.642007 -15.402002,-31.768003 Q -15.513008,-31.893999 -15.747002,-31.971997" />
|
||||||
|
<path d="M -13.467012,-28.029995 L -13.971016,-28.029995 L -13.971016,-28.659995 L -13.467012,-28.659995 L -13.467012,-28.029995" />
|
||||||
|
<path d="M -13.467012,-29.259995 L -13.96502,-29.259995 L -13.96502,-32.403999 L -13.467012,-32.403999 L -13.467012,-29.259995" />
|
||||||
|
<path d="M -10.101016,-28.029995 L -10.599023,-28.029995 L -10.599023,-29.655991 Q -10.917012,-29.169995 -11.56502,-29.169995 Q -12.177012,-29.169995 -12.546016,-29.622993 Q -12.91502,-30.075991 -12.91502,-30.825991 Q -12.91502,-31.623999 -12.549023,-32.083003 Q -12.183027,-32.542007 -11.547012,-32.542007 Q -11.223027,-32.542007 -10.986025,-32.413003 Q -10.749023,-32.283999 -10.54502,-31.989995 L -10.54502,-32.403999 L -10.101016,-32.403999 L -10.101016,-28.029995 M -12.141025,-29.973999 Q -11.889023,-29.638003 -11.481016,-29.638003 Q -11.079023,-29.638003 -10.839023,-29.971001 Q -10.599023,-30.303999 -10.599023,-30.868003 Q -10.599023,-31.413999 -10.839023,-31.743999 Q -11.079023,-32.073999 -11.47502,-32.073999 Q -11.889023,-32.073999 -12.141025,-31.741001 Q -12.393027,-31.408003 -12.393027,-30.855991 Q -12.393027,-30.309995 -12.141025,-29.973999" />
|
||||||
|
<path d="M -9.099023,-30.999995 L -6.783008,-30.999995 Q -6.783008,-29.169995 -8.181016,-29.169995 Q -8.83502,-29.169995 -9.228018,-29.634995 Q -9.621016,-30.099995 -9.621016,-30.873999 Q -9.621016,-31.648003 -9.237012,-32.095005 Q -8.853008,-32.542007 -8.193008,-32.542007 Q -7.653008,-32.542007 -7.299014,-32.253999 Q -6.94502,-31.965991 -6.849023,-31.449995 L -7.353008,-31.449995 Q -7.563008,-32.079995 -8.17502,-32.079995 Q -8.601016,-32.079995 -8.844014,-31.786001 Q -9.087012,-31.492007 -9.099023,-30.999995 M -7.317012,-30.592007 L -9.087012,-30.592007 Q -9.057012,-30.153999 -8.811016,-29.893003 Q -8.56502,-29.632007 -8.187021,-29.632007 Q -7.809023,-29.632007 -7.563018,-29.908003 Q -7.317012,-30.183999 -7.317012,-30.592007" />
|
||||||
|
<path d="M -4.065,-30.412007 L -1.683008,-30.412007 L -1.683008,-29.919995 L -4.065,-29.919995 L -4.065,-28.522007 L -1.593008,-28.522007 L -1.593008,-28.029995 L -4.623008,-28.029995 L -4.623008,-32.403999 L -1.485,-32.403999 L -1.485,-31.912007 L -4.065,-31.912007 L -4.065,-30.412007" />
|
||||||
|
<path d="M -0.440996,-28.029995 L -0.945,-28.029995 L -0.945,-32.403999 L -0.440996,-32.403999 L -0.440996,-28.029995" />
|
||||||
|
<path d="M 0.620996,-30.999995 L 2.937012,-30.999995 Q 2.937012,-29.169995 1.539004,-29.169995 Q 0.885,-29.169995 0.492002,-29.634995 Q 0.099004,-30.099995 0.099004,-30.873999 Q 0.099004,-31.648003 0.483008,-32.095005 Q 0.867012,-32.542007 1.527012,-32.542007 Q 2.067012,-32.542007 2.421006,-32.253999 Q 2.775,-31.965991 2.870996,-31.449995 L 2.367012,-31.449995 Q 2.157012,-32.079995 1.545,-32.079995 Q 1.119004,-32.079995 0.876006,-31.786001 Q 0.633008,-31.492007 0.620996,-30.999995 M 2.403008,-30.592007 L 0.633008,-30.592007 Q 0.663008,-30.153999 0.909004,-29.893003 Q 1.155,-29.632007 1.532998,-29.632007 Q 1.910996,-29.632007 2.157002,-29.908003 Q 2.403008,-30.183999 2.403008,-30.592007" />
|
||||||
|
<path d="M 4.767012,-32.403999 L 5.973008,-29.259995 L 5.409004,-29.259995 L 4.521016,-31.809995 L 3.681016,-29.259995 L 3.117012,-29.259995 L 4.221016,-32.403999 L 4.767012,-32.403999" />
|
||||||
|
<path d="M 6.291016,-30.189995 L 6.795,-30.189995 Q 6.825,-29.902007 7.005,-29.767007 Q 7.185,-29.632007 7.533008,-29.632007 Q 7.869004,-29.632007 8.052002,-29.752007 Q 8.235,-29.872007 8.235,-30.099995 L 8.235,-30.232007 Q 8.235,-30.388003 8.121006,-30.466001 Q 8.007012,-30.543999 7.713008,-30.579995 Q 7.497012,-30.609995 7.413008,-30.621997 Q 7.329004,-30.633999 7.149004,-30.663999 Q 6.969004,-30.693999 6.897002,-30.714995 Q 6.825,-30.735991 6.69,-30.780991 Q 6.555,-30.825991 6.498008,-30.873999 Q 6.441016,-30.922007 6.357012,-30.997007 Q 6.273008,-31.072007 6.24001,-31.158999 Q 6.207012,-31.245991 6.18001,-31.359995 Q 6.153008,-31.473999 6.153008,-31.612007 Q 6.153008,-32.038003 6.432012,-32.290005 Q 6.711016,-32.542007 7.185,-32.542007 Q 7.749004,-32.542007 8.253008,-32.079995 Q 8.283008,-32.319995 8.406006,-32.431001 Q 8.529004,-32.542007 8.769004,-32.542007 Q 8.901016,-32.542007 9.111016,-32.488003 L 9.111016,-32.109995 Q 9.057012,-32.122007 9.003008,-32.122007 Q 8.733008,-32.122007 8.733008,-31.875991 L 8.733008,-30.028003 Q 8.733008,-29.608003 8.433008,-29.388999 Q 8.133008,-29.169995 7.551016,-29.169995 Q 6.327012,-29.169995 6.291016,-30.189995 M 7.971006,-31.884995 Q 7.707012,-32.103999 7.293008,-32.103999 Q 6.999004,-32.103999 6.837002,-31.971997 Q 6.675,-31.839995 6.675,-31.599995 Q 6.675,-31.359995 6.855,-31.224995 Q 7.035,-31.089995 7.257002,-31.053999 Q 7.479004,-31.018003 7.782002,-30.970005 Q 8.085,-30.922007 8.235,-30.849995 L 8.235,-31.413999 Q 8.235,-31.665991 7.971006,-31.884995" />
|
||||||
|
<path d="M 10.719023,-29.259995 L 10.203027,-29.259995 L 10.203027,-28.395991 L 9.70502,-28.395991 L 9.70502,-29.259995 L 9.279023,-29.259995 L 9.279023,-29.668003 L 9.70502,-29.668003 L 9.70502,-32.043999 Q 9.70502,-32.283999 9.861025,-32.413003 Q 10.017031,-32.542007 10.311016,-32.542007 Q 10.48502,-32.542007 10.719023,-32.499995 L 10.719023,-32.079995 Q 10.629023,-32.103999 10.479023,-32.103999 Q 10.317031,-32.103999 10.260029,-32.046997 Q 10.203027,-31.989995 10.203027,-31.822007 L 10.203027,-29.668003 L 10.719023,-29.668003 L 10.719023,-29.259995" />
|
||||||
|
<path d="M 11.691016,-28.029995 L 11.187012,-28.029995 L 11.187012,-28.659995 L 11.691016,-28.659995 L 11.691016,-28.029995" />
|
||||||
|
<path d="M 11.691016,-29.259995 L 11.193008,-29.259995 L 11.193008,-32.403999 L 11.691016,-32.403999 L 11.691016,-29.259995" />
|
||||||
|
<path d="M 14.712012,-29.616997 Q 14.337012,-29.169995 13.659004,-29.169995 Q 12.999004,-29.169995 12.621006,-29.616997 Q 12.243008,-30.063999 12.243008,-30.856001 Q 12.243008,-31.648003 12.618008,-32.095005 Q 12.993008,-32.542007 13.665,-32.542007 Q 14.325,-32.542007 14.706006,-32.098003 Q 15.087012,-31.653999 15.087012,-30.879995 Q 15.087012,-30.063999 14.712012,-29.616997 M 13.005,-29.958999 Q 13.245,-29.632007 13.665,-29.632007 Q 14.091016,-29.632007 14.328008,-29.962007 Q 14.565,-30.292007 14.565,-30.873999 Q 14.565,-31.425991 14.322002,-31.752993 Q 14.079004,-32.079995 13.665,-32.079995 Q 13.245,-32.079995 13.005,-31.752993 Q 12.765,-31.425991 12.765,-30.855991 Q 12.765,-30.285991 13.005,-29.958999" />
|
||||||
|
<path d="M 15.567012,-29.259995 L 15.567012,-32.403999 L 16.071016,-32.403999 L 16.071016,-30.669995 Q 16.071016,-30.189995 16.30501,-29.898999 Q 16.539004,-29.608003 16.923008,-29.608003 Q 17.223008,-29.608003 17.397012,-29.776001 Q 17.571016,-29.943999 17.571016,-30.225991 L 17.571016,-32.403999 L 18.069004,-32.403999 L 18.069004,-30.028003 Q 18.069004,-29.638003 17.799004,-29.403999 Q 17.529004,-29.169995 17.073008,-29.169995 Q 16.72502,-29.169995 16.482012,-29.313999 Q 16.239004,-29.458003 16.029004,-29.788003 L 16.029004,-29.259995 L 15.567012,-29.259995" />
|
||||||
|
</g>
|
||||||
|
<g fill="none" stroke="rgb(99,99,99)" stroke-width="0.09" id="Hidden" stroke-dasharray="0.002286 0.271143">
|
||||||
|
<line x1="88.539518" y1="76.052448" x2="88.539518" y2="73.11306" />
|
||||||
|
<line x1="88.539518" y1="73.11306" x2="92.532904" y2="70.807478" />
|
||||||
|
<line x1="92.532904" y1="70.807478" x2="92.532904" y2="67.010769" />
|
||||||
|
<line x1="109.577713" y1="57.169944" x2="92.532904" y2="67.010769" />
|
||||||
|
<line x1="109.577713" y1="60.966653" x2="109.577713" y2="57.169944" />
|
||||||
|
<line x1="109.577713" y1="60.966653" x2="113.571098" y2="58.661071" />
|
||||||
|
<line x1="113.571098" y1="61.600459" x2="113.571098" y2="58.661071" />
|
||||||
|
<path d="M 84.747658,73.844845 A 1.9124999999999999,1.1041823898251593 0.0 0,1 87.542592,73.900687" />
|
||||||
|
<path d="M 84.657408,73.900687 A 1.9124999999999999,1.1041823898251593 0.0 0,1 84.747658,73.844845" />
|
||||||
|
<path d="M 100.3875,60.192002 A 14.287500000000001,8.24889197104678 0.0 0,1 71.8125,60.192002" />
|
||||||
|
<line x1="83.660482" y1="76.052448" x2="83.660482" y2="73.11306" />
|
||||||
|
<path d="M 88.539518,73.11306 A 3.4499999999999997,1.991858428704209 0.0 0,1 83.660482,73.11306" />
|
||||||
|
<path d="M 92.532904,70.807478 A 1.875,1.0825317547305484 0.0 0,1 89.881254,70.807478" />
|
||||||
|
<line x1="88.75165" y1="70.155302" x2="89.881254" y2="70.807478" />
|
||||||
|
<path d="M 83.44835,70.155302 A 3.75,2.165063509461097 0.0 0,1 88.75165,70.155302" />
|
||||||
|
<line x1="83.44835" y1="70.155302" x2="82.318746" y2="70.807478" />
|
||||||
|
<path d="M 82.318746,70.807478 A 1.875,1.0825317547305482 3.895368034302951e-15 0,1 79.667096,70.807478" />
|
||||||
|
<line x1="83.660482" y1="73.11306" x2="79.667096" y2="70.807478" />
|
||||||
|
<path d="M 84.747658,70.905458 A 1.9124999999999999,1.1041823898251593 0.0 0,1 88.0125,71.686233" />
|
||||||
|
<path d="M 88.0125,71.686233 A 1.9125000000000012,1.10418238982516 0.0 0,1 84.1875,71.686233" />
|
||||||
|
<path d="M 84.1875,71.686233 A 1.9124999999999999,1.1041823898251593 0.0 0,1 84.747658,70.905458" />
|
||||||
|
<path d="M 92.532904,67.010769 A 1.875,1.0825317547305484 0.0 0,1 89.881254,67.010769" />
|
||||||
|
<path d="M 62.073112,50.507332 A 1.875,1.0825317547305484 180.0 0,0 62.622287,51.272797" />
|
||||||
|
<path d="M 84.600538,41.006736 A 3.75,2.165063509461097 180.0 0,0 87.599462,41.006736" />
|
||||||
|
<line x1="62.622287" y1="51.272797" x2="63.75189" y2="51.924974" />
|
||||||
|
<path d="M 63.75189,54.986836 A 3.75,2.165063509461097 180.0 0,0 64.85024,53.455905" />
|
||||||
|
<path d="M 64.85024,53.455905 A 3.75,2.165063509461097 180.0 0,0 63.75189,51.924974" />
|
||||||
|
<line x1="62.622287" y1="55.639013" x2="63.75189" y2="54.986836" />
|
||||||
|
<path d="M 62.622287,57.169944 A 1.875,1.0825317547305484 0.0 0,1 62.073112,56.404478" />
|
||||||
|
<path d="M 62.073112,56.404478 A 1.875,1.0825317547305484 0.0 0,1 62.622287,55.639013" />
|
||||||
|
<path d="M 110.126888,50.507332 A 1.875,1.0825317547305484 0.0 0,1 109.577713,51.272797" />
|
||||||
|
<line x1="79.667096" y1="67.010769" x2="62.622287" y2="57.169944" />
|
||||||
|
<line x1="109.577713" y1="51.272797" x2="108.44811" y2="51.924974" />
|
||||||
|
<path d="M 79.667096,67.010769 A 1.875,1.0825317547305484 180.0 0,0 82.318746,67.010769" />
|
||||||
|
<path d="M 108.44811,51.924974 A 3.75,2.165063509461097 180.0 0,0 107.34976,53.455905" />
|
||||||
|
<path d="M 107.34976,53.455905 A 3.75,2.165063509461097 180.0 0,0 108.44811,54.986836" />
|
||||||
|
<line x1="82.318746" y1="67.010769" x2="83.44835" y2="66.358592" />
|
||||||
|
<line x1="109.577713" y1="55.639013" x2="108.44811" y2="54.986836" />
|
||||||
|
<path d="M 88.75165,66.358592 A 3.75,2.165063509461097 180.0 0,0 83.44835,66.358592" />
|
||||||
|
<path d="M 109.577713,57.169944 A 1.875,1.0825317547305484 180.0 0,0 110.126888,56.404478" />
|
||||||
|
<path d="M 110.126888,56.404478 A 1.875,1.0825317547305484 180.0 0,0 109.577713,55.639013" />
|
||||||
|
<line x1="89.881254" y1="67.010769" x2="88.75165" y2="66.358592" />
|
||||||
|
<path d="M 79.720129,39.93166 A 1.7999999999999998,1.0392304845413265 0.0 0,1 82.265713,39.93166" />
|
||||||
|
<line x1="82.265713" y1="39.93166" x2="83.395317" y2="40.583837" />
|
||||||
|
<path d="M 88.804683,40.583837 A 3.8249999999999997,2.2083647796503185 0.0 0,1 83.395317,40.583837" />
|
||||||
|
<line x1="89.934287" y1="39.93166" x2="88.804683" y2="40.583837" />
|
||||||
|
<path d="M 89.934287,39.93166 A 1.7999999999999998,1.0392304845413263 3.895368034302951e-15 0,1 92.479871,39.93166" />
|
||||||
|
<line x1="92.479871" y1="39.93166" x2="109.52468" y2="49.772485" />
|
||||||
|
<path d="M 109.52468,49.772485 A 1.7999999999999998,1.0392304845413263 -3.895368034302951e-15 0,1 110.051888,50.507332" />
|
||||||
|
<path d="M 110.051888,50.507332 A 1.7999999999999998,1.0392304845413263 -3.895368034302951e-15 0,1 109.52468,51.242179" />
|
||||||
|
<line x1="109.52468" y1="51.242179" x2="108.395077" y2="51.894356" />
|
||||||
|
<path d="M 108.395077,55.017455 A 3.8249999999999997,2.2083647796503185 0.0 0,1 107.27476,53.455905" />
|
||||||
|
<path d="M 107.27476,53.455905 A 3.8249999999999997,2.2083647796503185 0.0 0,1 108.395077,51.894356" />
|
||||||
|
<line x1="109.52468" y1="55.669632" x2="108.395077" y2="55.017455" />
|
||||||
|
<path d="M 109.52468,55.669632 A 1.7999999999999998,1.0392304845413265 0.0 0,1 110.051888,56.404478" />
|
||||||
|
<path d="M 110.051888,56.404478 A 1.7999999999999998,1.0392304845413265 0.0 0,1 109.52468,57.139325" />
|
||||||
|
<line x1="109.52468" y1="57.139325" x2="92.479871" y2="66.98015" />
|
||||||
|
<path d="M 92.479871,66.98015 A 1.7999999999999998,1.0392304845413265 0.0 0,1 89.934287,66.98015" />
|
||||||
|
<line x1="89.934287" y1="66.98015" x2="88.804683" y2="66.327974" />
|
||||||
|
<path d="M 83.395317,66.327974 A 3.8249999999999997,2.2083647796503185 0.0 0,1 88.804683,66.327974" />
|
||||||
|
<line x1="82.265713" y1="66.98015" x2="83.395317" y2="66.327974" />
|
||||||
|
<path d="M 82.265713,66.98015 A 1.7999999999999998,1.0392304845413263 3.895368034302951e-15 0,1 79.720129,66.98015" />
|
||||||
|
<line x1="79.720129" y1="66.98015" x2="62.67532" y2="57.139325" />
|
||||||
|
<path d="M 62.67532,57.139325 A 1.7999999999999998,1.0392304845413263 -3.895368034302951e-15 0,1 62.148112,56.404478" />
|
||||||
|
<path d="M 62.148112,56.404478 A 1.7999999999999998,1.0392304845413263 -3.895368034302951e-15 0,1 62.67532,55.669632" />
|
||||||
|
<line x1="62.67532" y1="55.669632" x2="63.804923" y2="55.017455" />
|
||||||
|
<path d="M 63.804923,51.894356 A 3.8249999999999997,2.2083647796503185 0.0 0,1 64.92524,53.455905" />
|
||||||
|
<path d="M 64.92524,53.455905 A 3.8249999999999997,2.2083647796503185 0.0 0,1 63.804923,55.017455" />
|
||||||
|
<line x1="62.67532" y1="51.242179" x2="63.804923" y2="51.894356" />
|
||||||
|
<path d="M 62.67532,51.242179 A 1.7999999999999998,1.0392304845413265 0.0 0,1 62.148112,50.507332" />
|
||||||
|
<path d="M 62.148112,50.507332 A 1.7999999999999998,1.0392304845413265 0.0 0,1 62.67532,49.772485" />
|
||||||
|
<line x1="62.67532" y1="49.772485" x2="79.720129" y2="39.93166" />
|
||||||
|
<path d="M 109.577713,59.435722 A 1.875,1.0825317547305484 0.0 0,1 110.126888,60.201188" />
|
||||||
|
<path d="M 110.126888,60.201188 A 1.875,1.0825317547305484 0.0 0,1 109.577713,60.966653" />
|
||||||
|
<path d="M 114.58158,57.252614 A 3.4499999999999997,1.991858428704209 0.0 0,1 113.571098,58.661071" />
|
||||||
|
<path d="M 109.577713,53.538575 A 1.875,1.0825317547305482 -3.895368034302951e-15 0,1 110.126888,54.304041" />
|
||||||
|
<path d="M 110.126888,54.304041 A 1.875,1.0825317547305482 -3.895368034302951e-15 0,1 109.577713,55.069507" />
|
||||||
|
<line x1="108.44811" y1="55.721683" x2="109.577713" y2="55.069507" />
|
||||||
|
<path d="M 108.44811,58.783545 A 3.75,2.165063509461097 0.0 0,1 107.34976,57.252614" />
|
||||||
|
<path d="M 107.34976,57.252614 A 3.75,2.165063509461097 0.0 0,1 108.44811,55.721683" />
|
||||||
|
<line x1="108.44811" y1="58.783545" x2="109.577713" y2="59.435722" />
|
||||||
|
<path d="M 109.747419,56.471839 A 1.9124999999999999,1.1041823898251593 0.0 0,1 113.01226,57.252614" />
|
||||||
|
<path d="M 113.01226,57.252614 A 1.9125000000000012,1.10418238982516 0.0 0,1 109.18726,57.252614" />
|
||||||
|
<path d="M 109.18726,57.252614 A 1.9124999999999999,1.1041823898251593 0.0 0,1 109.747419,56.471839" />
|
||||||
|
<line x1="79.667096" y1="70.807478" x2="79.667096" y2="67.010769" />
|
||||||
|
<line x1="62.622287" y1="60.966653" x2="62.622287" y2="57.169944" />
|
||||||
|
<line x1="62.622287" y1="60.966653" x2="58.628902" y2="58.661071" />
|
||||||
|
<line x1="58.628902" y1="61.600459" x2="58.628902" y2="58.661071" />
|
||||||
|
<path d="M 58.628902,58.661071 A 3.4499999999999997,1.991858428704209 0.0 0,1 57.61842,57.252614" />
|
||||||
|
<path d="M 59.747898,56.471839 A 1.9124999999999999,1.1041823898251593 0.0 0,1 63.01274,57.252614" />
|
||||||
|
<path d="M 63.01274,57.252614 A 1.9125000000000012,1.10418238982516 0.0 0,1 59.18774,57.252614" />
|
||||||
|
<path d="M 59.18774,57.252614 A 1.9124999999999999,1.1041823898251593 0.0 0,1 59.747898,56.471839" />
|
||||||
|
<path d="M 88.48125,69.305315 A 14.287500000000001,8.24889197104678 0.0 0,1 83.71875,69.305315" />
|
||||||
|
<path d="M 84.747658,42.038221 A 1.9124999999999999,1.1041823898251593 0.0 0,1 88.0125,42.818996" />
|
||||||
|
<path d="M 88.0125,42.818996 A 1.9125000000000012,1.10418238982516 0.0 0,1 84.1875,42.818996" />
|
||||||
|
<path d="M 84.1875,42.818996 A 1.9124999999999999,1.1041823898251593 0.0 0,1 84.747658,42.038221" />
|
||||||
|
<path d="M 79.667096,43.69775 A 1.875,1.0825317547305484 0.0 0,1 82.318746,43.69775" />
|
||||||
|
<line x1="83.44835" y1="44.349927" x2="82.318746" y2="43.69775" />
|
||||||
|
<path d="M 62.622287,55.069507 A 1.875,1.0825317547305484 0.0 0,1 62.073112,54.304041" />
|
||||||
|
<path d="M 62.073112,54.304041 A 1.875,1.0825317547305484 0.0 0,1 62.622287,53.538575" />
|
||||||
|
<path d="M 88.75165,44.349927 A 3.75,2.165063509461097 0.0 0,1 83.44835,44.349927" />
|
||||||
|
<line x1="63.75189" y1="55.721683" x2="62.622287" y2="55.069507" />
|
||||||
|
<line x1="88.75165" y1="44.349927" x2="89.881254" y2="43.69775" />
|
||||||
|
<path d="M 63.75189,55.721683 A 3.75,2.165063509461097 0.0 0,1 64.85024,57.252614" />
|
||||||
|
<path d="M 64.85024,57.252614 A 3.75,2.165063509461097 0.0 0,1 63.75189,58.783545" />
|
||||||
|
<path d="M 89.881254,43.69775 A 1.875,1.0825317547305482 3.895368034302951e-15 0,1 92.532904,43.69775" />
|
||||||
|
<line x1="63.75189" y1="58.783545" x2="62.622287" y2="59.435722" />
|
||||||
|
<path d="M 62.622287,60.966653 A 1.875,1.0825317547305482 -3.895368034302951e-15 0,1 62.073112,60.201188" />
|
||||||
|
<path d="M 62.073112,60.201188 A 1.875,1.0825317547305482 -3.895368034302951e-15 0,1 62.622287,59.435722" />
|
||||||
|
<path d="M 110.051888,29.686669 A 1.7999999999999998,1.0392304845413263 -3.895368034302951e-15 0,1 109.52468,30.421516" />
|
||||||
|
<line x1="109.52468" y1="30.421516" x2="108.395077" y2="31.073693" />
|
||||||
|
<path d="M 108.395077,31.073693 A 3.8249999999999997,2.2083647796503185 180.0 0,0 107.27476,32.635242" />
|
||||||
|
<path d="M 107.27476,32.635242 A 3.8249999999999997,2.2083647796503185 180.0 0,0 108.395077,34.196792" />
|
||||||
|
<line x1="109.52468" y1="34.848969" x2="108.395077" y2="34.196792" />
|
||||||
|
<path d="M 109.52468,36.318663 A 1.7999999999999998,1.0392304845413265 180.0 0,0 110.051888,35.583816" />
|
||||||
|
<path d="M 110.051888,35.583816 A 1.7999999999999998,1.0392304845413265 180.0 0,0 109.52468,34.848969" />
|
||||||
|
<line x1="109.52468" y1="36.318663" x2="92.479871" y2="46.159488" />
|
||||||
|
<path d="M 92.479871,46.159488 A 1.7999999999999998,1.0392304845413265 0.0 0,1 89.934287,46.159488" />
|
||||||
|
<line x1="89.934287" y1="46.159488" x2="88.804683" y2="45.507311" />
|
||||||
|
<path d="M 88.804683,45.507311 A 3.8249999999999997,2.2083647796503185 180.0 0,0 83.395317,45.507311" />
|
||||||
|
<line x1="82.265713" y1="46.159488" x2="83.395317" y2="45.507311" />
|
||||||
|
<path d="M 79.720129,46.159488 A 1.7999999999999998,1.0392304845413263 -180.0 0,0 82.265713,46.159488" />
|
||||||
|
<line x1="79.720129" y1="46.159488" x2="62.67532" y2="36.318663" />
|
||||||
|
<path d="M 62.67532,36.318663 A 1.7999999999999998,1.0392304845413263 -3.895368034302951e-15 0,1 62.148112,35.583816" />
|
||||||
|
<path d="M 62.148112,35.583816 A 1.7999999999999998,1.0392304845413263 -3.895368034302951e-15 0,1 62.67532,34.848969" />
|
||||||
|
<line x1="62.67532" y1="34.848969" x2="63.804923" y2="34.196792" />
|
||||||
|
<path d="M 63.804923,34.196792 A 3.8249999999999997,2.2083647796503185 180.0 0,0 64.92524,32.635242" />
|
||||||
|
<path d="M 64.92524,32.635242 A 3.8249999999999997,2.2083647796503185 180.0 0,0 63.804923,31.073693" />
|
||||||
|
<line x1="62.67532" y1="30.421516" x2="63.804923" y2="31.073693" />
|
||||||
|
<path d="M 62.148112,29.686669 A 1.7999999999999998,1.0392304845413265 180.0 0,0 62.67532,30.421516" />
|
||||||
|
<path d="M 62.148112,29.989778 A 1.875,1.0825317547305484 180.0 0,0 62.622287,30.452135" />
|
||||||
|
<line x1="62.622287" y1="30.452135" x2="63.75189" y2="31.104311" />
|
||||||
|
<path d="M 63.75189,34.166173 A 3.75,2.165063509461097 180.0 0,0 64.85024,32.635242" />
|
||||||
|
<path d="M 64.85024,32.635242 A 3.75,2.165063509461097 180.0 0,0 63.75189,31.104311" />
|
||||||
|
<line x1="62.622287" y1="34.81835" x2="63.75189" y2="34.166173" />
|
||||||
|
<path d="M 62.622287,36.349281 A 1.875,1.0825317547305484 0.0 0,1 62.148112,35.886925" />
|
||||||
|
<path d="M 62.148112,35.280707 A 1.875,1.0825317547305484 0.0 0,1 62.622287,34.81835" />
|
||||||
|
<path d="M 110.051888,29.989778 A 1.875,1.0825317547305484 0.0 0,1 109.577713,30.452135" />
|
||||||
|
<line x1="79.667096" y1="46.190106" x2="62.622287" y2="36.349281" />
|
||||||
|
<line x1="109.577713" y1="30.452135" x2="108.44811" y2="31.104311" />
|
||||||
|
<path d="M 79.667096,46.190106 A 1.875,1.0825317547305484 180.0 0,0 82.318746,46.190106" />
|
||||||
|
<path d="M 108.44811,31.104311 A 3.75,2.165063509461097 180.0 0,0 107.34976,32.635242" />
|
||||||
|
<path d="M 107.34976,32.635242 A 3.75,2.165063509461097 180.0 0,0 108.44811,34.166173" />
|
||||||
|
<line x1="82.318746" y1="46.190106" x2="83.44835" y2="45.53793" />
|
||||||
|
<line x1="109.577713" y1="34.81835" x2="108.44811" y2="34.166173" />
|
||||||
|
<path d="M 88.75165,45.53793 A 3.75,2.165063509461097 180.0 0,0 83.44835,45.53793" />
|
||||||
|
<path d="M 109.577713,36.349281 A 1.875,1.0825317547305484 180.0 0,0 110.051888,35.886925" />
|
||||||
|
<path d="M 110.051888,35.280707 A 1.875,1.0825317547305484 180.0 0,0 109.577713,34.81835" />
|
||||||
|
<line x1="89.881254" y1="46.190106" x2="88.75165" y2="45.53793" />
|
||||||
|
<line x1="109.577713" y1="36.349281" x2="92.532904" y2="46.190106" />
|
||||||
|
<path d="M 92.532904,46.190106 A 1.875,1.0825317547305484 0.0 0,1 89.881254,46.190106" />
|
||||||
|
<path d="M 62.073112,22.950572 A 1.875,1.0825317547305484 180.0 0,0 62.622287,23.716038" />
|
||||||
|
<line x1="62.622287" y1="23.716038" x2="63.75189" y2="24.368215" />
|
||||||
|
<path d="M 63.75189,27.430077 A 3.75,2.165063509461097 180.0 0,0 64.85024,25.899146" />
|
||||||
|
<path d="M 64.85024,25.899146 A 3.75,2.165063509461097 180.0 0,0 63.75189,24.368215" />
|
||||||
|
<line x1="62.622287" y1="28.082253" x2="63.75189" y2="27.430077" />
|
||||||
|
<path d="M 62.622287,29.613184 A 1.875,1.0825317547305484 0.0 0,1 62.073112,28.847719" />
|
||||||
|
<path d="M 62.073112,28.847719 A 1.875,1.0825317547305484 0.0 0,1 62.622287,28.082253" />
|
||||||
|
<path d="M 110.126888,22.950572 A 1.875,1.0825317547305484 0.0 0,1 109.577713,23.716038" />
|
||||||
|
<line x1="79.667096" y1="39.454009" x2="62.622287" y2="29.613184" />
|
||||||
|
<line x1="109.577713" y1="23.716038" x2="108.44811" y2="24.368215" />
|
||||||
|
<path d="M 79.667096,39.454009 A 1.875,1.0825317547305484 180.0 0,0 82.318746,39.454009" />
|
||||||
|
<path d="M 108.44811,24.368215 A 3.75,2.165063509461097 180.0 0,0 107.34976,25.899146" />
|
||||||
|
<path d="M 107.34976,25.899146 A 3.75,2.165063509461097 180.0 0,0 108.44811,27.430077" />
|
||||||
|
<line x1="82.318746" y1="39.454009" x2="83.44835" y2="38.801833" />
|
||||||
|
<line x1="109.577713" y1="28.082253" x2="108.44811" y2="27.430077" />
|
||||||
|
<path d="M 88.75165,38.801833 A 3.75,2.165063509461097 180.0 0,0 83.44835,38.801833" />
|
||||||
|
<path d="M 109.577713,29.613184 A 1.875,1.0825317547305484 180.0 0,0 110.126888,28.847719" />
|
||||||
|
<path d="M 110.126888,28.847719 A 1.875,1.0825317547305484 180.0 0,0 109.577713,28.082253" />
|
||||||
|
<line x1="89.881254" y1="39.454009" x2="88.75165" y2="38.801833" />
|
||||||
|
<line x1="109.577713" y1="29.613184" x2="92.532904" y2="39.454009" />
|
||||||
|
<path d="M 92.532904,39.454009 A 1.875,1.0825317547305484 0.0 0,1 89.881254,39.454009" />
|
||||||
|
<path d="M 84.416202,59.219861 A 2.3812499999999996,1.3748153285077964 0.0 0,1 88.48125,60.192002" />
|
||||||
|
<path d="M 88.48125,60.192002 A 2.3812500000000014,1.3748153285077973 0.0 0,1 83.71875,60.192002" />
|
||||||
|
<path d="M 83.71875,60.192002 A 2.3812499999999996,1.3748153285077964 0.0 0,1 84.416202,59.219861" />
|
||||||
|
<line x1="110.126888" y1="56.404478" x2="110.126888" y2="60.201188" />
|
||||||
|
<line x1="88.0125" y1="71.686233" x2="88.0125" y2="74.62562" />
|
||||||
|
<line x1="84.1875" y1="71.686233" x2="84.1875" y2="74.62562" />
|
||||||
|
<line x1="63.01274" y1="57.252614" x2="63.01274" y2="60.192002" />
|
||||||
|
<line x1="59.18774" y1="57.252614" x2="59.18774" y2="60.192002" />
|
||||||
|
<line x1="113.01226" y1="57.252614" x2="113.01226" y2="60.192002" />
|
||||||
|
<line x1="109.18726" y1="57.252614" x2="109.18726" y2="60.192002" />
|
||||||
|
<line x1="88.0125" y1="42.818996" x2="88.0125" y2="45.758384" />
|
||||||
|
<line x1="84.1875" y1="42.818996" x2="84.1875" y2="45.758384" />
|
||||||
|
<line x1="62.073112" y1="53.855642" x2="62.073112" y2="54.304041" />
|
||||||
|
<line x1="64.85024" y1="53.455905" x2="64.85024" y2="57.252614" />
|
||||||
|
<line x1="62.073112" y1="56.404478" x2="62.073112" y2="60.201188" />
|
||||||
|
<line x1="110.126888" y1="53.855642" x2="110.126888" y2="54.304041" />
|
||||||
|
<line x1="107.34976" y1="53.455905" x2="107.34976" y2="57.252614" />
|
||||||
|
<line x1="110.051888" y1="50.204223" x2="110.051888" y2="50.507332" />
|
||||||
|
<line x1="107.27476" y1="32.635242" x2="107.27476" y2="53.455905" />
|
||||||
|
<line x1="110.051888" y1="35.583816" x2="110.051888" y2="56.404478" />
|
||||||
|
<line x1="62.148112" y1="35.583816" x2="62.148112" y2="56.404478" />
|
||||||
|
<line x1="64.92524" y1="32.635242" x2="64.92524" y2="53.455905" />
|
||||||
|
<line x1="62.148112" y1="50.204223" x2="62.148112" y2="50.507332" />
|
||||||
|
<line x1="64.85024" y1="25.899146" x2="64.85024" y2="32.635242" />
|
||||||
|
<line x1="62.073112" y1="28.847719" x2="62.073112" y2="29.686669" />
|
||||||
|
<line x1="107.34976" y1="25.899146" x2="107.34976" y2="32.635242" />
|
||||||
|
<line x1="110.126888" y1="28.847719" x2="110.126888" y2="29.686669" />
|
||||||
|
<line x1="89.881254" y1="70.807478" x2="89.881254" y2="67.010769" />
|
||||||
|
<line x1="109.577713" y1="59.435722" x2="109.577713" y2="55.639013" />
|
||||||
|
<line x1="88.75165" y1="70.155302" x2="88.75165" y2="66.358592" />
|
||||||
|
<line x1="83.44835" y1="70.155302" x2="83.44835" y2="66.358592" />
|
||||||
|
<line x1="82.318746" y1="70.807478" x2="82.318746" y2="67.010769" />
|
||||||
|
<line x1="82.318746" y1="43.69775" x2="82.318746" y2="42.166819" />
|
||||||
|
<line x1="83.44835" y1="44.349927" x2="83.44835" y2="41.514643" />
|
||||||
|
<line x1="62.622287" y1="55.069507" x2="62.622287" y2="51.272797" />
|
||||||
|
<line x1="88.75165" y1="44.349927" x2="88.75165" y2="41.514643" />
|
||||||
|
<line x1="63.75189" y1="55.721683" x2="63.75189" y2="51.924974" />
|
||||||
|
<line x1="89.881254" y1="43.69775" x2="89.881254" y2="42.166819" />
|
||||||
|
<line x1="63.75189" y1="58.783545" x2="63.75189" y2="54.986836" />
|
||||||
|
<line x1="62.622287" y1="59.435722" x2="62.622287" y2="55.639013" />
|
||||||
|
<line x1="109.577713" y1="55.069507" x2="109.577713" y2="53.538575" />
|
||||||
|
<line x1="108.44811" y1="55.721683" x2="108.44811" y2="51.924974" />
|
||||||
|
<line x1="108.44811" y1="58.783545" x2="108.44811" y2="54.986836" />
|
||||||
|
<line x1="82.265713" y1="39.93166" x2="82.265713" y2="39.871601" />
|
||||||
|
<line x1="79.720129" y1="39.93166" x2="79.720129" y2="39.871601" />
|
||||||
|
<line x1="83.395317" y1="40.583837" x2="83.395317" y2="40.522599" />
|
||||||
|
<line x1="88.804683" y1="40.583837" x2="88.804683" y2="40.522599" />
|
||||||
|
<line x1="89.934287" y1="39.93166" x2="89.934287" y2="39.871601" />
|
||||||
|
<line x1="92.479871" y1="39.93166" x2="92.479871" y2="39.871601" />
|
||||||
|
<line x1="109.52468" y1="49.772485" x2="109.52468" y2="49.711248" />
|
||||||
|
<line x1="109.52468" y1="51.242179" x2="109.52468" y2="30.421516" />
|
||||||
|
<line x1="108.395077" y1="51.894356" x2="108.395077" y2="31.073693" />
|
||||||
|
<line x1="108.395077" y1="55.017455" x2="108.395077" y2="34.196792" />
|
||||||
|
<line x1="109.52468" y1="55.669632" x2="109.52468" y2="34.848969" />
|
||||||
|
<line x1="109.52468" y1="57.139325" x2="109.52468" y2="36.318663" />
|
||||||
|
<line x1="92.479871" y1="66.98015" x2="92.479871" y2="46.159488" />
|
||||||
|
<line x1="89.934287" y1="66.98015" x2="89.934287" y2="46.159488" />
|
||||||
|
<line x1="88.804683" y1="66.327974" x2="88.804683" y2="45.507311" />
|
||||||
|
<line x1="83.395317" y1="66.327974" x2="83.395317" y2="45.507311" />
|
||||||
|
<line x1="82.265713" y1="66.98015" x2="82.265713" y2="46.159488" />
|
||||||
|
<line x1="79.720129" y1="66.98015" x2="79.720129" y2="46.159488" />
|
||||||
|
<line x1="62.67532" y1="57.139325" x2="62.67532" y2="36.318663" />
|
||||||
|
<line x1="62.67532" y1="55.669632" x2="62.67532" y2="34.848969" />
|
||||||
|
<line x1="63.804923" y1="55.017455" x2="63.804923" y2="34.196792" />
|
||||||
|
<line x1="63.804923" y1="51.894356" x2="63.804923" y2="31.073693" />
|
||||||
|
<line x1="62.67532" y1="51.242179" x2="62.67532" y2="30.421516" />
|
||||||
|
<line x1="62.67532" y1="49.772485" x2="62.67532" y2="49.711248" />
|
||||||
|
<line x1="62.622287" y1="30.452135" x2="62.622287" y2="23.716038" />
|
||||||
|
<line x1="63.75189" y1="31.104311" x2="63.75189" y2="24.368215" />
|
||||||
|
<line x1="63.75189" y1="34.166173" x2="63.75189" y2="27.430077" />
|
||||||
|
<line x1="62.622287" y1="34.81835" x2="62.622287" y2="28.082253" />
|
||||||
|
<line x1="62.622287" y1="36.349281" x2="62.622287" y2="29.613184" />
|
||||||
|
<line x1="109.577713" y1="30.452135" x2="109.577713" y2="23.716038" />
|
||||||
|
<line x1="79.667096" y1="46.190106" x2="79.667096" y2="39.454009" />
|
||||||
|
<line x1="108.44811" y1="31.104311" x2="108.44811" y2="24.368215" />
|
||||||
|
<line x1="82.318746" y1="46.190106" x2="82.318746" y2="39.454009" />
|
||||||
|
<line x1="108.44811" y1="34.166173" x2="108.44811" y2="27.430077" />
|
||||||
|
<line x1="83.44835" y1="45.53793" x2="83.44835" y2="38.801833" />
|
||||||
|
<line x1="109.577713" y1="34.81835" x2="109.577713" y2="28.082253" />
|
||||||
|
<line x1="88.75165" y1="45.53793" x2="88.75165" y2="38.801833" />
|
||||||
|
<line x1="109.577713" y1="36.349281" x2="109.577713" y2="29.613184" />
|
||||||
|
<line x1="89.881254" y1="46.190106" x2="89.881254" y2="39.454009" />
|
||||||
|
<line x1="92.532904" y1="46.190106" x2="92.532904" y2="39.454009" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 168 KiB |
|
|
@ -15,6 +15,8 @@ Cheat Sheet
|
||||||
|
|
||||||
.. grid-item-card:: 1D - BuildLine
|
.. grid-item-card:: 1D - BuildLine
|
||||||
|
|
||||||
|
| :class:`~objects_curve.ArcArcTangentArc`
|
||||||
|
| :class:`~objects_curve.ArcArcTangentLine`
|
||||||
| :class:`~objects_curve.Bezier`
|
| :class:`~objects_curve.Bezier`
|
||||||
| :class:`~objects_curve.CenterArc`
|
| :class:`~objects_curve.CenterArc`
|
||||||
| :class:`~objects_curve.DoubleTangentArc`
|
| :class:`~objects_curve.DoubleTangentArc`
|
||||||
|
|
@ -24,6 +26,8 @@ Cheat Sheet
|
||||||
| :class:`~objects_curve.IntersectingLine`
|
| :class:`~objects_curve.IntersectingLine`
|
||||||
| :class:`~objects_curve.JernArc`
|
| :class:`~objects_curve.JernArc`
|
||||||
| :class:`~objects_curve.Line`
|
| :class:`~objects_curve.Line`
|
||||||
|
| :class:`~objects_curve.PointArcTangentArc`
|
||||||
|
| :class:`~objects_curve.PointArcTangentLine`
|
||||||
| :class:`~objects_curve.PolarLine`
|
| :class:`~objects_curve.PolarLine`
|
||||||
| :class:`~objects_curve.Polyline`
|
| :class:`~objects_curve.Polyline`
|
||||||
| :class:`~objects_curve.RadiusArc`
|
| :class:`~objects_curve.RadiusArc`
|
||||||
|
|
@ -99,6 +103,7 @@ Cheat Sheet
|
||||||
|
|
||||||
| :func:`~operations_generic.add`
|
| :func:`~operations_generic.add`
|
||||||
| :func:`~operations_generic.chamfer`
|
| :func:`~operations_generic.chamfer`
|
||||||
|
| :func:`~operations_part.draft`
|
||||||
| :func:`~operations_part.extrude`
|
| :func:`~operations_part.extrude`
|
||||||
| :func:`~operations_generic.fillet`
|
| :func:`~operations_generic.fillet`
|
||||||
| :func:`~operations_part.loft`
|
| :func:`~operations_part.loft`
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,21 @@ Most of the examples show the builder and algebra modes.
|
||||||
:link: examples-benchy
|
:link: examples-benchy
|
||||||
:link-type: ref
|
:link-type: ref
|
||||||
|
|
||||||
|
.. grid-item-card:: Bicycle Tire |Builder|
|
||||||
|
:img-top: assets/examples/bicycle_tire.png
|
||||||
|
:link: examples-bicycle_tire
|
||||||
|
:link-type: ref
|
||||||
|
|
||||||
.. grid-item-card:: Canadian Flag Blowing in The Wind |Builder| |Algebra|
|
.. grid-item-card:: Canadian Flag Blowing in The Wind |Builder| |Algebra|
|
||||||
:img-top: assets/examples/example_canadian_flag_01.png
|
:img-top: assets/examples/example_canadian_flag_01.png
|
||||||
:link: examples-canadian_flag
|
:link: examples-canadian_flag
|
||||||
:link-type: ref
|
:link-type: ref
|
||||||
|
|
||||||
|
.. grid-item-card:: Cast Bearing Unit |Builder|
|
||||||
|
:img-top: assets/examples/cast_bearing_unit.png
|
||||||
|
:link: examples-cast_bearing_unit
|
||||||
|
:link-type: ref
|
||||||
|
|
||||||
.. grid-item-card:: Circuit Board With Holes |Builder| |Algebra|
|
.. grid-item-card:: Circuit Board With Holes |Builder| |Algebra|
|
||||||
:img-top: assets/examples/thumbnail_circuit_board_01.png
|
:img-top: assets/examples/thumbnail_circuit_board_01.png
|
||||||
:link: examples-circuit_board
|
:link: examples-circuit_board
|
||||||
|
|
@ -39,6 +49,11 @@ Most of the examples show the builder and algebra modes.
|
||||||
:link: clock_face
|
:link: clock_face
|
||||||
:link-type: ref
|
:link-type: ref
|
||||||
|
|
||||||
|
.. grid-item-card:: Fast Grid Holes |Algebra|
|
||||||
|
:img-top: assets/examples/fast_grid_holes.png
|
||||||
|
:link: fast_grid_holes
|
||||||
|
:link-type: ref
|
||||||
|
|
||||||
.. grid-item-card:: Handle |Builder| |Algebra|
|
.. grid-item-card:: Handle |Builder| |Algebra|
|
||||||
:img-top: assets/examples/handle.png
|
:img-top: assets/examples/handle.png
|
||||||
:link: handle
|
:link: handle
|
||||||
|
|
@ -154,6 +169,24 @@ modify it by replacing chimney with a BREP version.
|
||||||
|
|
||||||
.. ----------------------------------------------------------------------------------------------
|
.. ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.. _examples-bicycle_tire:
|
||||||
|
|
||||||
|
Bicycle Tire
|
||||||
|
--------------------------------
|
||||||
|
.. image:: assets/examples/bicycle_tire.png
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
This example demonstrates how to model a realistic bicycle tire with a
|
||||||
|
patterned tread using build123d. The key concept showcased here is the
|
||||||
|
use of wrap_faces to project 2D planar geometry onto a curved 3D
|
||||||
|
surface.
|
||||||
|
|
||||||
|
.. dropdown:: |Builder| Reference Implementation (Builder Mode)
|
||||||
|
|
||||||
|
.. literalinclude:: ../examples/bicycle_tire.py
|
||||||
|
:start-after: [Code]
|
||||||
|
:end-before: [End]
|
||||||
|
|
||||||
.. _examples-build123d_logo:
|
.. _examples-build123d_logo:
|
||||||
|
|
||||||
Former build123d Logo
|
Former build123d Logo
|
||||||
|
|
@ -181,6 +214,23 @@ The builder mode example also generates the SVG file `logo.svg`.
|
||||||
:end-before: [End]
|
:end-before: [End]
|
||||||
|
|
||||||
|
|
||||||
|
.. _examples-cast_bearing_unit:
|
||||||
|
|
||||||
|
Cast Bearing Unit
|
||||||
|
-----------------
|
||||||
|
.. image:: assets/examples/cast_bearing_unit.png
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
This example demonstrates the creation of a castable flanged bearing housing
|
||||||
|
using the `draft` operation to add appropriate draft angles for mold release.
|
||||||
|
|
||||||
|
|
||||||
|
.. dropdown:: |Builder| Reference Implementation (Builder Mode)
|
||||||
|
|
||||||
|
.. literalinclude:: ../examples/cast_bearing_unit.py
|
||||||
|
:start-after: [Code]
|
||||||
|
:end-before: [End]
|
||||||
|
|
||||||
.. _examples-canadian_flag:
|
.. _examples-canadian_flag:
|
||||||
|
|
||||||
Canadian Flag Blowing in The Wind
|
Canadian Flag Blowing in The Wind
|
||||||
|
|
@ -280,6 +330,32 @@ a detailed and visually appealing clock design.
|
||||||
|
|
||||||
:class:`~build_common.PolarLocations` are used to position features on the clock face.
|
:class:`~build_common.PolarLocations` are used to position features on the clock face.
|
||||||
|
|
||||||
|
.. _fast_grid_holes:
|
||||||
|
|
||||||
|
Fast Grid Holes
|
||||||
|
---------------
|
||||||
|
.. image:: assets/examples/fast_grid_holes.png
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
.. dropdown:: |Algebra| Reference Implementation (Algebra Mode)
|
||||||
|
|
||||||
|
.. literalinclude:: ../examples/fast_grid_holes.py
|
||||||
|
:start-after: [Code]
|
||||||
|
:end-before: [End]
|
||||||
|
|
||||||
|
This example demonstrates an efficient approach to creating a large number of holes
|
||||||
|
(625 in this case) in a planar part using build123d.
|
||||||
|
|
||||||
|
Instead of modeling and subtracting 3D solids for each hole—which is computationally
|
||||||
|
expensive—this method constructs a 2D Face from an outer perimeter wire and a list of
|
||||||
|
hole wires. The entire face is then extruded in a single operation to form the final
|
||||||
|
3D object. This approach significantly reduces modeling time and complexity.
|
||||||
|
|
||||||
|
The hexagonal hole pattern is generated using HexLocations, and each location is
|
||||||
|
populated with a hexagonal wire. These wires are passed directly to the Face constructor
|
||||||
|
as holes. On a typical Linux laptop, this script completes in approximately 1.02 seconds,
|
||||||
|
compared to substantially longer runtimes for boolean subtraction of individual holes in 3D.
|
||||||
|
|
||||||
|
|
||||||
.. _handle:
|
.. _handle:
|
||||||
|
|
||||||
|
|
@ -473,7 +549,7 @@ Stud Wall
|
||||||
.. image:: assets/examples/stud_wall.png
|
.. image:: assets/examples/stud_wall.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
This example demonstrates creatings custom `Part` objects and putting them into
|
This example demonstrates creating custom `Part` objects and putting them into
|
||||||
assemblies. The custom object is a `Stud` used in the building industry while
|
assemblies. The custom object is a `Stud` used in the building industry while
|
||||||
the assembly is a `StudWall` created from copies of `Stud` objects for efficiency.
|
the assembly is a `StudWall` created from copies of `Stud` objects for efficiency.
|
||||||
Both the `Stud` and `StudWall` objects use `RigidJoints` to define snap points which
|
Both the `Stud` and `StudWall` objects use `RigidJoints` to define snap points which
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ flanges are attached to the ends of a curved pipe:
|
||||||
|
|
||||||
Note how the locations of the joints are determined by the :meth:`~topology.Mixin1D.location_at` method
|
Note how the locations of the joints are determined by the :meth:`~topology.Mixin1D.location_at` method
|
||||||
and how the ``-`` negate operator is used to reverse the direction of the location without changing its
|
and how the ``-`` negate operator is used to reverse the direction of the location without changing its
|
||||||
poosition. Also note that the ``WeldNeckFlange`` class predefines two joints, one at the pipe end and
|
position. Also note that the ``WeldNeckFlange`` class predefines two joints, one at the pipe end and
|
||||||
one at the face end - both of which are shown in the above image (generated by ocp-vscode with the
|
one at the face end - both of which are shown in the above image (generated by ocp-vscode with the
|
||||||
``render_joints=True`` flag set in the ``show`` function).
|
``render_joints=True`` flag set in the ``show`` function).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,51 +21,53 @@ The following table summarizes all of the available operations. Operations marke
|
||||||
applicable to BuildLine and Algebra Curve, 2D to BuildSketch and Algebra Sketch, 3D to
|
applicable to BuildLine and Algebra Curve, 2D to BuildSketch and Algebra Sketch, 3D to
|
||||||
BuildPart and Algebra Part.
|
BuildPart and Algebra Part.
|
||||||
|
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| Operation | Description | 0D | 1D | 2D | 3D | Example |
|
| Operation | Description | 0D | 1D | 2D | 3D | Example |
|
||||||
+==============================================+====================================+====+====+====+====+========================+
|
+==============================================+====================================+====+====+====+====+===================================+
|
||||||
| :func:`~operations_generic.add` | Add object to builder | | ✓ | ✓ | ✓ | :ref:`16 <ex 16>` |
|
| :func:`~operations_generic.add` | Add object to builder | | ✓ | ✓ | ✓ | :ref:`16 <ex 16>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.bounding_box` | Add bounding box as Shape | | ✓ | ✓ | ✓ | |
|
| :func:`~operations_generic.bounding_box` | Add bounding box as Shape | | ✓ | ✓ | ✓ | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.chamfer` | Bevel Vertex or Edge | | | ✓ | ✓ | :ref:`9 <ex 9>` |
|
| :func:`~operations_generic.chamfer` | Bevel Vertex or Edge | | | ✓ | ✓ | :ref:`9 <ex 9>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
|
| :func:`~operations_part.draft` | Add a draft taper to a part | | | | ✓ | :ref:`examples-cast_bearing_unit` |
|
||||||
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_part.extrude` | Draw 2D Shape into 3D | | | | ✓ | :ref:`3 <ex 3>` |
|
| :func:`~operations_part.extrude` | Draw 2D Shape into 3D | | | | ✓ | :ref:`3 <ex 3>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.fillet` | Radius Vertex or Edge | | | ✓ | ✓ | :ref:`9 <ex 9>` |
|
| :func:`~operations_generic.fillet` | Radius Vertex or Edge | | | ✓ | ✓ | :ref:`9 <ex 9>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_sketch.full_round` | Round-off Face along given Edge | | | ✓ | | :ref:`ttt-24-spo-06` |
|
| :func:`~operations_sketch.full_round` | Round-off Face along given Edge | | | ✓ | | :ref:`ttt-24-spo-06` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_part.loft` | Create 3D Shape from sections | | | | ✓ | :ref:`24 <ex 24>` |
|
| :func:`~operations_part.loft` | Create 3D Shape from sections | | | | ✓ | :ref:`24 <ex 24>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_part.make_brake_formed` | Create sheet metal parts | | | | ✓ | |
|
| :func:`~operations_part.make_brake_formed` | Create sheet metal parts | | | | ✓ | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_sketch.make_face` | Create a Face from Edges | | | ✓ | | :ref:`4 <ex 4>` |
|
| :func:`~operations_sketch.make_face` | Create a Face from Edges | | | ✓ | | :ref:`4 <ex 4>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_sketch.make_hull` | Create Convex Hull from Edges | | | ✓ | | |
|
| :func:`~operations_sketch.make_hull` | Create Convex Hull from Edges | | | ✓ | | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.mirror` | Mirror about Plane | | ✓ | ✓ | ✓ | :ref:`15 <ex 15>` |
|
| :func:`~operations_generic.mirror` | Mirror about Plane | | ✓ | ✓ | ✓ | :ref:`15 <ex 15>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.offset` | Inset or outset Shape | | ✓ | ✓ | ✓ | :ref:`25 <ex 25>` |
|
| :func:`~operations_generic.offset` | Inset or outset Shape | | ✓ | ✓ | ✓ | :ref:`25 <ex 25>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.project` | Project points, lines or Faces | ✓ | ✓ | ✓ | | |
|
| :func:`~operations_generic.project` | Project points, lines or Faces | ✓ | ✓ | ✓ | | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_part.project_workplane` | Create workplane for projection | | | | | |
|
| :func:`~operations_part.project_workplane` | Create workplane for projection | | | | | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_part.revolve` | Swing 2D Shape about Axis | | | | ✓ | :ref:`23 <ex 23>` |
|
| :func:`~operations_part.revolve` | Swing 2D Shape about Axis | | | | ✓ | :ref:`23 <ex 23>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.scale` | Change size of Shape | | ✓ | ✓ | ✓ | |
|
| :func:`~operations_generic.scale` | Change size of Shape | | ✓ | ✓ | ✓ | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_part.section` | Generate 2D slices from 3D Shape | | | | ✓ | |
|
| :func:`~operations_part.section` | Generate 2D slices from 3D Shape | | | | ✓ | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.split` | Divide object by Plane | | ✓ | ✓ | ✓ | :ref:`27 <ex 27>` |
|
| :func:`~operations_generic.split` | Divide object by Plane | | ✓ | ✓ | ✓ | :ref:`27 <ex 27>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_generic.sweep` | Extrude 1/2D section(s) along path | | | ✓ | ✓ | :ref:`14 <ex 14>` |
|
| :func:`~operations_generic.sweep` | Extrude 1/2D section(s) along path | | | ✓ | ✓ | :ref:`14 <ex 14>` |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_part.thicken` | Expand 2D section(s) | | | | ✓ | |
|
| :func:`~operations_part.thicken` | Expand 2D section(s) | | | | ✓ | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
| :func:`~operations_sketch.trace` | Convert lines to faces | | | ✓ | | |
|
| :func:`~operations_sketch.trace` | Convert lines to faces | | | ✓ | | |
|
||||||
+----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
|
+----------------------------------------------+------------------------------------+----+----+----+----+-----------------------------------+
|
||||||
|
|
||||||
The following table summarizes all of the selectors that can be used within
|
The following table summarizes all of the selectors that can be used within
|
||||||
the scope of a Builder. Note that they will extract objects from the builder that is
|
the scope of a Builder. Note that they will extract objects from the builder that is
|
||||||
|
|
@ -104,6 +106,7 @@ Reference
|
||||||
.. autofunction:: operations_generic.add
|
.. autofunction:: operations_generic.add
|
||||||
.. autofunction:: operations_generic.bounding_box
|
.. autofunction:: operations_generic.bounding_box
|
||||||
.. autofunction:: operations_generic.chamfer
|
.. autofunction:: operations_generic.chamfer
|
||||||
|
.. autofunction:: operations_part.draft
|
||||||
.. autofunction:: operations_part.extrude
|
.. autofunction:: operations_part.extrude
|
||||||
.. autofunction:: operations_generic.fillet
|
.. autofunction:: operations_generic.fillet
|
||||||
.. autofunction:: operations_sketch.full_round
|
.. autofunction:: operations_sketch.full_round
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,7 @@ from bd_warehouse.thread import IsoThread
|
||||||
from ocp_vscode import *
|
from ocp_vscode import *
|
||||||
|
|
||||||
# Create the thread so the min radius is available below
|
# Create the thread so the min radius is available below
|
||||||
thread = IsoThread(
|
thread = IsoThread(major_diameter=6, pitch=1, length=20, end_finishes=("fade", "raw"))
|
||||||
major_diameter=8, pitch=1.25, length=20, end_finishes=("fade", "raw")
|
|
||||||
)
|
|
||||||
inner_radius = 15.89 / 2
|
inner_radius = 15.89 / 2
|
||||||
inner_gap = 0.2
|
inner_gap = 0.2
|
||||||
|
|
||||||
|
|
@ -52,4 +50,4 @@ with BuildPart() as ball:
|
||||||
|
|
||||||
rod_end.part.joints["socket"].connect_to(ball.part.joints["ball"], angles=(5, 10, 0))
|
rod_end.part.joints["socket"].connect_to(ball.part.joints["ball"], angles=(5, 10, 0))
|
||||||
|
|
||||||
show(rod_end.part, ball.part)
|
show(rod_end.part, ball.part, s2)
|
||||||
|
|
|
||||||
73
docs/tech_drawing_tutorial.rst
Normal file
73
docs/tech_drawing_tutorial.rst
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
.. _tech_drawing_tutorial:
|
||||||
|
|
||||||
|
##########################
|
||||||
|
Technical Drawing Tutorial
|
||||||
|
##########################
|
||||||
|
|
||||||
|
This example demonstrates how to generate a standard technical drawing of a 3D part
|
||||||
|
using `build123d`. It creates orthographic and isometric views of a Nema 23 stepper
|
||||||
|
motor and exports the result as an SVG file suitable for printing or inspection.
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------
|
||||||
|
|
||||||
|
A technical drawing represents a 3D object in 2D using a series of standardized views.
|
||||||
|
These include:
|
||||||
|
|
||||||
|
- **Plan (Top View)** – as seen from directly above (Z-axis down)
|
||||||
|
- **Front Elevation** – looking at the object head-on (Y-axis forward)
|
||||||
|
- **Side Elevation (Right Side)** – viewed from the right (X-axis)
|
||||||
|
- **Isometric Projection** – a 3D perspective view to help visualize depth
|
||||||
|
|
||||||
|
Each view is aligned to a position on the page and optionally scaled or annotated.
|
||||||
|
|
||||||
|
How It Works
|
||||||
|
------------
|
||||||
|
|
||||||
|
The script uses the `project_to_viewport` method to project the 3D part geometry into 2D.
|
||||||
|
A helper function, `project_to_2d`, sets up the viewport (camera origin and up direction)
|
||||||
|
and places the result onto a virtual drawing sheet.
|
||||||
|
|
||||||
|
The steps involved are:
|
||||||
|
|
||||||
|
1. Load or construct a 3D part (in this case, a stepper motor).
|
||||||
|
2. Define a `TechnicalDrawing` border and title block using A4 page size.
|
||||||
|
3. Generate each of the standard views and apply transformations to place them.
|
||||||
|
4. Add dimensions using `ExtensionLine` and labels using `Text`.
|
||||||
|
5. Export the drawing using `ExportSVG`, separating visible and hidden edges by layer
|
||||||
|
and style.
|
||||||
|
|
||||||
|
Result
|
||||||
|
------
|
||||||
|
|
||||||
|
.. image:: /assets/stepper_drawing.svg
|
||||||
|
:alt: Stepper motor technical drawing
|
||||||
|
:class: align-center
|
||||||
|
:width: 80%
|
||||||
|
|
||||||
|
Try It Yourself
|
||||||
|
---------------
|
||||||
|
|
||||||
|
You can modify the script to:
|
||||||
|
|
||||||
|
- Replace the part with your own `Part` model
|
||||||
|
- Adjust camera angles and scale
|
||||||
|
- Add other views (bottom, rear)
|
||||||
|
- Enhance with more labels and dimensions
|
||||||
|
|
||||||
|
Code
|
||||||
|
----
|
||||||
|
|
||||||
|
.. literalinclude:: technical_drawing.py
|
||||||
|
:language: python
|
||||||
|
:start-after: [code]
|
||||||
|
:end-before: [end]
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
This example depends on the following packages:
|
||||||
|
|
||||||
|
- `build123d`
|
||||||
|
- `bd_warehouse` (for the `StepperMotor` part)
|
||||||
|
- `ocp_vscode` (for local preview)
|
||||||
188
docs/technical_drawing.py
Normal file
188
docs/technical_drawing.py
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: technical_drawing.py
|
||||||
|
by: gumyr
|
||||||
|
date: May 23, 2025
|
||||||
|
|
||||||
|
desc:
|
||||||
|
|
||||||
|
Generate a multi-view technical drawing of a part, including isometric and
|
||||||
|
orthographic projections.
|
||||||
|
|
||||||
|
This module demonstrates how to create a standard technical drawing using
|
||||||
|
`build123d`. It includes:
|
||||||
|
- Projection of a 3D part to 2D views (plan, front, side, isometric)
|
||||||
|
- Drawing borders and dimensioning using extension lines
|
||||||
|
- SVG export of visible and hidden geometry
|
||||||
|
- Example part: Nema 23 stepper motor from `bd_warehouse.open_builds`
|
||||||
|
|
||||||
|
The following standard views are generated:
|
||||||
|
- Plan View (Top)
|
||||||
|
- Front Elevation
|
||||||
|
- Side Elevation (Right Side)
|
||||||
|
- Isometric Projection
|
||||||
|
|
||||||
|
The resulting drawing is exported as an SVG and can be previewed using
|
||||||
|
the `ocp_vscode` viewer.
|
||||||
|
|
||||||
|
license:
|
||||||
|
|
||||||
|
Copyright 2025 gumyr
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# [code]
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from bd_warehouse.open_builds import StepperMotor
|
||||||
|
from build123d import *
|
||||||
|
from ocp_vscode import show
|
||||||
|
|
||||||
|
|
||||||
|
def project_to_2d(
|
||||||
|
part: Part,
|
||||||
|
viewport_origin: VectorLike,
|
||||||
|
viewport_up: VectorLike,
|
||||||
|
page_origin: VectorLike,
|
||||||
|
scale_factor: float = 1.0,
|
||||||
|
) -> tuple[ShapeList[Edge], ShapeList[Edge]]:
|
||||||
|
"""project_to_2d
|
||||||
|
|
||||||
|
Helper function to generate 2d views translated on the 2d page.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
part (Part): 3d object
|
||||||
|
viewport_origin (VectorLike): location of viewport
|
||||||
|
viewport_up (VectorLike): direction of the viewport Y axis
|
||||||
|
page_origin (VectorLike): center of 2d object on page
|
||||||
|
scale_factor (float, optional): part scalar. Defaults to 1.0.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[ShapeList[Edge], ShapeList[Edge]]: visible & hidden edges
|
||||||
|
"""
|
||||||
|
scaled_part = part if scale_factor == 1.0 else scale(part, scale_factor)
|
||||||
|
visible, hidden = scaled_part.project_to_viewport(
|
||||||
|
viewport_origin, viewport_up, look_at=(0, 0, 0)
|
||||||
|
)
|
||||||
|
visible = [Pos(*page_origin) * e for e in visible]
|
||||||
|
hidden = [Pos(*page_origin) * e for e in hidden]
|
||||||
|
|
||||||
|
return ShapeList(visible), ShapeList(hidden)
|
||||||
|
|
||||||
|
|
||||||
|
# The object that appearing in the drawing
|
||||||
|
stepper: Part = StepperMotor("Nema23")
|
||||||
|
|
||||||
|
# Create a standard technical drawing border on A4 paper
|
||||||
|
border = TechnicalDrawing(
|
||||||
|
designed_by="build123d",
|
||||||
|
design_date=date.fromisoformat("2025-05-23"),
|
||||||
|
page_size=PageSize.A4,
|
||||||
|
title="Nema 23 Stepper",
|
||||||
|
sub_title="Units: mm",
|
||||||
|
drawing_number="BD-1",
|
||||||
|
sheet_number=1,
|
||||||
|
drawing_scale=1,
|
||||||
|
)
|
||||||
|
page_size = border.bounding_box().size
|
||||||
|
|
||||||
|
# Specify the drafting options for extension lines
|
||||||
|
drafting_options = Draft(font_size=3.5, decimal_precision=1, display_units=False)
|
||||||
|
|
||||||
|
# Lists used to store the 2d visible and hidden lines
|
||||||
|
visible_lines, hidden_lines = [], []
|
||||||
|
|
||||||
|
# Isometric Projection - A 3D view where the part is rotated to reveal three
|
||||||
|
# dimensions equally.
|
||||||
|
iso_v, iso_h = project_to_2d(
|
||||||
|
stepper,
|
||||||
|
(100, 100, 100),
|
||||||
|
(0, 0, 1),
|
||||||
|
page_size * 0.3,
|
||||||
|
0.75,
|
||||||
|
)
|
||||||
|
visible_lines.extend(iso_v)
|
||||||
|
hidden_lines.extend(iso_h)
|
||||||
|
|
||||||
|
# Plan View (Top) - The view from directly above the part (looking down along
|
||||||
|
# the Z-axis).
|
||||||
|
vis, _ = project_to_2d(
|
||||||
|
stepper,
|
||||||
|
(0, 0, 100),
|
||||||
|
(0, 1, 0),
|
||||||
|
(page_size.X * -0.3, page_size.Y * 0.25),
|
||||||
|
)
|
||||||
|
visible_lines.extend(vis)
|
||||||
|
|
||||||
|
# Dimension the top of the stepper
|
||||||
|
top_bbox = Curve(vis).bounding_box()
|
||||||
|
perimeter = Pos(*top_bbox.center()) * Rectangle(top_bbox.size.X, top_bbox.size.Y)
|
||||||
|
d1 = ExtensionLine(
|
||||||
|
border=perimeter.edges().sort_by(Axis.X)[-1], offset=1 * CM, draft=drafting_options
|
||||||
|
)
|
||||||
|
d2 = ExtensionLine(
|
||||||
|
border=perimeter.edges().sort_by(Axis.Y)[0], offset=1 * CM, draft=drafting_options
|
||||||
|
)
|
||||||
|
# Add a label
|
||||||
|
l1 = Text("Plan View", 6)
|
||||||
|
l1.position = vis.sort_by(Axis.Y)[-1].center() + (0, 5 * MM)
|
||||||
|
|
||||||
|
# Front Elevation - The primary view, typically looking along the Y-axis,
|
||||||
|
# showing the height.
|
||||||
|
vis, _ = project_to_2d(
|
||||||
|
stepper,
|
||||||
|
(0, -100, 0),
|
||||||
|
(0, 0, 1),
|
||||||
|
(page_size.X * -0.3, page_size.Y * -0.125),
|
||||||
|
)
|
||||||
|
visible_lines.extend(vis)
|
||||||
|
d3 = ExtensionLine(
|
||||||
|
border=vis.sort_by(Axis.Y)[-1], offset=-5 * MM, draft=drafting_options
|
||||||
|
)
|
||||||
|
l2 = Text("Front Elevation", 6)
|
||||||
|
l2.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM)
|
||||||
|
|
||||||
|
# Side Elevation - Often refers to the Right Side View, looking along the X-axis.
|
||||||
|
vis, _ = project_to_2d(
|
||||||
|
stepper,
|
||||||
|
(100, 0, 0),
|
||||||
|
(0, 0, 1),
|
||||||
|
(0, page_size.Y * 0.15),
|
||||||
|
)
|
||||||
|
visible_lines.extend(vis)
|
||||||
|
side_bbox = Curve(vis).bounding_box()
|
||||||
|
perimeter = Pos(*side_bbox.center()) * Rectangle(side_bbox.size.X, side_bbox.size.Y)
|
||||||
|
d4 = ExtensionLine(
|
||||||
|
border=perimeter.edges().sort_by(Axis.X)[-1], offset=1 * CM, draft=drafting_options
|
||||||
|
)
|
||||||
|
l3 = Text("Side Elevation", 6)
|
||||||
|
l3.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM)
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize the SVG exporter
|
||||||
|
exporter = ExportSVG(unit=Unit.MM)
|
||||||
|
# Define visible and hidden line layers
|
||||||
|
exporter.add_layer("Visible")
|
||||||
|
exporter.add_layer("Hidden", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)
|
||||||
|
# Add the objects to the appropriate layer
|
||||||
|
exporter.add_shape(visible_lines, layer="Visible")
|
||||||
|
exporter.add_shape(hidden_lines, layer="Hidden")
|
||||||
|
exporter.add_shape(border, layer="Visible")
|
||||||
|
exporter.add_shape([d1, d2, d3, d4], layer="Visible")
|
||||||
|
exporter.add_shape([l1, l2, l3], layer="Visible")
|
||||||
|
# Write the file
|
||||||
|
exporter.write(f"assets/stepper_drawing.svg")
|
||||||
|
|
||||||
|
show(border, visible_lines, d1, d2, d3, d4, l1, l2, l3)
|
||||||
|
# [end]
|
||||||
4030
docs/topology_selection/examples/nema-17-bracket.step
Normal file
4030
docs/topology_selection/examples/nema-17-bracket.step
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -159,7 +159,7 @@ class Hinge(Compound):
|
||||||
for hole, hole_location in enumerate(hole_locations):
|
for hole, hole_location in enumerate(hole_locations):
|
||||||
CylindricalJoint(
|
CylindricalJoint(
|
||||||
label="hole" + str(hole),
|
label="hole" + str(hole),
|
||||||
axis=hole_location.to_axis(),
|
axis=Axis(hole_location),
|
||||||
linear_range=(-2 * CM, 2 * CM),
|
linear_range=(-2 * CM, 2 * CM),
|
||||||
angular_range=(0, 360),
|
angular_range=(0, 360),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,4 @@ as later tutorials build on the concepts introduced in earlier ones.
|
||||||
examples_1.rst
|
examples_1.rst
|
||||||
tttt.rst
|
tttt.rst
|
||||||
tutorial_surface_modeling.rst
|
tutorial_surface_modeling.rst
|
||||||
|
tech_drawing_tutorial.rst
|
||||||
|
|
|
||||||
109
examples/bicycle_tire.py
Normal file
109
examples/bicycle_tire.py
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
"""
|
||||||
|
A bicycle tire with tread.
|
||||||
|
|
||||||
|
name: bicycle_tire.py
|
||||||
|
by: Gumyr
|
||||||
|
date: May 20, 2025
|
||||||
|
|
||||||
|
desc:
|
||||||
|
|
||||||
|
This example demonstrates how to model a realistic bicycle tire with a
|
||||||
|
patterned tread using build123d. The key concept showcased here is the
|
||||||
|
use of wrap_faces to project 2D planar geometry onto a curved 3D
|
||||||
|
surface.
|
||||||
|
|
||||||
|
The tire cross-section is defined using a series of Bezier curves and
|
||||||
|
revolved to form the main tire body. A 2D tread pattern is created as a
|
||||||
|
sketch on a plane and then wrapped onto the non-planar revolved surface
|
||||||
|
using wrap_faces, following a path on the surface. The wrapped faces are
|
||||||
|
then thickened into 3D solid nubs and copied around the tire using
|
||||||
|
rotational placement.
|
||||||
|
|
||||||
|
This technique is particularly useful for applying surface detail—such
|
||||||
|
as grooves, logos, or textures—to curved or freeform geometries in a CAD
|
||||||
|
model.
|
||||||
|
|
||||||
|
Highlights:
|
||||||
|
- Complex profile creation using multiple Bezier segments.
|
||||||
|
- Surface wrapping of planar sketches using wrap_faces.
|
||||||
|
- Solidification of surface features via thicken.
|
||||||
|
- Circular duplication of solids using rotational transforms.
|
||||||
|
|
||||||
|
license:
|
||||||
|
|
||||||
|
Copyright 2025 Gumyr
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# [Code]
|
||||||
|
import copy
|
||||||
|
from build123d import *
|
||||||
|
from ocp_vscode import show
|
||||||
|
|
||||||
|
wheel_diameter = 740 * MM
|
||||||
|
|
||||||
|
with BuildSketch() as tire_profile:
|
||||||
|
with BuildLine() as build_profile:
|
||||||
|
l00 = Bezier((0.0, 0.0), (7.05, 0.0), (12.18, 1.54), (15.13, 4.54))
|
||||||
|
l01 = Bezier(l00 @ 1, (15.81, 5.22), (15.98, 5.44), (16.5, 6.23))
|
||||||
|
l02 = Bezier(l01 @ 1, (18.45, 9.19), (19.61, 13.84), (19.94, 20.06))
|
||||||
|
l03 = Bezier(l02 @ 1, (20.1, 23.24), (19.93, 27.48), (19.56, 29.45))
|
||||||
|
l04 = Bezier(l03 @ 1, (19.13, 31.69), (18.23, 33.67), (16.91, 35.32))
|
||||||
|
l05 = Bezier(l04 @ 1, (16.26, 36.12), (15.57, 36.77), (14.48, 37.58))
|
||||||
|
l06 = Bezier(l05 @ 1, (12.77, 38.85), (11.51, 40.28), (10.76, 41.78))
|
||||||
|
l07 = Bezier(l06 @ 1, (10.07, 43.16), (10.15, 43.81), (11.03, 43.98))
|
||||||
|
l08 = Bezier(l07 @ 1, (11.82, 44.13), (12.15, 44.55), (12.08, 45.33))
|
||||||
|
l09 = Bezier(l08 @ 1, (12.01, 46.07), (11.84, 46.43), (11.43, 46.69))
|
||||||
|
l10 = Bezier(l09 @ 1, (10.98, 46.97), (10.07, 46.7), (9.47, 46.1))
|
||||||
|
l11 = Bezier(l10 @ 1, (9.03, 45.65), (8.88, 45.31), (8.84, 44.65))
|
||||||
|
l12 = Bezier(l11 @ 1, (8.78, 43.6), (9.11, 42.26), (9.72, 41.0))
|
||||||
|
l13 = Bezier(l12 @ 1, (10.43, 39.54), (11.52, 38.2), (12.78, 37.22))
|
||||||
|
l14 = Bezier(l13 @ 1, (15.36, 35.23), (16.58, 33.76), (17.45, 31.62))
|
||||||
|
l15 = Bezier(l14 @ 1, (17.91, 30.49), (18.22, 29.27), (18.4, 27.8))
|
||||||
|
l16 = Bezier(l15 @ 1, (18.53, 26.78), (18.52, 23.69), (18.37, 22.61))
|
||||||
|
l17 = Bezier(l16 @ 1, (17.8, 18.23), (16.15, 14.7), (13.39, 11.94))
|
||||||
|
l18 = Bezier(l17 @ 1, (11.89, 10.45), (10.19, 9.31), (8.09, 8.41))
|
||||||
|
l19 = Bezier(l18 @ 1, (3.32, 6.35), (0.0, 6.64))
|
||||||
|
mirror(about=Plane.YZ)
|
||||||
|
make_face()
|
||||||
|
|
||||||
|
tire = revolve(Pos(Y=-wheel_diameter / 2) * tire_profile.face(), Axis.X)
|
||||||
|
|
||||||
|
with BuildSketch() as tread_pattern:
|
||||||
|
with Locations((1, 1)):
|
||||||
|
Trapezoid(15, 12, 60, 120, align=Align.MIN)
|
||||||
|
with Locations((1, 8)):
|
||||||
|
with GridLocations(0, 5, 1, 2):
|
||||||
|
Rectangle(50, 2, mode=Mode.SUBTRACT)
|
||||||
|
|
||||||
|
# Define the surface and path that the tread pattern will be wrapped onto
|
||||||
|
half_road_surface = Face.revolve(Pos(Y=-wheel_diameter / 2) * l00, 360, Axis.X)
|
||||||
|
tread_path = half_road_surface.edges().sort_by(Axis.X)[0]
|
||||||
|
|
||||||
|
# Wrap the planar tread pattern onto the tire's outside surface
|
||||||
|
tread_faces = half_road_surface.wrap_faces(tread_pattern.faces(), tread_path)
|
||||||
|
|
||||||
|
# Mirror the faces to the other half of the tire
|
||||||
|
tread_faces.extend([mirror(t, Plane.YZ) for t in tread_faces])
|
||||||
|
|
||||||
|
# Thicken the tread to become solid nubs
|
||||||
|
# tread_prime = [Solid.thicken(f, 3 * MM) for f in tread_faces]
|
||||||
|
tread_prime = [thicken(f, 3 * MM) for f in tread_faces]
|
||||||
|
|
||||||
|
# Copy the nubs around the whole tire
|
||||||
|
tread = [Rot(X=r) * copy.copy(t) for t in tread_prime for r in range(0, 360, 2)]
|
||||||
|
|
||||||
|
show(tire, tread)
|
||||||
|
# [End]
|
||||||
73
examples/cast_bearing_unit.py
Normal file
73
examples/cast_bearing_unit.py
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
"""
|
||||||
|
An oval flanged bearing unit with tapered sides created with the draft operation.
|
||||||
|
|
||||||
|
name: cast_bearing_unit.py
|
||||||
|
by: Gumyr
|
||||||
|
date: May 25, 2025
|
||||||
|
|
||||||
|
desc:
|
||||||
|
|
||||||
|
This example demonstrates the creation of a castable flanged bearing housing
|
||||||
|
using the `draft` operation to add appropriate draft angles for mold release.
|
||||||
|
|
||||||
|
### Highlights:
|
||||||
|
|
||||||
|
- **Component Integration**: The design incorporates a press-fit bore for a
|
||||||
|
`SingleRowAngularContactBallBearing` and mounting holes for
|
||||||
|
`SocketHeadCapScrew` fasteners.
|
||||||
|
- **Draft Angle Application**: Vertical side faces are identified and modified
|
||||||
|
with a 4-degree draft angle using the `draft()` function. This simulates the
|
||||||
|
taper needed for cast parts to be removed cleanly from a mold.
|
||||||
|
- **Filleting**: All edges are filleted to reflect casting-friendly geometry and
|
||||||
|
improve aesthetics.
|
||||||
|
- **Parametric Design**: Dimensions such as bolt spacing, bearing size, and
|
||||||
|
housing depth are parameterized for reuse and adaptation to other sizes.
|
||||||
|
|
||||||
|
The result is a realistic, fabrication-aware model that can be used for
|
||||||
|
documentation, simulation, or manufacturing workflows. The final assembly
|
||||||
|
includes the housing, inserted bearing, and positioned screws, rendered with
|
||||||
|
appropriate coloring for clarity.
|
||||||
|
|
||||||
|
license:
|
||||||
|
|
||||||
|
Copyright 2025 Gumyr
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# [Code]
|
||||||
|
from build123d import *
|
||||||
|
from ocp_vscode import show
|
||||||
|
|
||||||
|
A, A1, Db2, H, J = 26, 11, 57, 98.5, 76.5
|
||||||
|
with BuildPart() as oval_flanged_bearing_unit:
|
||||||
|
with BuildSketch() as plan:
|
||||||
|
housing = Circle(Db2 / 2)
|
||||||
|
with GridLocations(J, 0, 2, 1) as bolt_centers:
|
||||||
|
Circle((H - J) / 2)
|
||||||
|
make_hull()
|
||||||
|
extrude(amount=A1)
|
||||||
|
extrude(housing, amount=A)
|
||||||
|
drafted_faces = oval_flanged_bearing_unit.faces().filter_by(Axis.Z, reverse=True)
|
||||||
|
draft(drafted_faces, Plane.XY, 4)
|
||||||
|
fillet(oval_flanged_bearing_unit.edges(), 1)
|
||||||
|
with Locations(oval_flanged_bearing_unit.faces().sort_by(Axis.Z)[-1]):
|
||||||
|
CounterBoreHole(14 / 2, 47 / 2, 14)
|
||||||
|
with Locations(*bolt_centers):
|
||||||
|
Hole(5)
|
||||||
|
|
||||||
|
oval_flanged_bearing_unit.part.color = Color(0x4C6377)
|
||||||
|
|
||||||
|
show(oval_flanged_bearing_unit)
|
||||||
|
# [End]
|
||||||
65
examples/fast_grid_holes.py
Normal file
65
examples/fast_grid_holes.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""
|
||||||
|
A fast way to make many holes.
|
||||||
|
|
||||||
|
name: fast_grid_holes.py
|
||||||
|
by: Gumyr
|
||||||
|
date: May 31, 2025
|
||||||
|
|
||||||
|
desc:
|
||||||
|
|
||||||
|
This example demonstrates an efficient approach to creating a large number of holes
|
||||||
|
(625 in this case) in a planar part using build123d.
|
||||||
|
|
||||||
|
Instead of modeling and subtracting 3D solids for each hole—which is computationally
|
||||||
|
expensive—this method constructs a 2D Face from an outer perimeter wire and a list of
|
||||||
|
hole wires. The entire face is then extruded in a single operation to form the final
|
||||||
|
3D object. This approach significantly reduces modeling time and complexity.
|
||||||
|
|
||||||
|
The hexagonal hole pattern is generated using HexLocations, and each location is
|
||||||
|
populated with a hexagonal wire. These wires are passed directly to the Face constructor
|
||||||
|
as holes. On a typical Linux laptop, this script completes in approximately 1.02 seconds,
|
||||||
|
compared to substantially longer runtimes for boolean subtraction of individual holes in 3D.
|
||||||
|
|
||||||
|
license:
|
||||||
|
|
||||||
|
Copyright 2025 Gumyr
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# [Code]
|
||||||
|
import timeit
|
||||||
|
from build123d import *
|
||||||
|
from ocp_vscode import show
|
||||||
|
|
||||||
|
start_time = timeit.default_timer()
|
||||||
|
|
||||||
|
# Calculate the locations of 625 holes
|
||||||
|
major_r = 10
|
||||||
|
hole_locs = HexLocations(major_r, 25, 25)
|
||||||
|
|
||||||
|
# Create wires for both the perimeter and all the holes
|
||||||
|
face_perimeter = Rectangle(500, 600).wire()
|
||||||
|
hex_hole = RegularPolygon(major_r - 1, 6, major_radius=True).wire()
|
||||||
|
holes = hole_locs * hex_hole
|
||||||
|
|
||||||
|
# Create a new Face from the perimeter and hole wires
|
||||||
|
grid_pattern = Face(face_perimeter, holes)
|
||||||
|
|
||||||
|
# Extrude to a 3D part
|
||||||
|
grid = extrude(grid_pattern, 1)
|
||||||
|
|
||||||
|
print(f"Time: {timeit.default_timer() - start_time:0.3f}s")
|
||||||
|
show(grid)
|
||||||
|
# [End]
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Experimental Joint development file
|
Experimental Joint development file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from build123d import *
|
from build123d import *
|
||||||
from ocp_vscode import *
|
from ocp_vscode import *
|
||||||
|
|
||||||
|
|
@ -72,9 +73,9 @@ swing_arm_hinge_edge: Edge = (
|
||||||
.sort_by(Axis.X)[-2:]
|
.sort_by(Axis.X)[-2:]
|
||||||
.sort_by(Axis.Y)[0]
|
.sort_by(Axis.Y)[0]
|
||||||
)
|
)
|
||||||
swing_arm_hinge_axis = swing_arm_hinge_edge.to_axis()
|
swing_arm_hinge_axis = Axis(swing_arm_hinge_edge)
|
||||||
base_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1]
|
base_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1]
|
||||||
base_hinge_axis = base_corner_edge.to_axis()
|
base_hinge_axis = Axis(base_corner_edge)
|
||||||
j3 = RevoluteJoint("hinge", base, axis=base_hinge_axis, angular_range=(0, 180))
|
j3 = RevoluteJoint("hinge", base, axis=base_hinge_axis, angular_range=(0, 180))
|
||||||
j4 = RigidJoint("corner", hinge_arm, swing_arm_hinge_axis.location)
|
j4 = RigidJoint("corner", hinge_arm, swing_arm_hinge_axis.location)
|
||||||
base.joints["hinge"].connect_to(hinge_arm.joints["corner"], angle=90)
|
base.joints["hinge"].connect_to(hinge_arm.joints["corner"], angle=90)
|
||||||
|
|
@ -86,7 +87,7 @@ slider_arm = JointBox(4, 1, 2, 0.2)
|
||||||
s1 = LinearJoint(
|
s1 = LinearJoint(
|
||||||
"slide",
|
"slide",
|
||||||
base,
|
base,
|
||||||
axis=Edge.make_mid_way(*base_top_edges, 0.67).to_axis(),
|
axis=Axis(Edge.make_mid_way(*base_top_edges, 0.67)),
|
||||||
linear_range=(0, base_top_edges[0].length),
|
linear_range=(0, base_top_edges[0].length),
|
||||||
)
|
)
|
||||||
s2 = RigidJoint("slide", slider_arm, Location(Vector(0, 0, 0)))
|
s2 = RigidJoint("slide", slider_arm, Location(Vector(0, 0, 0)))
|
||||||
|
|
@ -111,7 +112,7 @@ j5.connect_to(j6, position=-1, angle=90)
|
||||||
j7 = LinearJoint(
|
j7 = LinearJoint(
|
||||||
"slot",
|
"slot",
|
||||||
base,
|
base,
|
||||||
axis=Edge.make_mid_way(*base_top_edges, 0.33).to_axis(),
|
axis=Axis(Edge.make_mid_way(*base_top_edges, 0.33)),
|
||||||
linear_range=(0, base_top_edges[0].length),
|
linear_range=(0, base_top_edges[0].length),
|
||||||
)
|
)
|
||||||
pin_arm = JointBox(2, 1, 2)
|
pin_arm = JointBox(2, 1, 2)
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,9 @@ swing_arm_hinge_edge = (
|
||||||
.sort_by(Axis.X)[-2:]
|
.sort_by(Axis.X)[-2:]
|
||||||
.sort_by(Axis.Y)[0]
|
.sort_by(Axis.Y)[0]
|
||||||
)
|
)
|
||||||
swing_arm_hinge_axis = swing_arm_hinge_edge.to_axis()
|
swing_arm_hinge_axis = Axis(swing_arm_hinge_edge)
|
||||||
base_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1]
|
base_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1]
|
||||||
base_hinge_axis = base_corner_edge.to_axis()
|
base_hinge_axis = Axis(base_corner_edge)
|
||||||
j3 = RevoluteJoint("hinge", base, axis=base_hinge_axis, angular_range=(0, 180))
|
j3 = RevoluteJoint("hinge", base, axis=base_hinge_axis, angular_range=(0, 180))
|
||||||
j4 = RigidJoint("corner", hinge_arm, swing_arm_hinge_axis.location)
|
j4 = RigidJoint("corner", hinge_arm, swing_arm_hinge_axis.location)
|
||||||
base.joints["hinge"].connect_to(hinge_arm.joints["corner"], angle=90)
|
base.joints["hinge"].connect_to(hinge_arm.joints["corner"], angle=90)
|
||||||
|
|
@ -77,7 +77,7 @@ slider_arm = JointBox(4, 1, 2, 0.2)
|
||||||
s1 = LinearJoint(
|
s1 = LinearJoint(
|
||||||
"slide",
|
"slide",
|
||||||
base,
|
base,
|
||||||
axis=Edge.make_mid_way(*base_top_edges, 0.67).to_axis(),
|
axis=Axis(Edge.make_mid_way(*base_top_edges, 0.67)),
|
||||||
linear_range=(0, base_top_edges[0].length),
|
linear_range=(0, base_top_edges[0].length),
|
||||||
)
|
)
|
||||||
s2 = RigidJoint("slide", slider_arm, Location(Vector(0, 0, 0)))
|
s2 = RigidJoint("slide", slider_arm, Location(Vector(0, 0, 0)))
|
||||||
|
|
@ -102,7 +102,7 @@ j5.connect_to(j6, position=-1, angle=90)
|
||||||
j7 = LinearJoint(
|
j7 = LinearJoint(
|
||||||
"slot",
|
"slot",
|
||||||
base,
|
base,
|
||||||
axis=Edge.make_mid_way(*base_top_edges, 0.33).to_axis(),
|
axis=Axis(Edge.make_mid_way(*base_top_edges, 0.33)),
|
||||||
linear_range=(0, base_top_edges[0].length),
|
linear_range=(0, base_top_edges[0].length),
|
||||||
)
|
)
|
||||||
pin_arm = JointBox(2, 1, 2)
|
pin_arm = JointBox(2, 1, 2)
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ with BuildPart() as lego:
|
||||||
exporter = ExportSVG(scale=6)
|
exporter = ExportSVG(scale=6)
|
||||||
exporter.add_shape(plan.sketch)
|
exporter.add_shape(plan.sketch)
|
||||||
exporter.write("assets/lego_step6.svg")
|
exporter.write("assets/lego_step6.svg")
|
||||||
# Substract a rectangle leaving ribs on the block walls
|
# Subtract a rectangle leaving ribs on the block walls
|
||||||
Rectangle(
|
Rectangle(
|
||||||
block_length - 2 * (wall_thickness + ridge_depth),
|
block_length - 2 * (wall_thickness + ridge_depth),
|
||||||
block_width - 2 * (wall_thickness + ridge_depth),
|
block_width - 2 * (wall_thickness + ridge_depth),
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ plan += locs * Rectangle(width=block_length, height=ridge_width)
|
||||||
locs = GridLocations(lego_unit_size, 0, pip_count, 1)
|
locs = GridLocations(lego_unit_size, 0, pip_count, 1)
|
||||||
plan += locs * Rectangle(width=ridge_width, height=block_width)
|
plan += locs * Rectangle(width=ridge_width, height=block_width)
|
||||||
|
|
||||||
# Substract a rectangle leaving ribs on the block walls
|
# Subtract a rectangle leaving ribs on the block walls
|
||||||
plan -= Rectangle(
|
plan -= Rectangle(
|
||||||
block_length - 2 * (wall_thickness + ridge_depth),
|
block_length - 2 * (wall_thickness + ridge_depth),
|
||||||
block_width - 2 * (wall_thickness + ridge_depth),
|
block_width - 2 * (wall_thickness + ridge_depth),
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,7 @@ __all__ = [
|
||||||
"LinearJoint",
|
"LinearJoint",
|
||||||
"CylindricalJoint",
|
"CylindricalJoint",
|
||||||
"BallJoint",
|
"BallJoint",
|
||||||
|
"DraftAngleError",
|
||||||
# Exporter classes
|
# Exporter classes
|
||||||
"Export2D",
|
"Export2D",
|
||||||
"ExportDXF",
|
"ExportDXF",
|
||||||
|
|
@ -197,6 +198,7 @@ __all__ = [
|
||||||
"add",
|
"add",
|
||||||
"bounding_box",
|
"bounding_box",
|
||||||
"chamfer",
|
"chamfer",
|
||||||
|
"draft",
|
||||||
"extrude",
|
"extrude",
|
||||||
"fillet",
|
"fillet",
|
||||||
"full_round",
|
"full_round",
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ import functools
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from math import sqrt, cos, pi
|
from math import sqrt, cos, pi
|
||||||
from typing import Any, cast, overload, Protocol, Type, TypeVar
|
from typing import Any, cast, overload, Protocol, Type, TypeVar, Generic
|
||||||
|
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
@ -155,6 +155,7 @@ operations_apply_to = {
|
||||||
"add": ["BuildPart", "BuildSketch", "BuildLine"],
|
"add": ["BuildPart", "BuildSketch", "BuildLine"],
|
||||||
"bounding_box": ["BuildPart", "BuildSketch", "BuildLine"],
|
"bounding_box": ["BuildPart", "BuildSketch", "BuildLine"],
|
||||||
"chamfer": ["BuildPart", "BuildSketch", "BuildLine"],
|
"chamfer": ["BuildPart", "BuildSketch", "BuildLine"],
|
||||||
|
"draft": ["BuildPart"],
|
||||||
"extrude": ["BuildPart"],
|
"extrude": ["BuildPart"],
|
||||||
"fillet": ["BuildPart", "BuildSketch", "BuildLine"],
|
"fillet": ["BuildPart", "BuildSketch", "BuildLine"],
|
||||||
"full_round": ["BuildSketch"],
|
"full_round": ["BuildSketch"],
|
||||||
|
|
@ -177,8 +178,11 @@ operations_apply_to = {
|
||||||
B = TypeVar("B", bound="Builder")
|
B = TypeVar("B", bound="Builder")
|
||||||
"""Builder type hint"""
|
"""Builder type hint"""
|
||||||
|
|
||||||
|
ShapeT = TypeVar("ShapeT", bound=Shape)
|
||||||
|
"""Builder's are generic shape creators"""
|
||||||
|
|
||||||
class Builder(ABC):
|
|
||||||
|
class Builder(ABC, Generic[ShapeT]):
|
||||||
"""Builder
|
"""Builder
|
||||||
|
|
||||||
Base class for the build123d Builders.
|
Base class for the build123d Builders.
|
||||||
|
|
@ -230,7 +234,7 @@ class Builder(ABC):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _obj(self) -> Shape:
|
def _obj(self) -> Shape | None:
|
||||||
"""Object to pass to parent"""
|
"""Object to pass to parent"""
|
||||||
raise NotImplementedError # pragma: no cover
|
raise NotImplementedError # pragma: no cover
|
||||||
|
|
||||||
|
|
@ -247,6 +251,8 @@ class Builder(ABC):
|
||||||
@property
|
@property
|
||||||
def new_edges(self) -> ShapeList[Edge]:
|
def new_edges(self) -> ShapeList[Edge]:
|
||||||
"""Edges that changed during last operation"""
|
"""Edges that changed during last operation"""
|
||||||
|
if self._obj is None:
|
||||||
|
return ShapeList()
|
||||||
before_list = [] if self.obj_before is None else [self.obj_before]
|
before_list = [] if self.obj_before is None else [self.obj_before]
|
||||||
return new_edges(*(before_list + self.to_combine), combined=self._obj)
|
return new_edges(*(before_list + self.to_combine), combined=self._obj)
|
||||||
|
|
||||||
|
|
@ -534,7 +540,8 @@ class Builder(ABC):
|
||||||
"""
|
"""
|
||||||
vertex_list: list[Vertex] = []
|
vertex_list: list[Vertex] = []
|
||||||
if select == Select.ALL:
|
if select == Select.ALL:
|
||||||
for obj_edge in self._obj.edges():
|
obj_edges = [] if self._obj is None else self._obj.edges()
|
||||||
|
for obj_edge in obj_edges:
|
||||||
vertex_list.extend(obj_edge.vertices())
|
vertex_list.extend(obj_edge.vertices())
|
||||||
elif select == Select.LAST:
|
elif select == Select.LAST:
|
||||||
vertex_list = self.lasts[Vertex]
|
vertex_list = self.lasts[Vertex]
|
||||||
|
|
@ -578,7 +585,7 @@ class Builder(ABC):
|
||||||
ShapeList[Edge]: Edges extracted
|
ShapeList[Edge]: Edges extracted
|
||||||
"""
|
"""
|
||||||
if select == Select.ALL:
|
if select == Select.ALL:
|
||||||
edge_list = self._obj.edges()
|
edge_list = ShapeList() if self._obj is None else self._obj.edges()
|
||||||
elif select == Select.LAST:
|
elif select == Select.LAST:
|
||||||
edge_list = self.lasts[Edge]
|
edge_list = self.lasts[Edge]
|
||||||
elif select == Select.NEW:
|
elif select == Select.NEW:
|
||||||
|
|
@ -621,7 +628,7 @@ class Builder(ABC):
|
||||||
ShapeList[Wire]: Wires extracted
|
ShapeList[Wire]: Wires extracted
|
||||||
"""
|
"""
|
||||||
if select == Select.ALL:
|
if select == Select.ALL:
|
||||||
wire_list = self._obj.wires()
|
wire_list = ShapeList() if self._obj is None else self._obj.wires()
|
||||||
elif select == Select.LAST:
|
elif select == Select.LAST:
|
||||||
wire_list = Wire.combine(self.lasts[Edge])
|
wire_list = Wire.combine(self.lasts[Edge])
|
||||||
elif select == Select.NEW:
|
elif select == Select.NEW:
|
||||||
|
|
@ -664,7 +671,7 @@ class Builder(ABC):
|
||||||
ShapeList[Face]: Faces extracted
|
ShapeList[Face]: Faces extracted
|
||||||
"""
|
"""
|
||||||
if select == Select.ALL:
|
if select == Select.ALL:
|
||||||
face_list = self._obj.faces()
|
face_list = ShapeList() if self._obj is None else self._obj.faces()
|
||||||
elif select == Select.LAST:
|
elif select == Select.LAST:
|
||||||
face_list = self.lasts[Face]
|
face_list = self.lasts[Face]
|
||||||
elif select == Select.NEW:
|
elif select == Select.NEW:
|
||||||
|
|
@ -707,7 +714,7 @@ class Builder(ABC):
|
||||||
ShapeList[Solid]: Solids extracted
|
ShapeList[Solid]: Solids extracted
|
||||||
"""
|
"""
|
||||||
if select == Select.ALL:
|
if select == Select.ALL:
|
||||||
solid_list = self._obj.solids()
|
solid_list = ShapeList() if self._obj is None else self._obj.solids()
|
||||||
elif select == Select.LAST:
|
elif select == Select.LAST:
|
||||||
solid_list = self.lasts[Solid]
|
solid_list = self.lasts[Solid]
|
||||||
elif select == Select.NEW:
|
elif select == Select.NEW:
|
||||||
|
|
@ -744,17 +751,18 @@ class Builder(ABC):
|
||||||
) -> ShapeList:
|
) -> ShapeList:
|
||||||
"""Extract Shapes"""
|
"""Extract Shapes"""
|
||||||
obj_type = self._shape if obj_type is None else obj_type
|
obj_type = self._shape if obj_type is None else obj_type
|
||||||
|
if self._obj is None:
|
||||||
|
return ShapeList()
|
||||||
|
|
||||||
if obj_type == Vertex:
|
if obj_type == Vertex:
|
||||||
result = self._obj.vertices()
|
return self._obj.vertices()
|
||||||
elif obj_type == Edge:
|
if obj_type == Edge:
|
||||||
result = self._obj.edges()
|
return self._obj.edges()
|
||||||
elif obj_type == Face:
|
if obj_type == Face:
|
||||||
result = self._obj.faces()
|
return self._obj.faces()
|
||||||
elif obj_type == Solid:
|
if obj_type == Solid:
|
||||||
result = self._obj.solids()
|
return self._obj.solids()
|
||||||
else:
|
return ShapeList()
|
||||||
result = None
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_inputs(
|
def validate_inputs(
|
||||||
self, validating_class, objects: Shape | Iterable[Shape] | None = None
|
self, validating_class, objects: Shape | Iterable[Shape] | None = None
|
||||||
|
|
@ -1110,7 +1118,7 @@ class Locations(LocationList):
|
||||||
elif isinstance(point, Vector):
|
elif isinstance(point, Vector):
|
||||||
local_locations.append(Location(point))
|
local_locations.append(Location(point))
|
||||||
elif isinstance(point, Vertex):
|
elif isinstance(point, Vertex):
|
||||||
local_locations.append(Location(Vector(point.to_tuple())))
|
local_locations.append(Location(Vector(point)))
|
||||||
elif isinstance(point, tuple):
|
elif isinstance(point, tuple):
|
||||||
local_locations.append(Location(Vector(point)))
|
local_locations.append(Location(Vector(point)))
|
||||||
elif isinstance(point, Plane):
|
elif isinstance(point, Plane):
|
||||||
|
|
@ -1377,8 +1385,8 @@ def __gen_context_component_getter(
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def getter(select: Select = Select.ALL) -> T2:
|
def getter(select: Select = Select.ALL) -> T2:
|
||||||
# Retrieve the current Builder context based on the method name
|
# Retrieve the current Builder context based on the method name
|
||||||
context = Builder._get_context(func.__name__)
|
context: Builder | None = Builder._get_context(func.__name__)
|
||||||
if not context:
|
if context is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{func.__name__}() requires a Builder context to be in scope"
|
f"{func.__name__}() requires a Builder context to be in scope"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ from build123d.geometry import Location, Plane
|
||||||
from build123d.topology import Curve, Edge, Face
|
from build123d.topology import Curve, Edge, Face
|
||||||
|
|
||||||
|
|
||||||
class BuildLine(Builder):
|
class BuildLine(Builder[Curve]):
|
||||||
"""BuildLine
|
"""BuildLine
|
||||||
|
|
||||||
The BuildLine class is a subclass of Builder for building lines (objects
|
The BuildLine class is a subclass of Builder for building lines (objects
|
||||||
|
|
@ -89,7 +89,15 @@ class BuildLine(Builder):
|
||||||
"""Set the current line"""
|
"""Set the current line"""
|
||||||
self._line = value
|
self._line = value
|
||||||
|
|
||||||
_obj = line # Alias _obj to line
|
@property
|
||||||
|
def _obj(self) -> Curve | None:
|
||||||
|
"""Alias _obj to line"""
|
||||||
|
return self._line
|
||||||
|
|
||||||
|
@_obj.setter
|
||||||
|
def _obj(self, value: Curve) -> None:
|
||||||
|
"""Set the current line"""
|
||||||
|
self._line = value
|
||||||
|
|
||||||
def __exit__(self, exception_type, exception_value, traceback):
|
def __exit__(self, exception_type, exception_value, traceback):
|
||||||
"""Upon exiting restore context and send object to parent"""
|
"""Upon exiting restore context and send object to parent"""
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ from build123d.geometry import Location, Plane
|
||||||
from build123d.topology import Edge, Face, Joint, Part, Solid, Wire
|
from build123d.topology import Edge, Face, Joint, Part, Solid, Wire
|
||||||
|
|
||||||
|
|
||||||
class BuildPart(Builder):
|
class BuildPart(Builder[Part]):
|
||||||
"""BuildPart
|
"""BuildPart
|
||||||
|
|
||||||
The BuildPart class is another subclass of Builder for building parts
|
The BuildPart class is another subclass of Builder for building parts
|
||||||
|
|
@ -80,7 +80,15 @@ class BuildPart(Builder):
|
||||||
"""Set the current part"""
|
"""Set the current part"""
|
||||||
self._part = value
|
self._part = value
|
||||||
|
|
||||||
_obj = part # Alias _obj to part
|
@property
|
||||||
|
def _obj(self) -> Part | None:
|
||||||
|
"""Alias _obj to part"""
|
||||||
|
return self._part
|
||||||
|
|
||||||
|
@_obj.setter
|
||||||
|
def _obj(self, value: Part) -> None:
|
||||||
|
"""Set the current part"""
|
||||||
|
self._part = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pending_edges_as_wire(self) -> Wire:
|
def pending_edges_as_wire(self) -> Wire:
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ from build123d.geometry import Location, Plane
|
||||||
from build123d.topology import Compound, Edge, Face, ShapeList, Sketch, Wire
|
from build123d.topology import Compound, Edge, Face, ShapeList, Sketch, Wire
|
||||||
|
|
||||||
|
|
||||||
class BuildSketch(Builder):
|
class BuildSketch(Builder[Sketch]):
|
||||||
"""BuildSketch
|
"""BuildSketch
|
||||||
|
|
||||||
The BuildSketch class is a subclass of Builder for building planar 2D
|
The BuildSketch class is a subclass of Builder for building planar 2D
|
||||||
|
|
@ -83,7 +83,15 @@ class BuildSketch(Builder):
|
||||||
"""Set the builder's object"""
|
"""Set the builder's object"""
|
||||||
self._sketch_local = value
|
self._sketch_local = value
|
||||||
|
|
||||||
_obj = sketch_local # Alias _obj to sketch_local
|
@property
|
||||||
|
def _obj(self) -> Sketch | None:
|
||||||
|
"""Alias _obj to sketch"""
|
||||||
|
return self._sketch_local
|
||||||
|
|
||||||
|
@_obj.setter
|
||||||
|
def _obj(self, value: Sketch) -> None:
|
||||||
|
"""Set the current sketch"""
|
||||||
|
self._sketch_local = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sketch(self):
|
def sketch(self):
|
||||||
|
|
|
||||||
|
|
@ -277,10 +277,7 @@ class Draft:
|
||||||
if isinstance(path, (Edge, Wire)):
|
if isinstance(path, (Edge, Wire)):
|
||||||
processed_path = path
|
processed_path = path
|
||||||
elif isinstance(path, Iterable):
|
elif isinstance(path, Iterable):
|
||||||
pnts = [
|
pnts = [Vector(p) for p in path]
|
||||||
Vector(p.to_tuple()) if isinstance(p, Vertex) else Vector(p)
|
|
||||||
for p in path
|
|
||||||
]
|
|
||||||
if len(pnts) == 2:
|
if len(pnts) == 2:
|
||||||
processed_path = Edge.make_line(*pnts)
|
processed_path = Edge.make_line(*pnts)
|
||||||
else:
|
else:
|
||||||
|
|
@ -458,7 +455,7 @@ class DimensionLine(BaseSketchObject):
|
||||||
else:
|
else:
|
||||||
self_intersection_area = self_intersection.area
|
self_intersection_area = self_intersection.area
|
||||||
d_line += placed_label
|
d_line += placed_label
|
||||||
bbox_size = d_line.bounding_box().size
|
bbox_size = d_line.bounding_box().diagonal
|
||||||
|
|
||||||
# Minimize size while avoiding intersections
|
# Minimize size while avoiding intersections
|
||||||
if sketch is None:
|
if sketch is None:
|
||||||
|
|
@ -472,7 +469,7 @@ class DimensionLine(BaseSketchObject):
|
||||||
else:
|
else:
|
||||||
common_area = line_intersection.area
|
common_area = line_intersection.area
|
||||||
common_area += self_intersection_area
|
common_area += self_intersection_area
|
||||||
score = (d_line.area - 10 * common_area) / bbox_size.X
|
score = (d_line.area - 10 * common_area) / bbox_size
|
||||||
d_lines[d_line] = score
|
d_lines[d_line] = score
|
||||||
|
|
||||||
# Sort by score to find the best option
|
# Sort by score to find the best option
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ license:
|
||||||
# pylint has trouble with the OCP imports
|
# pylint has trouble with the OCP imports
|
||||||
# pylint: disable=no-name-in-module, import-error
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
|
||||||
from io import BytesIO
|
from datetime import datetime
|
||||||
import warnings
|
import warnings
|
||||||
|
from io import BytesIO
|
||||||
from os import PathLike, fsdecode, fspath
|
from os import PathLike, fsdecode, fspath
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
import OCP.TopAbs as ta
|
import OCP.TopAbs as ta
|
||||||
from anytree import PreOrderIter
|
from anytree import PreOrderIter
|
||||||
|
|
@ -47,7 +47,11 @@ from OCP.RWGltf import RWGltf_CafWriter
|
||||||
from OCP.STEPCAFControl import STEPCAFControl_Controller, STEPCAFControl_Writer
|
from OCP.STEPCAFControl import STEPCAFControl_Controller, STEPCAFControl_Writer
|
||||||
from OCP.STEPControl import STEPControl_Controller, STEPControl_StepModelType
|
from OCP.STEPControl import STEPControl_Controller, STEPControl_StepModelType
|
||||||
from OCP.StlAPI import StlAPI_Writer
|
from OCP.StlAPI import StlAPI_Writer
|
||||||
from OCP.TCollection import TCollection_AsciiString, TCollection_ExtendedString, TCollection_HAsciiString
|
from OCP.TCollection import (
|
||||||
|
TCollection_AsciiString,
|
||||||
|
TCollection_ExtendedString,
|
||||||
|
TCollection_HAsciiString,
|
||||||
|
)
|
||||||
from OCP.TColStd import TColStd_IndexedDataMapOfStringString
|
from OCP.TColStd import TColStd_IndexedDataMapOfStringString
|
||||||
from OCP.TDataStd import TDataStd_Name
|
from OCP.TDataStd import TDataStd_Name
|
||||||
from OCP.TDF import TDF_Label
|
from OCP.TDF import TDF_Label
|
||||||
|
|
@ -262,6 +266,8 @@ def export_step(
|
||||||
unit: Unit = Unit.MM,
|
unit: Unit = Unit.MM,
|
||||||
write_pcurves: bool = True,
|
write_pcurves: bool = True,
|
||||||
precision_mode: PrecisionMode = PrecisionMode.AVERAGE,
|
precision_mode: PrecisionMode = PrecisionMode.AVERAGE,
|
||||||
|
*, # Too many positional arguments
|
||||||
|
timestamp: str | datetime | None = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""export_step
|
"""export_step
|
||||||
|
|
||||||
|
|
@ -302,6 +308,11 @@ def export_step(
|
||||||
header = APIHeaderSection_MakeHeader(writer.Writer().Model())
|
header = APIHeaderSection_MakeHeader(writer.Writer().Model())
|
||||||
if to_export.label:
|
if to_export.label:
|
||||||
header.SetName(TCollection_HAsciiString(to_export.label))
|
header.SetName(TCollection_HAsciiString(to_export.label))
|
||||||
|
if timestamp is not None:
|
||||||
|
if isinstance(timestamp, datetime):
|
||||||
|
header.SetTimeStamp(TCollection_HAsciiString(timestamp.isoformat()))
|
||||||
|
else:
|
||||||
|
header.SetTimeStamp(TCollection_HAsciiString(timestamp))
|
||||||
# consider using e.g. the non *Value versions instead
|
# consider using e.g. the non *Value versions instead
|
||||||
# header.SetAuthorValue(1, TCollection_HAsciiString("Volker"));
|
# header.SetAuthorValue(1, TCollection_HAsciiString("Volker"));
|
||||||
# header.SetOrganizationValue(1, TCollection_HAsciiString("myCompanyName"));
|
# header.SetOrganizationValue(1, TCollection_HAsciiString("myCompanyName"));
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -564,7 +564,7 @@ class FilletPolyline(BaseLineObject):
|
||||||
if len(edges) != 2:
|
if len(edges) != 2:
|
||||||
continue
|
continue
|
||||||
other_vertices = {ve for e in edges for ve in e.vertices() if ve != vertex}
|
other_vertices = {ve for e in edges for ve in e.vertices() if ve != vertex}
|
||||||
third_edge = Edge.make_line(*[v.to_tuple() for v in other_vertices])
|
third_edge = Edge.make_line(*[v for v in other_vertices])
|
||||||
fillet_face = Face(Wire(edges + [third_edge])).fillet_2d(radius, [vertex])
|
fillet_face = Face(Wire(edges + [third_edge])).fillet_2d(radius, [vertex])
|
||||||
fillets.append(fillet_face.edges().filter_by(GeomType.CIRCLE)[0])
|
fillets.append(fillet_face.edges().filter_by(GeomType.CIRCLE)[0])
|
||||||
|
|
||||||
|
|
@ -1095,9 +1095,7 @@ class PointArcTangentLine(BaseEdgeObject):
|
||||||
tangent_point = WorkplaneList.localize(point)
|
tangent_point = WorkplaneList.localize(point)
|
||||||
if context is None:
|
if context is None:
|
||||||
# Making the plane validates points and arc are coplanar
|
# Making the plane validates points and arc are coplanar
|
||||||
coplane = Edge.make_line(tangent_point, arc.arc_center).common_plane(
|
coplane = Edge.make_line(tangent_point, arc.arc_center).common_plane(arc)
|
||||||
arc
|
|
||||||
)
|
|
||||||
if coplane is None:
|
if coplane is None:
|
||||||
raise ValueError("PointArcTangentLine only works on a single plane.")
|
raise ValueError("PointArcTangentLine only works on a single plane.")
|
||||||
|
|
||||||
|
|
@ -1478,4 +1476,10 @@ class ArcArcTangentArc(BaseEdgeObject):
|
||||||
intersect.reverse()
|
intersect.reverse()
|
||||||
|
|
||||||
arc = RadiusArc(intersect[0], intersect[1], radius=radius)
|
arc = RadiusArc(intersect[0], intersect[1], radius=radius)
|
||||||
|
|
||||||
|
# Check and flip arc if not tangent
|
||||||
|
_, _, point = start_arc.distance_to_with_closest_points(arc)
|
||||||
|
if start_arc.tangent_at(point).cross(arc.tangent_at(point)).length > TOLERANCE:
|
||||||
|
arc = RadiusArc(intersect[0], intersect[1], radius=-radius)
|
||||||
|
|
||||||
super().__init__(arc, mode)
|
super().__init__(arc, mode)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ from build123d.topology import (
|
||||||
Face,
|
Face,
|
||||||
ShapeList,
|
ShapeList,
|
||||||
Sketch,
|
Sketch,
|
||||||
|
Vertex,
|
||||||
Wire,
|
Wire,
|
||||||
tuplify,
|
tuplify,
|
||||||
topo_explore_common_vertex,
|
topo_explore_common_vertex,
|
||||||
|
|
@ -205,7 +206,7 @@ class Polygon(BaseSketchObject):
|
||||||
self.pts = flattened_pts
|
self.pts = flattened_pts
|
||||||
self.align = tuplify(align, 2)
|
self.align = tuplify(align, 2)
|
||||||
|
|
||||||
poly_pts = [Vector(p) for p in pts]
|
poly_pts = [Vector(p) for p in self.pts]
|
||||||
face = Face(Wire.make_polygon(poly_pts))
|
face = Face(Wire.make_polygon(poly_pts))
|
||||||
super().__init__(face, rotation, self.align, mode)
|
super().__init__(face, rotation, self.align, mode)
|
||||||
|
|
||||||
|
|
@ -386,7 +387,7 @@ class SlotArc(BaseSketchObject):
|
||||||
self.slot_height = height
|
self.slot_height = height
|
||||||
|
|
||||||
arc = arc if isinstance(arc, Wire) else Wire([arc])
|
arc = arc if isinstance(arc, Wire) else Wire([arc])
|
||||||
face = Face(arc.offset_2d(height / 2)).rotate(Axis.Z, rotation)
|
face = Face(arc.offset_2d(height / 2))
|
||||||
super().__init__(face, rotation, None, mode)
|
super().__init__(face, rotation, None, mode)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -425,10 +426,10 @@ class SlotCenterPoint(BaseSketchObject):
|
||||||
|
|
||||||
half_line = point_v - center_v
|
half_line = point_v - center_v
|
||||||
|
|
||||||
if half_line.length * 2 <= height:
|
if half_line.length <= 0:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Slots must have width > height. "
|
"Distance between center and point must be greater than 0 "
|
||||||
"Got: {height=} width={half_line.length * 2} (computed)"
|
f"Got: distance = {half_line.length} (computed)"
|
||||||
)
|
)
|
||||||
|
|
||||||
face = Face(
|
face = Face(
|
||||||
|
|
@ -463,7 +464,7 @@ class SlotCenterToCenter(BaseSketchObject):
|
||||||
rotation: float = 0,
|
rotation: float = 0,
|
||||||
mode: Mode = Mode.ADD,
|
mode: Mode = Mode.ADD,
|
||||||
):
|
):
|
||||||
if center_separation <= 0:
|
if center_separation < 0:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Requires center_separation > 0. Got: {center_separation=}"
|
f"Requires center_separation > 0. Got: {center_separation=}"
|
||||||
)
|
)
|
||||||
|
|
@ -474,6 +475,7 @@ class SlotCenterToCenter(BaseSketchObject):
|
||||||
self.center_separation = center_separation
|
self.center_separation = center_separation
|
||||||
self.slot_height = height
|
self.slot_height = height
|
||||||
|
|
||||||
|
if center_separation > 0:
|
||||||
face = Face(
|
face = Face(
|
||||||
Wire(
|
Wire(
|
||||||
[
|
[
|
||||||
|
|
@ -482,6 +484,9 @@ class SlotCenterToCenter(BaseSketchObject):
|
||||||
]
|
]
|
||||||
).offset_2d(height / 2)
|
).offset_2d(height / 2)
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
face = cast(Face, Circle(height / 2, mode=mode).face())
|
||||||
|
|
||||||
super().__init__(face, rotation, None, mode)
|
super().__init__(face, rotation, None, mode)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -509,7 +514,7 @@ class SlotOverall(BaseSketchObject):
|
||||||
align: Align | tuple[Align, Align] | None = (Align.CENTER, Align.CENTER),
|
align: Align | tuple[Align, Align] | None = (Align.CENTER, Align.CENTER),
|
||||||
mode: Mode = Mode.ADD,
|
mode: Mode = Mode.ADD,
|
||||||
):
|
):
|
||||||
if width <= height:
|
if width < height:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Slot requires that width > height. Got: {width=}, {height=}"
|
f"Slot requires that width > height. Got: {width=}, {height=}"
|
||||||
)
|
)
|
||||||
|
|
@ -520,7 +525,7 @@ class SlotOverall(BaseSketchObject):
|
||||||
self.width = width
|
self.width = width
|
||||||
self.slot_height = height
|
self.slot_height = height
|
||||||
|
|
||||||
if width != height:
|
if width > height:
|
||||||
face = Face(
|
face = Face(
|
||||||
Wire(
|
Wire(
|
||||||
[
|
[
|
||||||
|
|
@ -531,6 +536,7 @@ class SlotOverall(BaseSketchObject):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
face = cast(Face, Circle(width / 2, mode=mode).face())
|
face = cast(Face, Circle(width / 2, mode=mode).face())
|
||||||
|
|
||||||
super().__init__(face, rotation, align, mode)
|
super().__init__(face, rotation, align, mode)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -782,9 +788,15 @@ class Triangle(BaseSketchObject):
|
||||||
self.vertex_A = topo_explore_common_vertex(
|
self.vertex_A = topo_explore_common_vertex(
|
||||||
self.edge_b, self.edge_c
|
self.edge_b, self.edge_c
|
||||||
) #: vertex 'A'
|
) #: vertex 'A'
|
||||||
|
assert isinstance(self.vertex_A, Vertex)
|
||||||
|
self.vertex_A.topo_parent = self
|
||||||
self.vertex_B = topo_explore_common_vertex(
|
self.vertex_B = topo_explore_common_vertex(
|
||||||
self.edge_a, self.edge_c
|
self.edge_a, self.edge_c
|
||||||
) #: vertex 'B'
|
) #: vertex 'B'
|
||||||
|
assert isinstance(self.vertex_B, Vertex)
|
||||||
|
self.vertex_B.topo_parent = self
|
||||||
self.vertex_C = topo_explore_common_vertex(
|
self.vertex_C = topo_explore_common_vertex(
|
||||||
self.edge_a, self.edge_b
|
self.edge_a, self.edge_b
|
||||||
) #: vertex 'C'
|
) #: vertex 'C'
|
||||||
|
assert isinstance(self.vertex_C, Vertex)
|
||||||
|
self.vertex_C.topo_parent = self
|
||||||
|
|
|
||||||
|
|
@ -119,11 +119,11 @@ def add(
|
||||||
(
|
(
|
||||||
obj.unwrap(fully=False)
|
obj.unwrap(fully=False)
|
||||||
if isinstance(obj, Compound)
|
if isinstance(obj, Compound)
|
||||||
else obj._obj if isinstance(obj, Builder) else obj
|
else obj._obj if isinstance(obj, Builder) and obj._obj is not None else obj
|
||||||
)
|
)
|
||||||
for obj in object_list
|
for obj in object_list
|
||||||
|
if not (isinstance(obj, Builder) and obj._obj is None)
|
||||||
]
|
]
|
||||||
|
|
||||||
validate_inputs(context, "add", object_iter)
|
validate_inputs(context, "add", object_iter)
|
||||||
|
|
||||||
if isinstance(context, BuildPart):
|
if isinstance(context, BuildPart):
|
||||||
|
|
@ -364,11 +364,14 @@ def chamfer(
|
||||||
return new_sketch
|
return new_sketch
|
||||||
|
|
||||||
if target._dim == 1:
|
if target._dim == 1:
|
||||||
target = (
|
if isinstance(target, BaseLineObject):
|
||||||
Wire(target.wrapped)
|
if target.wrapped is None:
|
||||||
if isinstance(target, BaseLineObject)
|
target = Wire([]) # empty wire
|
||||||
else target.wires()[0]
|
else:
|
||||||
)
|
target = Wire(target.wrapped)
|
||||||
|
else:
|
||||||
|
target = target.wires()[0]
|
||||||
|
|
||||||
if not all([isinstance(obj, Vertex) for obj in object_list]):
|
if not all([isinstance(obj, Vertex) for obj in object_list]):
|
||||||
raise ValueError("1D fillet operation takes only Vertices")
|
raise ValueError("1D fillet operation takes only Vertices")
|
||||||
# Remove any end vertices as these can't be filleted
|
# Remove any end vertices as these can't be filleted
|
||||||
|
|
@ -376,14 +379,8 @@ def chamfer(
|
||||||
object_list = ShapeList(
|
object_list = ShapeList(
|
||||||
filter(
|
filter(
|
||||||
lambda v: not (
|
lambda v: not (
|
||||||
isclose_b(
|
isclose_b((Vector(v) - target.position_at(0)).length, 0.0)
|
||||||
(Vector(*v.to_tuple()) - target.position_at(0)).length,
|
or isclose_b((Vector(v) - target.position_at(1)).length, 0.0)
|
||||||
0.0,
|
|
||||||
)
|
|
||||||
or isclose_b(
|
|
||||||
(Vector(*v.to_tuple()) - target.position_at(1)).length,
|
|
||||||
0.0,
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
object_list,
|
object_list,
|
||||||
)
|
)
|
||||||
|
|
@ -467,11 +464,14 @@ def fillet(
|
||||||
return new_sketch
|
return new_sketch
|
||||||
|
|
||||||
if target._dim == 1:
|
if target._dim == 1:
|
||||||
target = (
|
if isinstance(target, BaseLineObject):
|
||||||
Wire(target.wrapped)
|
if target.wrapped is None:
|
||||||
if isinstance(target, BaseLineObject)
|
target = Wire([]) # empty wire
|
||||||
else target.wires()[0]
|
else:
|
||||||
)
|
target = Wire(target.wrapped)
|
||||||
|
else:
|
||||||
|
target = target.wires()[0]
|
||||||
|
|
||||||
if not all([isinstance(obj, Vertex) for obj in object_list]):
|
if not all([isinstance(obj, Vertex) for obj in object_list]):
|
||||||
raise ValueError("1D fillet operation takes only Vertices")
|
raise ValueError("1D fillet operation takes only Vertices")
|
||||||
# Remove any end vertices as these can't be filleted
|
# Remove any end vertices as these can't be filleted
|
||||||
|
|
@ -479,14 +479,8 @@ def fillet(
|
||||||
object_list = ShapeList(
|
object_list = ShapeList(
|
||||||
filter(
|
filter(
|
||||||
lambda v: not (
|
lambda v: not (
|
||||||
isclose_b(
|
isclose_b((Vector(v) - target.position_at(0)).length, 0.0)
|
||||||
(Vector(*v.to_tuple()) - target.position_at(0)).length,
|
or isclose_b((Vector(v) - target.position_at(1)).length, 0.0)
|
||||||
0.0,
|
|
||||||
)
|
|
||||||
or isclose_b(
|
|
||||||
(Vector(*v.to_tuple()) - target.position_at(1)).length,
|
|
||||||
0.0,
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
object_list,
|
object_list,
|
||||||
)
|
)
|
||||||
|
|
@ -758,9 +752,7 @@ def project(
|
||||||
|
|
||||||
# The size of the object determines the size of the target projection screen
|
# The size of the object determines the size of the target projection screen
|
||||||
# as the screen is normal to the direction of parallel projection
|
# as the screen is normal to the direction of parallel projection
|
||||||
shape_list = [
|
shape_list = [Vertex(o) if isinstance(o, Vector) else o for o in object_list]
|
||||||
Vertex(*o.to_tuple()) if isinstance(o, Vector) else o for o in object_list
|
|
||||||
]
|
|
||||||
object_size = Compound(children=shape_list).bounding_box(optimal=False).diagonal
|
object_size = Compound(children=shape_list).bounding_box(optimal=False).diagonal
|
||||||
|
|
||||||
vct_vrt_list = [o for o in object_list if isinstance(o, (Vector, Vertex))]
|
vct_vrt_list = [o for o in object_list if isinstance(o, (Vector, Vertex))]
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,13 @@ from __future__ import annotations
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from build123d.build_enums import Mode, Until, Kind, Side
|
from build123d.build_enums import GeomType, Mode, Until, Kind, Side
|
||||||
from build123d.build_part import BuildPart
|
from build123d.build_part import BuildPart
|
||||||
from build123d.geometry import Axis, Plane, Vector, VectorLike
|
from build123d.geometry import Axis, Plane, Vector, VectorLike
|
||||||
from build123d.topology import (
|
from build123d.topology import (
|
||||||
Compound,
|
Compound,
|
||||||
Curve,
|
Curve,
|
||||||
|
DraftAngleError,
|
||||||
Edge,
|
Edge,
|
||||||
Face,
|
Face,
|
||||||
Shell,
|
Shell,
|
||||||
|
|
@ -55,6 +56,59 @@ from build123d.build_common import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def draft(
|
||||||
|
faces: Face | Iterable[Face],
|
||||||
|
neutral_plane: Plane,
|
||||||
|
angle: float,
|
||||||
|
) -> Part:
|
||||||
|
"""Part Operation: draft
|
||||||
|
|
||||||
|
Apply a draft angle to the given faces of the part
|
||||||
|
|
||||||
|
Args:
|
||||||
|
faces: Faces to which the draft should be applied.
|
||||||
|
neutral_plane: Plane defining the neutral direction and position.
|
||||||
|
angle: Draft angle in degrees.
|
||||||
|
"""
|
||||||
|
context: BuildPart | None = BuildPart._get_context("draft")
|
||||||
|
|
||||||
|
face_list: ShapeList[Face] = flatten_sequence(faces)
|
||||||
|
assert all(isinstance(f, Face) for f in face_list), "all faces must be of type Face"
|
||||||
|
validate_inputs(context, "draft", face_list)
|
||||||
|
|
||||||
|
valid_geom_types = {GeomType.PLANE, GeomType.CYLINDER, GeomType.CONE}
|
||||||
|
unsupported = [f for f in face_list if f.geom_type not in valid_geom_types]
|
||||||
|
if unsupported:
|
||||||
|
raise ValueError(
|
||||||
|
f"Draft not supported on face(s) with geometry: "
|
||||||
|
f"{', '.join(set(f.geom_type.name for f in unsupported))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that all the faces are associated with the same Solid
|
||||||
|
topo_parents = set(f.topo_parent for f in face_list if f.topo_parent is not None)
|
||||||
|
if len(topo_parents) != 1:
|
||||||
|
raise ValueError("All faces must share the same topological parent (a Solid)")
|
||||||
|
parent_solids = next(iter(topo_parents)).solids()
|
||||||
|
if len(parent_solids) != 1:
|
||||||
|
raise ValueError("Topological parent must be a single Solid")
|
||||||
|
|
||||||
|
# Create the drafted solid
|
||||||
|
try:
|
||||||
|
new_solid = parent_solids[0].draft(face_list, neutral_plane, angle)
|
||||||
|
except DraftAngleError as err:
|
||||||
|
raise DraftAngleError(
|
||||||
|
f"Draft operation failed. "
|
||||||
|
f"Use `err.face` and `err.problematic_shape` for more information.",
|
||||||
|
face=err.face,
|
||||||
|
problematic_shape=err.problematic_shape,
|
||||||
|
) from err
|
||||||
|
|
||||||
|
if context is not None:
|
||||||
|
context._add_to_context(new_solid, clean=False, mode=Mode.REPLACE)
|
||||||
|
|
||||||
|
return Part(Compound([new_solid]).wrapped)
|
||||||
|
|
||||||
|
|
||||||
def extrude(
|
def extrude(
|
||||||
to_extrude: Face | Sketch | None = None,
|
to_extrude: Face | Sketch | None = None,
|
||||||
amount: float | None = None,
|
amount: float | None = None,
|
||||||
|
|
@ -250,11 +304,11 @@ def loft(
|
||||||
new_solid = Solid.make_loft(loft_wires, ruled)
|
new_solid = Solid.make_loft(loft_wires, ruled)
|
||||||
|
|
||||||
# Try to recover an invalid loft
|
# Try to recover an invalid loft
|
||||||
if not new_solid.is_valid():
|
if not new_solid.is_valid:
|
||||||
new_solid = Solid(Shell(new_solid.faces() + section_list))
|
new_solid = Solid(Shell(new_solid.faces() + section_list))
|
||||||
if clean:
|
if clean:
|
||||||
new_solid = new_solid.clean()
|
new_solid = new_solid.clean()
|
||||||
if not new_solid.is_valid():
|
if not new_solid.is_valid:
|
||||||
raise RuntimeError("Failed to create valid loft")
|
raise RuntimeError("Failed to create valid loft")
|
||||||
|
|
||||||
if context is not None:
|
if context is not None:
|
||||||
|
|
@ -338,12 +392,10 @@ def make_brake_formed(
|
||||||
raise TypeError("station_widths must be either a single number or an iterable")
|
raise TypeError("station_widths must be either a single number or an iterable")
|
||||||
|
|
||||||
for vertex in line_vertices:
|
for vertex in line_vertices:
|
||||||
others = offset_vertices.sort_by_distance(Vector(vertex.X, vertex.Y, vertex.Z))
|
others = offset_vertices.sort_by_distance(Vector(vertex))
|
||||||
for other in others[1:]:
|
for other in others[1:]:
|
||||||
if abs(Vector(*(vertex - other).to_tuple()).length - thickness) < 1e-2:
|
if abs(Vector((vertex - other)).length - thickness) < 1e-2:
|
||||||
station_edges.append(
|
station_edges.append(Edge.make_line(vertex, other))
|
||||||
Edge.make_line(vertex.to_tuple(), other.to_tuple())
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
station_edges = station_edges.sort_by(line)
|
station_edges = station_edges.sort_by(line)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,14 @@ from __future__ import annotations
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from scipy.spatial import Voronoi
|
from scipy.spatial import Voronoi
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from build123d.build_enums import Mode, SortBy
|
from build123d.build_enums import Mode, SortBy, Transition
|
||||||
from build123d.topology import (
|
from build123d.topology import (
|
||||||
Compound,
|
Compound,
|
||||||
Curve,
|
Curve,
|
||||||
Edge,
|
Edge,
|
||||||
Face,
|
Face,
|
||||||
ShapeList,
|
ShapeList,
|
||||||
|
Shell,
|
||||||
Wire,
|
Wire,
|
||||||
Sketch,
|
Sketch,
|
||||||
topo_explore_connected_edges,
|
topo_explore_connected_edges,
|
||||||
|
|
@ -298,10 +299,15 @@ def trace(
|
||||||
else:
|
else:
|
||||||
raise ValueError("No objects to trace")
|
raise ValueError("No objects to trace")
|
||||||
|
|
||||||
|
# Group the edges into wires to allow for nice transitions
|
||||||
|
trace_wires = Wire.combine(trace_edges)
|
||||||
|
|
||||||
new_faces: list[Face] = []
|
new_faces: list[Face] = []
|
||||||
for edge in trace_edges:
|
for to_trace in trace_wires:
|
||||||
trace_pen = edge.perpendicular_line(line_width, 0)
|
trace_pen = to_trace.perpendicular_line(line_width, 0)
|
||||||
new_faces.extend(Face.sweep(trace_pen, edge).faces())
|
new_faces.extend(
|
||||||
|
Shell.sweep(trace_pen, to_trace, transition=Transition.RIGHT).faces()
|
||||||
|
)
|
||||||
if context is not None:
|
if context is not None:
|
||||||
context._add_to_context(*new_faces, mode=mode)
|
context._add_to_context(*new_faces, mode=mode)
|
||||||
context.pending_edges = ShapeList()
|
context.pending_edges = ShapeList()
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,13 @@ from .one_d import (
|
||||||
topo_explore_connected_faces,
|
topo_explore_connected_faces,
|
||||||
)
|
)
|
||||||
from .two_d import Face, Shell, Mixin2D, sort_wires_by_build_order
|
from .two_d import Face, Shell, Mixin2D, sort_wires_by_build_order
|
||||||
from .three_d import Solid, Mixin3D
|
from .three_d import Solid, Mixin3D, DraftAngleError
|
||||||
from .composite import Compound, Curve, Sketch, Part
|
from .composite import Compound, Curve, Sketch, Part
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Shape",
|
"Shape",
|
||||||
"Comparable",
|
"Comparable",
|
||||||
|
"DraftAngleError",
|
||||||
"ShapePredicate",
|
"ShapePredicate",
|
||||||
"GroupBy",
|
"GroupBy",
|
||||||
"ShapeList",
|
"ShapeList",
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ from math import radians, inf, pi, cos, copysign, ceil, floor
|
||||||
from typing import Literal, overload, TYPE_CHECKING
|
from typing import Literal, overload, TYPE_CHECKING
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
from numpy import ndarray
|
from numpy import ndarray
|
||||||
from scipy.optimize import minimize
|
from scipy.optimize import minimize, minimize_scalar
|
||||||
from scipy.spatial import ConvexHull
|
from scipy.spatial import ConvexHull
|
||||||
|
|
||||||
import OCP.TopAbs as ta
|
import OCP.TopAbs as ta
|
||||||
|
|
@ -176,6 +176,7 @@ from build123d.build_enums import (
|
||||||
from build123d.geometry import (
|
from build123d.geometry import (
|
||||||
DEG2RAD,
|
DEG2RAD,
|
||||||
TOLERANCE,
|
TOLERANCE,
|
||||||
|
TOL_DIGITS,
|
||||||
Axis,
|
Axis,
|
||||||
Color,
|
Color,
|
||||||
Location,
|
Location,
|
||||||
|
|
@ -436,7 +437,7 @@ class Mixin1D(Shape):
|
||||||
if all(a0.is_coaxial(a1) for a0, a1 in combinations(as_axis, 2)):
|
if all(a0.is_coaxial(a1) for a0, a1 in combinations(as_axis, 2)):
|
||||||
origin = as_axis[0].position
|
origin = as_axis[0].position
|
||||||
x_dir = as_axis[0].direction
|
x_dir = as_axis[0].direction
|
||||||
z_dir = as_axis[0].to_plane().x_dir
|
z_dir = Plane(as_axis[0]).x_dir
|
||||||
c_plane = Plane(origin, z_dir=z_dir)
|
c_plane = Plane(origin, z_dir=z_dir)
|
||||||
result = c_plane.shift_origin((0, 0))
|
result = c_plane.shift_origin((0, 0))
|
||||||
|
|
||||||
|
|
@ -492,7 +493,11 @@ class Mixin1D(Shape):
|
||||||
|
|
||||||
edge_list: ShapeList[Edge] = ShapeList()
|
edge_list: ShapeList[Edge] = ShapeList()
|
||||||
while explorer.More():
|
while explorer.More():
|
||||||
edge_list.append(Edge(explorer.Current()))
|
next_edge = Edge(explorer.Current())
|
||||||
|
next_edge.topo_parent = (
|
||||||
|
self if self.topo_parent is None else self.topo_parent
|
||||||
|
)
|
||||||
|
edge_list.append(next_edge)
|
||||||
explorer.Next()
|
explorer.Next()
|
||||||
return edge_list
|
return edge_list
|
||||||
else:
|
else:
|
||||||
|
|
@ -1563,7 +1568,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
Returns:
|
Returns:
|
||||||
Edge: linear Edge between two Edges
|
Edge: linear Edge between two Edges
|
||||||
"""
|
"""
|
||||||
flip = first.to_axis().is_opposite(second.to_axis())
|
flip = Axis(first).is_opposite(Axis(second))
|
||||||
pnts = [
|
pnts = [
|
||||||
Edge.make_line(
|
Edge.make_line(
|
||||||
first.position_at(i), second.position_at(1 - i if flip else i)
|
first.position_at(i), second.position_at(1 - i if flip else i)
|
||||||
|
|
@ -2078,14 +2083,25 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def param_at_point(self, point: VectorLike) -> float:
|
def param_at_point(self, point: VectorLike) -> float:
|
||||||
"""Normalized parameter at point along Edge"""
|
"""param_at_point
|
||||||
|
|
||||||
|
Args:
|
||||||
|
point (VectorLike): point on Edge
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: point not on edge
|
||||||
|
RuntimeError: failed to find parameter
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: parameter value at point on edge
|
||||||
|
"""
|
||||||
|
|
||||||
# Note that this search algorithm would ideally be replaced with
|
# Note that this search algorithm would ideally be replaced with
|
||||||
# an OCP based solution, something like that which is shown below.
|
# an OCP based solution, something like that which is shown below.
|
||||||
# However, there are known issues with the OCP methods for some
|
# However, there are known issues with the OCP methods for some
|
||||||
# curves which may return negative values or incorrect values at
|
# curves which may return negative values or incorrect values at
|
||||||
# end points. Also note that this search takes about 1.5ms while
|
# end points. Also note that this search takes about 1.3ms on a
|
||||||
# the OCP methods take about 0.4ms.
|
# complex curve while the OCP methods take about 0.4ms.
|
||||||
#
|
#
|
||||||
# curve = BRep_Tool.Curve_s(self.wrapped, float(), float())
|
# curve = BRep_Tool.Curve_s(self.wrapped, float(), float())
|
||||||
# param_min, param_max = BRep_Tool.Range_s(self.wrapped)
|
# param_min, param_max = BRep_Tool.Range_s(self.wrapped)
|
||||||
|
|
@ -2095,26 +2111,47 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
|
|
||||||
point = Vector(point)
|
point = Vector(point)
|
||||||
|
|
||||||
if not isclose_b(self.distance_to(point), 0, abs_tol=TOLERANCE):
|
separation = self.distance_to(point)
|
||||||
raise ValueError(f"point ({point}) is not on edge")
|
if not isclose_b(separation, 0, abs_tol=TOLERANCE):
|
||||||
|
raise ValueError(f"point ({point}) is {separation} from edge")
|
||||||
|
|
||||||
# Function to be minimized
|
# This algorithm finds the normalized [0, 1] parameter of a point on an edge
|
||||||
def func(param: ndarray) -> float:
|
# by minimizing the 3D distance between the edge and the given point.
|
||||||
return (self.position_at(param[0]) - point).length
|
#
|
||||||
|
# Because some edges (e.g., BSplines) can have multiple local minima in the
|
||||||
|
# distance function, we subdivide the [0, 1] domain into 2^n intervals
|
||||||
|
# (logarithmic refinement) and perform a bounded minimization in each subinterval.
|
||||||
|
#
|
||||||
|
# The first solution found with an error smaller than the geometric resolution
|
||||||
|
# is returned. If no such minimum is found after all subdivisions, a runtime error
|
||||||
|
# is raised.
|
||||||
|
|
||||||
# Find the u value that results in a point within tolerance of the target
|
max_divisions = 10 # Logarithmic refinement depth
|
||||||
initial_guess = max(
|
|
||||||
0.0, min(1.0, (point - self.position_at(0)).length / self.length)
|
for division in range(max_divisions):
|
||||||
|
intervals = 2**division
|
||||||
|
step = 1.0 / intervals
|
||||||
|
|
||||||
|
for i in range(intervals):
|
||||||
|
lo, hi = i * step, (i + 1) * step
|
||||||
|
|
||||||
|
result = minimize_scalar(
|
||||||
|
lambda u: (self.position_at(u) - point).length,
|
||||||
|
bounds=(lo, hi),
|
||||||
|
method="bounded",
|
||||||
|
options={"xatol": TOLERANCE / 2},
|
||||||
)
|
)
|
||||||
result = minimize(
|
|
||||||
func,
|
# Early exit if we're below resolution limit
|
||||||
x0=initial_guess,
|
if (
|
||||||
method="Nelder-Mead",
|
result.fun
|
||||||
bounds=[(0.0, 1.0)],
|
< (
|
||||||
tol=TOLERANCE,
|
self @ (result.x + TOLERANCE) - self @ (result.x - TOLERANCE)
|
||||||
)
|
).length
|
||||||
u_value = float(result.x[0])
|
):
|
||||||
return u_value
|
return round(float(result.x), TOL_DIGITS)
|
||||||
|
|
||||||
|
raise RuntimeError("Unable to find parameter, Edge is too complex")
|
||||||
|
|
||||||
def project_to_shape(
|
def project_to_shape(
|
||||||
self,
|
self,
|
||||||
|
|
@ -2184,6 +2221,12 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
|
|
||||||
def to_axis(self) -> Axis:
|
def to_axis(self) -> Axis:
|
||||||
"""Translate a linear Edge to an Axis"""
|
"""Translate a linear Edge to an Axis"""
|
||||||
|
warnings.warn(
|
||||||
|
"to_axis is deprecated and will be removed in a future version. "
|
||||||
|
"Use 'Axis(Edge)' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
if self.geom_type != GeomType.LINE:
|
if self.geom_type != GeomType.LINE:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"to_axis is only valid for linear Edges not {self.geom_type}"
|
f"to_axis is only valid for linear Edges not {self.geom_type}"
|
||||||
|
|
@ -2192,6 +2235,12 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
|
|
||||||
def to_wire(self) -> Wire:
|
def to_wire(self) -> Wire:
|
||||||
"""Edge as Wire"""
|
"""Edge as Wire"""
|
||||||
|
warnings.warn(
|
||||||
|
"to_wire is deprecated and will be removed in a future version. "
|
||||||
|
"Use 'Wire(Edge)' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
return Wire([self])
|
return Wire([self])
|
||||||
|
|
||||||
def trim(self, start: float, end: float) -> Edge:
|
def trim(self, start: float, end: float) -> Edge:
|
||||||
|
|
@ -2596,7 +2645,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
for edge_index, edge in enumerate(edges):
|
for edge_index, edge in enumerate(edges):
|
||||||
for i in range(fragments_per_edge):
|
for i in range(fragments_per_edge):
|
||||||
param = i / (fragments_per_edge - 1)
|
param = i / (fragments_per_edge - 1)
|
||||||
points.append(edge.position_at(param).to_tuple()[:2])
|
points.append(tuple(edge.position_at(param))[:2])
|
||||||
points_lookup[edge_index * fragments_per_edge + i] = (edge_index, param)
|
points_lookup[edge_index * fragments_per_edge + i] = (edge_index, param)
|
||||||
|
|
||||||
convex_hull = ConvexHull(points)
|
convex_hull = ConvexHull(points)
|
||||||
|
|
@ -2990,13 +3039,13 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
projection_object = BRepProj_Projection(
|
projection_object = BRepProj_Projection(
|
||||||
self.wrapped,
|
self.wrapped,
|
||||||
target_object.wrapped,
|
target_object.wrapped,
|
||||||
gp_Dir(*direction_vector.to_tuple()),
|
gp_Dir(*direction_vector),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
projection_object = BRepProj_Projection(
|
projection_object = BRepProj_Projection(
|
||||||
self.wrapped,
|
self.wrapped,
|
||||||
target_object.wrapped,
|
target_object.wrapped,
|
||||||
gp_Pnt(*center_point.to_tuple()),
|
gp_Pnt(*center_point),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate a list of the projected wires with aligned orientation
|
# Generate a list of the projected wires with aligned orientation
|
||||||
|
|
@ -3067,91 +3116,62 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
|
|
||||||
def to_wire(self) -> Wire:
|
def to_wire(self) -> Wire:
|
||||||
"""Return Wire - used as a pair with Edge.to_wire when self is Wire | Edge"""
|
"""Return Wire - used as a pair with Edge.to_wire when self is Wire | Edge"""
|
||||||
|
warnings.warn(
|
||||||
|
"to_wire is deprecated and will be removed in a future version. "
|
||||||
|
"Use 'Wire(Wire)' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def trim(self: Wire, start: float, end: float) -> Wire:
|
def trim(self: Wire, start: float, end: float) -> Wire:
|
||||||
"""trim
|
"""Trim a wire between [start, end] normalized over total length.
|
||||||
|
|
||||||
Create a new wire by keeping only the section between start and end.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
start (float): 0.0 <= start < 1.0
|
start (float): normalized start position (0.0 to <1.0)
|
||||||
end (float): 0.0 < end <= 1.0
|
end (float): normalized end position (>0.0 to 1.0)
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: start >= end
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Wire: trimmed wire
|
Wire: trimmed Wire
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
|
||||||
if start >= end:
|
if start >= end:
|
||||||
raise ValueError("start must be less than end")
|
raise ValueError("start must be less than end")
|
||||||
|
|
||||||
edges = self.edges()
|
# Extract the edges in order
|
||||||
|
ordered_edges = self.edges().sort_by(self)
|
||||||
|
|
||||||
# If this is really just an edge, skip the complexity of a Wire
|
# If this is really just an edge, skip the complexity of a Wire
|
||||||
if len(edges) == 1:
|
if len(ordered_edges) == 1:
|
||||||
return Wire([edges[0].trim(start, end)])
|
return Wire([ordered_edges[0].trim(start, end)])
|
||||||
|
|
||||||
# For each Edge determine the beginning and end wire parameters
|
total_length = self.length
|
||||||
# Note that u, v values are parameters along the Wire
|
start_len = start * total_length
|
||||||
edges_uv_values: list[tuple[float, float, Edge]] = []
|
end_len = end * total_length
|
||||||
found_end_of_wire = False # for finding ends of closed wires
|
|
||||||
|
|
||||||
for edge in edges:
|
|
||||||
u = self.param_at_point(edge.position_at(0))
|
|
||||||
v = self.param_at_point(edge.position_at(1))
|
|
||||||
if self.is_closed: # Avoid two beginnings or ends
|
|
||||||
u = (
|
|
||||||
1 - u
|
|
||||||
if found_end_of_wire and (isclose_b(u, 0) or isclose_b(u, 1))
|
|
||||||
else u
|
|
||||||
)
|
|
||||||
v = (
|
|
||||||
1 - v
|
|
||||||
if found_end_of_wire and (isclose_b(v, 0) or isclose_b(v, 1))
|
|
||||||
else v
|
|
||||||
)
|
|
||||||
found_end_of_wire = (
|
|
||||||
isclose_b(u, 0)
|
|
||||||
or isclose_b(u, 1)
|
|
||||||
or isclose_b(v, 0)
|
|
||||||
or isclose_b(v, 1)
|
|
||||||
or found_end_of_wire
|
|
||||||
)
|
|
||||||
|
|
||||||
# Edge might be reversed and require flipping parms
|
|
||||||
u, v = (v, u) if u > v else (u, v)
|
|
||||||
|
|
||||||
edges_uv_values.append((u, v, edge))
|
|
||||||
|
|
||||||
trimmed_edges = []
|
trimmed_edges = []
|
||||||
for u, v, edge in edges_uv_values:
|
cur_length = 0.0
|
||||||
if v < start or u > end: # Edge not needed
|
|
||||||
continue
|
|
||||||
|
|
||||||
if start <= u and v <= end: # keep whole Edge
|
for edge in ordered_edges:
|
||||||
trimmed_edges.append(edge)
|
edge_len = edge.length
|
||||||
|
edge_start = cur_length
|
||||||
|
edge_end = cur_length + edge_len
|
||||||
|
cur_length = edge_end
|
||||||
|
|
||||||
elif start >= u and end <= v: # Wire trimmed to single Edge
|
if edge_end <= start_len or edge_start >= end_len:
|
||||||
u_edge = edge.param_at_point(self.position_at(start))
|
continue # skip
|
||||||
v_edge = edge.param_at_point(self.position_at(end))
|
|
||||||
u_edge, v_edge = (
|
|
||||||
(v_edge, u_edge) if u_edge > v_edge else (u_edge, v_edge)
|
|
||||||
)
|
|
||||||
trimmed_edges.append(edge.trim(u_edge, v_edge))
|
|
||||||
|
|
||||||
elif start <= u: # keep start of Edge
|
if edge_start >= start_len and edge_end <= end_len:
|
||||||
u_edge = edge.param_at_point(self.position_at(end))
|
trimmed_edges.append(edge) # keep whole Edge
|
||||||
if u_edge != 0:
|
else:
|
||||||
trimmed_edges.append(edge.trim(0, u_edge))
|
# Normalize trim points relative to this edge
|
||||||
|
trim_start_len = max(start_len, edge_start)
|
||||||
|
trim_end_len = min(end_len, edge_end)
|
||||||
|
|
||||||
else: # v <= end keep end of Edge
|
u0 = (trim_start_len - edge_start) / edge_len
|
||||||
v_edge = edge.param_at_point(self.position_at(start))
|
u1 = (trim_end_len - edge_start) / edge_len
|
||||||
if v_edge != 1:
|
|
||||||
trimmed_edges.append(edge.trim(v_edge, 1))
|
if abs(u1 - u0) > TOLERANCE:
|
||||||
|
trimmed_edges.append(edge.trim(u0, u1))
|
||||||
|
|
||||||
return Wire(trimmed_edges)
|
return Wire(trimmed_edges)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,24 +50,27 @@ import copy
|
||||||
import itertools
|
import itertools
|
||||||
import warnings
|
import warnings
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from collections.abc import Callable, Iterable, Iterator
|
||||||
|
from functools import reduce
|
||||||
from typing import (
|
from typing import (
|
||||||
cast as tcast,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Generic,
|
Generic,
|
||||||
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
Protocol,
|
Protocol,
|
||||||
SupportsIndex,
|
SupportsIndex,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
overload,
|
|
||||||
TYPE_CHECKING,
|
|
||||||
)
|
)
|
||||||
|
from typing import cast as tcast
|
||||||
from collections.abc import Callable, Iterable, Iterator
|
from typing import overload
|
||||||
|
|
||||||
import OCP.GeomAbs as ga
|
import OCP.GeomAbs as ga
|
||||||
import OCP.TopAbs as ta
|
import OCP.TopAbs as ta
|
||||||
from IPython.lib.pretty import pretty, RepresentationPrinter
|
from anytree import NodeMixin, RenderTree
|
||||||
|
from IPython.lib.pretty import RepresentationPrinter, pretty
|
||||||
|
from OCP.Bnd import Bnd_Box, Bnd_OBB
|
||||||
from OCP.BOPAlgo import BOPAlgo_GlueEnum
|
from OCP.BOPAlgo import BOPAlgo_GlueEnum
|
||||||
from OCP.BRep import BRep_Tool
|
from OCP.BRep import BRep_Tool
|
||||||
from OCP.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
|
from OCP.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
|
||||||
|
|
@ -98,11 +101,12 @@ from OCP.BRepGProp import BRepGProp, BRepGProp_Face
|
||||||
from OCP.BRepIntCurveSurface import BRepIntCurveSurface_Inter
|
from OCP.BRepIntCurveSurface import BRepIntCurveSurface_Inter
|
||||||
from OCP.BRepMesh import BRepMesh_IncrementalMesh
|
from OCP.BRepMesh import BRepMesh_IncrementalMesh
|
||||||
from OCP.BRepTools import BRepTools
|
from OCP.BRepTools import BRepTools
|
||||||
from OCP.Bnd import Bnd_Box, Bnd_OBB
|
from OCP.gce import gce_MakeLin
|
||||||
from OCP.GProp import GProp_GProps
|
|
||||||
from OCP.Geom import Geom_Line
|
from OCP.Geom import Geom_Line
|
||||||
from OCP.GeomAPI import GeomAPI_ProjectPointOnSurf
|
from OCP.GeomAPI import GeomAPI_ProjectPointOnSurf
|
||||||
from OCP.GeomLib import GeomLib_IsPlanarSurface
|
from OCP.GeomLib import GeomLib_IsPlanarSurface
|
||||||
|
from OCP.gp import gp_Ax1, gp_Ax2, gp_Ax3, gp_Dir, gp_Pnt, gp_Trsf, gp_Vec
|
||||||
|
from OCP.GProp import GProp_GProps
|
||||||
from OCP.ShapeAnalysis import ShapeAnalysis_Curve
|
from OCP.ShapeAnalysis import ShapeAnalysis_Curve
|
||||||
from OCP.ShapeCustom import ShapeCustom, ShapeCustom_RestrictionParameters
|
from OCP.ShapeCustom import ShapeCustom, ShapeCustom_RestrictionParameters
|
||||||
from OCP.ShapeFix import ShapeFix_Shape
|
from OCP.ShapeFix import ShapeFix_Shape
|
||||||
|
|
@ -110,26 +114,25 @@ from OCP.ShapeUpgrade import ShapeUpgrade_UnifySameDomain
|
||||||
from OCP.TopAbs import TopAbs_Orientation, TopAbs_ShapeEnum
|
from OCP.TopAbs import TopAbs_Orientation, TopAbs_ShapeEnum
|
||||||
from OCP.TopExp import TopExp, TopExp_Explorer
|
from OCP.TopExp import TopExp, TopExp_Explorer
|
||||||
from OCP.TopLoc import TopLoc_Location
|
from OCP.TopLoc import TopLoc_Location
|
||||||
from OCP.TopTools import (
|
|
||||||
TopTools_IndexedDataMapOfShapeListOfShape,
|
|
||||||
TopTools_ListOfShape,
|
|
||||||
TopTools_SequenceOfShape,
|
|
||||||
)
|
|
||||||
from OCP.TopoDS import (
|
from OCP.TopoDS import (
|
||||||
TopoDS,
|
TopoDS,
|
||||||
TopoDS_Compound,
|
TopoDS_Compound,
|
||||||
|
TopoDS_Edge,
|
||||||
TopoDS_Face,
|
TopoDS_Face,
|
||||||
TopoDS_Iterator,
|
TopoDS_Iterator,
|
||||||
TopoDS_Shape,
|
TopoDS_Shape,
|
||||||
TopoDS_Shell,
|
TopoDS_Shell,
|
||||||
TopoDS_Solid,
|
TopoDS_Solid,
|
||||||
TopoDS_Vertex,
|
TopoDS_Vertex,
|
||||||
TopoDS_Edge,
|
|
||||||
TopoDS_Wire,
|
TopoDS_Wire,
|
||||||
)
|
)
|
||||||
from OCP.gce import gce_MakeLin
|
from OCP.TopTools import (
|
||||||
from OCP.gp import gp_Ax1, gp_Ax2, gp_Ax3, gp_Dir, gp_Pnt, gp_Trsf, gp_Vec
|
TopTools_IndexedDataMapOfShapeListOfShape,
|
||||||
from anytree import NodeMixin, RenderTree
|
TopTools_ListOfShape,
|
||||||
|
TopTools_SequenceOfShape,
|
||||||
|
)
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from build123d.build_enums import CenterOf, GeomType, Keep, SortBy, Transition
|
from build123d.build_enums import CenterOf, GeomType, Keep, SortBy, Transition
|
||||||
from build123d.geometry import (
|
from build123d.geometry import (
|
||||||
DEG2RAD,
|
DEG2RAD,
|
||||||
|
|
@ -145,19 +148,16 @@ from build123d.geometry import (
|
||||||
VectorLike,
|
VectorLike,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from .zero_d import Vertex # pylint: disable=R0801
|
|
||||||
from .one_d import Edge, Wire # pylint: disable=R0801
|
|
||||||
from .two_d import Face, Shell # pylint: disable=R0801
|
|
||||||
from .three_d import Solid # pylint: disable=R0801
|
|
||||||
from .composite import Compound # pylint: disable=R0801
|
|
||||||
from build123d.build_part import BuildPart # pylint: disable=R0801
|
from build123d.build_part import BuildPart # pylint: disable=R0801
|
||||||
|
|
||||||
|
from .composite import Compound # pylint: disable=R0801
|
||||||
|
from .one_d import Edge, Wire # pylint: disable=R0801
|
||||||
|
from .three_d import Solid # pylint: disable=R0801
|
||||||
|
from .two_d import Face, Shell # pylint: disable=R0801
|
||||||
|
from .zero_d import Vertex # pylint: disable=R0801
|
||||||
|
|
||||||
Shapes = Literal["Vertex", "Edge", "Wire", "Face", "Shell", "Solid", "Compound"]
|
Shapes = Literal["Vertex", "Edge", "Wire", "Face", "Shell", "Solid", "Compound"]
|
||||||
TrimmingTool = Union[Plane, "Shell", "Face"]
|
TrimmingTool = Union[Plane, "Shell", "Face"]
|
||||||
TOPODS = TypeVar("TOPODS", bound=TopoDS_Shape)
|
TOPODS = TypeVar("TOPODS", bound=TopoDS_Shape)
|
||||||
|
|
@ -422,6 +422,14 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_null(self) -> bool:
|
||||||
|
"""Returns true if this shape is null. In other words, it references no
|
||||||
|
underlying shape with the potential to be given a location and an
|
||||||
|
orientation.
|
||||||
|
"""
|
||||||
|
return self.wrapped is None or self.wrapped.IsNull()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_planar_face(self) -> bool:
|
def is_planar_face(self) -> bool:
|
||||||
"""Is the shape a planar face even though its geom_type may not be PLANE"""
|
"""Is the shape a planar face even though its geom_type may not be PLANE"""
|
||||||
|
|
@ -431,6 +439,35 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
is_face_planar = GeomLib_IsPlanarSurface(surface, TOLERANCE)
|
is_face_planar = GeomLib_IsPlanarSurface(surface, TOLERANCE)
|
||||||
return is_face_planar.IsPlanar()
|
return is_face_planar.IsPlanar()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
"""Returns True if no defect is detected on the shape S or any of its
|
||||||
|
subshapes. See the OCCT docs on BRepCheck_Analyzer::IsValid for a full
|
||||||
|
description of what is checked.
|
||||||
|
"""
|
||||||
|
if self.wrapped is None:
|
||||||
|
return True
|
||||||
|
chk = BRepCheck_Analyzer(self.wrapped)
|
||||||
|
chk.SetParallel(True)
|
||||||
|
return chk.IsValid()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def global_location(self) -> Location:
|
||||||
|
"""
|
||||||
|
The location of this Shape relative to the global coordinate system.
|
||||||
|
|
||||||
|
This property computes the composite transformation by traversing the
|
||||||
|
hierarchy from the root of the assembly to this node, combining the
|
||||||
|
location of each ancestor. It reflects the absolute position and
|
||||||
|
orientation of the shape in world space, even when the shape is deeply
|
||||||
|
nested within an assembly.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This is only meaningful when the Shape is part of an assembly tree
|
||||||
|
where parent-child relationships define relative placements.
|
||||||
|
"""
|
||||||
|
return reduce(lambda loc, n: loc * n.location, self.path, Location())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def location(self) -> Location | None:
|
def location(self) -> Location | None:
|
||||||
"""Get this Shape's Location"""
|
"""Get this Shape's Location"""
|
||||||
|
|
@ -543,6 +580,11 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
(Vector(principal_props.ThirdAxisOfInertia()), principal_moments[2]),
|
(Vector(principal_props.ThirdAxisOfInertia()), principal_moments[2]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shape_type(self) -> Shapes:
|
||||||
|
"""Return the shape type string for this class"""
|
||||||
|
return tcast(Shapes, Shape.shape_LUT[shapetype(self.wrapped)])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def static_moments(self) -> tuple[float, float, float]:
|
def static_moments(self) -> tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -660,15 +702,15 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
address = node.address
|
address = node.address
|
||||||
name = ""
|
name = ""
|
||||||
loc = (
|
loc = (
|
||||||
"Center" + str(node.position.to_tuple())
|
"Center" + str(tuple(node.position))
|
||||||
if show_center
|
if show_center
|
||||||
else "Position" + str(node.position.to_tuple())
|
else "Position" + str(tuple(node.position))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
address = id(node)
|
address = id(node)
|
||||||
name = node.__class__.__name__.ljust(9)
|
name = node.__class__.__name__.ljust(9)
|
||||||
loc = (
|
loc = (
|
||||||
"Center" + str(node.center().to_tuple())
|
"Center" + str(tuple(node.center()))
|
||||||
if show_center
|
if show_center
|
||||||
else "Location" + repr(node.location)
|
else "Location" + repr(node.location)
|
||||||
)
|
)
|
||||||
|
|
@ -758,7 +800,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
[shape.__class__.cast(i) for i in shape.entities(entity_type)]
|
[shape.__class__.cast(i) for i in shape.entities(entity_type)]
|
||||||
)
|
)
|
||||||
for item in shape_list:
|
for item in shape_list:
|
||||||
item.topo_parent = shape
|
item.topo_parent = shape if shape.topo_parent is None else shape.topo_parent
|
||||||
return shape_list
|
return shape_list
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -1188,7 +1230,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
"""fix - try to fix shape if not valid"""
|
"""fix - try to fix shape if not valid"""
|
||||||
if self.wrapped is None:
|
if self.wrapped is None:
|
||||||
return self
|
return self
|
||||||
if not self.is_valid():
|
if not self.is_valid:
|
||||||
shape_copy: Shape = copy.deepcopy(self, None)
|
shape_copy: Shape = copy.deepcopy(self, None)
|
||||||
shape_copy.wrapped = tcast(TOPODS, fix(self.wrapped))
|
shape_copy.wrapped = tcast(TOPODS, fix(self.wrapped))
|
||||||
|
|
||||||
|
|
@ -1332,7 +1374,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
return None
|
return None
|
||||||
if (
|
if (
|
||||||
not isinstance(shape_intersections, ShapeList)
|
not isinstance(shape_intersections, ShapeList)
|
||||||
and shape_intersections.is_null()
|
and shape_intersections.is_null
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
return shape_intersections
|
return shape_intersections
|
||||||
|
|
@ -1352,18 +1394,6 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
return False
|
return False
|
||||||
return self.wrapped.IsEqual(other.wrapped)
|
return self.wrapped.IsEqual(other.wrapped)
|
||||||
|
|
||||||
def is_null(self) -> bool:
|
|
||||||
"""Returns true if this shape is null. In other words, it references no
|
|
||||||
underlying shape with the potential to be given a location and an
|
|
||||||
orientation.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self.wrapped is None or self.wrapped.IsNull()
|
|
||||||
|
|
||||||
def is_same(self, other: Shape) -> bool:
|
def is_same(self, other: Shape) -> bool:
|
||||||
"""Returns True if other and this shape are same, i.e. if they share the
|
"""Returns True if other and this shape are same, i.e. if they share the
|
||||||
same TShape with the same Locations. Orientations may differ. Also see
|
same TShape with the same Locations. Orientations may differ. Also see
|
||||||
|
|
@ -1379,22 +1409,6 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
return False
|
return False
|
||||||
return self.wrapped.IsSame(other.wrapped)
|
return self.wrapped.IsSame(other.wrapped)
|
||||||
|
|
||||||
def is_valid(self) -> bool:
|
|
||||||
"""Returns True if no defect is detected on the shape S or any of its
|
|
||||||
subshapes. See the OCCT docs on BRepCheck_Analyzer::IsValid for a full
|
|
||||||
description of what is checked.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.wrapped is None:
|
|
||||||
return True
|
|
||||||
chk = BRepCheck_Analyzer(self.wrapped)
|
|
||||||
chk.SetParallel(True)
|
|
||||||
return chk.IsValid()
|
|
||||||
|
|
||||||
def locate(self, loc: Location) -> Self:
|
def locate(self, loc: Location) -> Self:
|
||||||
"""Apply a location in absolute sense to self
|
"""Apply a location in absolute sense to self
|
||||||
|
|
||||||
|
|
@ -1626,6 +1640,12 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Args:
|
Args:
|
||||||
loc (Location): new location to set for self
|
loc (Location): new location to set for self
|
||||||
"""
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"The 'relocate' method is deprecated and will be removed in a future version."
|
||||||
|
"Use move, moved, locate, or located instead",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
if self.wrapped is None:
|
if self.wrapped is None:
|
||||||
raise ValueError("Cannot relocate an empty shape")
|
raise ValueError("Cannot relocate an empty shape")
|
||||||
if loc.wrapped is None:
|
if loc.wrapped is None:
|
||||||
|
|
@ -1677,10 +1697,6 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
|
|
||||||
return self._apply_transform(transformation)
|
return self._apply_transform(transformation)
|
||||||
|
|
||||||
def shape_type(self) -> Shapes:
|
|
||||||
"""Return the shape type string for this class"""
|
|
||||||
return tcast(Shapes, Shape.shape_LUT[shapetype(self.wrapped)])
|
|
||||||
|
|
||||||
def shell(self) -> Shell | None:
|
def shell(self) -> Shell | None:
|
||||||
"""Return the Shell"""
|
"""Return the Shell"""
|
||||||
return None
|
return None
|
||||||
|
|
@ -1916,7 +1932,9 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
) -> Self:
|
) -> Self:
|
||||||
"""to_splines
|
"""to_splines
|
||||||
|
|
||||||
Approximate shape with b-splines of the specified degree.
|
A shape-processing utility that forces all geometry in a shape to be converted into
|
||||||
|
BSplines. It's useful when working with tools or export formats that require uniform
|
||||||
|
geometry, or for downstream processing that only understands BSpline representations.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
degree (int, optional): Maximum degree. Defaults to 3.
|
degree (int, optional): Maximum degree. Defaults to 3.
|
||||||
|
|
@ -2144,7 +2162,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
|
|
||||||
def _ocp_section(
|
def _ocp_section(
|
||||||
self: Shape, other: Vertex | Edge | Wire | Face
|
self: Shape, other: Vertex | Edge | Wire | Face
|
||||||
) -> tuple[list[Vertex], list[Edge]]:
|
) -> tuple[ShapeList[Vertex], ShapeList[Edge]]:
|
||||||
"""_ocp_section
|
"""_ocp_section
|
||||||
|
|
||||||
Create a BRepAlgoAPI_Section object
|
Create a BRepAlgoAPI_Section object
|
||||||
|
|
@ -2162,38 +2180,34 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
other (Union[Vertex, Edge, Wire, Face]): shape to section with
|
other (Union[Vertex, Edge, Wire, Face]): shape to section with
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[list[Vertex], list[Edge]]: section results
|
tuple[ShapeList[Vertex], ShapeList[Edge]]: section results
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None or other.wrapped is None:
|
if self.wrapped is None or other.wrapped is None:
|
||||||
return ([], [])
|
return (ShapeList(), ShapeList())
|
||||||
|
|
||||||
try:
|
section = BRepAlgoAPI_Section(self.wrapped, other.wrapped)
|
||||||
section = BRepAlgoAPI_Section(other.geom_adaptor(), self.wrapped)
|
section.SetRunParallel(True)
|
||||||
except (TypeError, AttributeError):
|
section.Approximation(True)
|
||||||
try:
|
section.ComputePCurveOn1(True)
|
||||||
section = BRepAlgoAPI_Section(self.geom_adaptor(), other.wrapped)
|
section.ComputePCurveOn2(True)
|
||||||
except (TypeError, AttributeError):
|
|
||||||
return ([], [])
|
|
||||||
|
|
||||||
# Perform the intersection calculation
|
|
||||||
section.Build()
|
section.Build()
|
||||||
|
|
||||||
# Get the resulting shapes from the intersection
|
# Get the resulting shapes from the intersection
|
||||||
intersection_shape = section.Shape()
|
intersection_shape: TopoDS_Shape = section.Shape()
|
||||||
|
|
||||||
vertices = []
|
vertices: list[Vertex] = []
|
||||||
# Iterate through the intersection shape to find intersection points/edges
|
# Iterate through the intersection shape to find intersection points/edges
|
||||||
explorer = TopExp_Explorer(intersection_shape, TopAbs_ShapeEnum.TopAbs_VERTEX)
|
explorer = TopExp_Explorer(intersection_shape, TopAbs_ShapeEnum.TopAbs_VERTEX)
|
||||||
while explorer.More():
|
while explorer.More():
|
||||||
vertices.append(self.__class__.cast(downcast(explorer.Current())))
|
vertices.append(self.__class__.cast(downcast(explorer.Current())))
|
||||||
explorer.Next()
|
explorer.Next()
|
||||||
edges = []
|
edges: ShapeList[Edge] = ShapeList()
|
||||||
explorer = TopExp_Explorer(intersection_shape, TopAbs_ShapeEnum.TopAbs_EDGE)
|
explorer = TopExp_Explorer(intersection_shape, TopAbs_ShapeEnum.TopAbs_EDGE)
|
||||||
while explorer.More():
|
while explorer.More():
|
||||||
edges.append(self.__class__.cast(downcast(explorer.Current())))
|
edges.append(self.__class__.cast(downcast(explorer.Current())))
|
||||||
explorer.Next()
|
explorer.Next()
|
||||||
|
|
||||||
return (vertices, edges)
|
return (ShapeList(set(vertices)), edges)
|
||||||
|
|
||||||
def _repr_html_(self):
|
def _repr_html_(self):
|
||||||
"""Jupyter 3D representation support"""
|
"""Jupyter 3D representation support"""
|
||||||
|
|
@ -2326,10 +2340,27 @@ class ShapeList(list[T]):
|
||||||
|
|
||||||
# ---- Instance Methods ----
|
# ---- Instance Methods ----
|
||||||
|
|
||||||
def __add__(self, other: ShapeList) -> ShapeList[T]: # type: ignore
|
def __add__(self, other: Shape | Iterable[Shape]) -> ShapeList[T]: # type: ignore
|
||||||
"""Combine two ShapeLists together operator +"""
|
"""Return a new ShapeList that includes other"""
|
||||||
# return ShapeList(itertools.chain(self, other)) # breaks MacOS-13
|
if isinstance(other, (Vector, Shape)):
|
||||||
|
return ShapeList(tcast(list[T], list(self) + [other]))
|
||||||
|
if isinstance(other, Iterable) and all(
|
||||||
|
isinstance(o, (Shape, Vector)) for o in other
|
||||||
|
):
|
||||||
return ShapeList(list(self) + list(other))
|
return ShapeList(list(self) + list(other))
|
||||||
|
raise TypeError(f"Cannot add object of type {type(other)} to ShapeList")
|
||||||
|
|
||||||
|
def __iadd__(self, other: Shape | Iterable[Shape]) -> Self: # type: ignore
|
||||||
|
"""In-place addition to this ShapeList"""
|
||||||
|
if isinstance(other, (Vector, Shape)):
|
||||||
|
self.append(tcast(T, other))
|
||||||
|
elif isinstance(other, Iterable) and all(
|
||||||
|
isinstance(o, (Shape, Vector)) for o in other
|
||||||
|
):
|
||||||
|
self.extend(other)
|
||||||
|
else:
|
||||||
|
raise TypeError(f"Cannot add object of type {type(other)} to ShapeList")
|
||||||
|
return self
|
||||||
|
|
||||||
def __and__(self, other: ShapeList) -> ShapeList[T]:
|
def __and__(self, other: ShapeList) -> ShapeList[T]:
|
||||||
"""Intersect two ShapeLists operator &"""
|
"""Intersect two ShapeLists operator &"""
|
||||||
|
|
@ -2582,29 +2613,27 @@ class ShapeList(list[T]):
|
||||||
if inclusive == (True, True):
|
if inclusive == (True, True):
|
||||||
objects = filter(
|
objects = filter(
|
||||||
lambda o: minimum
|
lambda o: minimum
|
||||||
<= axis.to_plane().to_local_coords(o).center().Z
|
<= Plane(axis).to_local_coords(o).center().Z
|
||||||
<= maximum,
|
<= maximum,
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
elif inclusive == (True, False):
|
elif inclusive == (True, False):
|
||||||
objects = filter(
|
objects = filter(
|
||||||
lambda o: minimum
|
lambda o: minimum
|
||||||
<= axis.to_plane().to_local_coords(o).center().Z
|
<= Plane(axis).to_local_coords(o).center().Z
|
||||||
< maximum,
|
< maximum,
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
elif inclusive == (False, True):
|
elif inclusive == (False, True):
|
||||||
objects = filter(
|
objects = filter(
|
||||||
lambda o: minimum
|
lambda o: minimum
|
||||||
< axis.to_plane().to_local_coords(o).center().Z
|
< Plane(axis).to_local_coords(o).center().Z
|
||||||
<= maximum,
|
<= maximum,
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
elif inclusive == (False, False):
|
elif inclusive == (False, False):
|
||||||
objects = filter(
|
objects = filter(
|
||||||
lambda o: minimum
|
lambda o: minimum < Plane(axis).to_local_coords(o).center().Z < maximum,
|
||||||
< axis.to_plane().to_local_coords(o).center().Z
|
|
||||||
< maximum,
|
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,11 @@ from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
||||||
from OCP.BRepFeat import BRepFeat_MakeDPrism
|
from OCP.BRepFeat import BRepFeat_MakeDPrism
|
||||||
from OCP.BRepFilletAPI import BRepFilletAPI_MakeChamfer, BRepFilletAPI_MakeFillet
|
from OCP.BRepFilletAPI import BRepFilletAPI_MakeChamfer, BRepFilletAPI_MakeFillet
|
||||||
from OCP.BRepOffset import BRepOffset_MakeOffset, BRepOffset_Skin
|
from OCP.BRepOffset import BRepOffset_MakeOffset, BRepOffset_Skin
|
||||||
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakePipeShell, BRepOffsetAPI_MakeThickSolid
|
from OCP.BRepOffsetAPI import (
|
||||||
|
BRepOffsetAPI_DraftAngle,
|
||||||
|
BRepOffsetAPI_MakePipeShell,
|
||||||
|
BRepOffsetAPI_MakeThickSolid,
|
||||||
|
)
|
||||||
from OCP.BRepPrimAPI import (
|
from OCP.BRepPrimAPI import (
|
||||||
BRepPrimAPI_MakeBox,
|
BRepPrimAPI_MakeBox,
|
||||||
BRepPrimAPI_MakeCone,
|
BRepPrimAPI_MakeCone,
|
||||||
|
|
@ -88,7 +92,7 @@ from OCP.TopExp import TopExp
|
||||||
from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape, TopTools_ListOfShape
|
from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape, TopTools_ListOfShape
|
||||||
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Solid, TopoDS_Wire
|
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Solid, TopoDS_Wire
|
||||||
from OCP.gp import gp_Ax2, gp_Pnt
|
from OCP.gp import gp_Ax2, gp_Pnt
|
||||||
from build123d.build_enums import CenterOf, Kind, Transition, Until
|
from build123d.build_enums import CenterOf, GeomType, Kind, Transition, Until
|
||||||
from build123d.geometry import (
|
from build123d.geometry import (
|
||||||
DEG2RAD,
|
DEG2RAD,
|
||||||
Axis,
|
Axis,
|
||||||
|
|
@ -255,7 +259,7 @@ class Mixin3D(Shape):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_shape = self.__class__(chamfer_builder.Shape())
|
new_shape = self.__class__(chamfer_builder.Shape())
|
||||||
if not new_shape.is_valid():
|
if not new_shape.is_valid:
|
||||||
raise Standard_Failure
|
raise Standard_Failure
|
||||||
except (StdFail_NotDone, Standard_Failure) as err:
|
except (StdFail_NotDone, Standard_Failure) as err:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|
@ -339,7 +343,7 @@ class Mixin3D(Shape):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_shape = self.__class__(fillet_builder.Shape())
|
new_shape = self.__class__(fillet_builder.Shape())
|
||||||
if not new_shape.is_valid():
|
if not new_shape.is_valid:
|
||||||
raise Standard_Failure
|
raise Standard_Failure
|
||||||
except (StdFail_NotDone, Standard_Failure) as err:
|
except (StdFail_NotDone, Standard_Failure) as err:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|
@ -431,7 +435,7 @@ class Mixin3D(Shape):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
solid_classifier = BRepClass3d_SolidClassifier(self.wrapped)
|
solid_classifier = BRepClass3d_SolidClassifier(self.wrapped)
|
||||||
solid_classifier.Perform(gp_Pnt(*Vector(point).to_tuple()), tolerance)
|
solid_classifier.Perform(gp_Pnt(*Vector(point)), tolerance)
|
||||||
|
|
||||||
return solid_classifier.State() == ta.TopAbs_IN or solid_classifier.IsOnAFace()
|
return solid_classifier.State() == ta.TopAbs_IN or solid_classifier.IsOnAFace()
|
||||||
|
|
||||||
|
|
@ -481,7 +485,7 @@ class Mixin3D(Shape):
|
||||||
# Do these numbers work? - if not try with the smaller window
|
# Do these numbers work? - if not try with the smaller window
|
||||||
try:
|
try:
|
||||||
new_shape = self.__class__(fillet_builder.Shape())
|
new_shape = self.__class__(fillet_builder.Shape())
|
||||||
if not new_shape.is_valid():
|
if not new_shape.is_valid:
|
||||||
raise fillet_exception
|
raise fillet_exception
|
||||||
except fillet_exception:
|
except fillet_exception:
|
||||||
return __max_fillet(window_min, window_mid, current_iteration + 1)
|
return __max_fillet(window_min, window_mid, current_iteration + 1)
|
||||||
|
|
@ -495,7 +499,7 @@ class Mixin3D(Shape):
|
||||||
)
|
)
|
||||||
return return_value
|
return return_value
|
||||||
|
|
||||||
if not self.is_valid():
|
if not self.is_valid:
|
||||||
raise ValueError("Invalid Shape")
|
raise ValueError("Invalid Shape")
|
||||||
|
|
||||||
native_edges = [e.wrapped for e in edge_list]
|
native_edges = [e.wrapped for e in edge_list]
|
||||||
|
|
@ -661,7 +665,7 @@ class Solid(Mixin3D, Shape[TopoDS_Solid]):
|
||||||
builder.SetMode(coordinate_system)
|
builder.SetMode(coordinate_system)
|
||||||
rotate = True
|
rotate = True
|
||||||
elif isinstance(binormal, (Wire, Edge)):
|
elif isinstance(binormal, (Wire, Edge)):
|
||||||
builder.SetMode(binormal.to_wire().wrapped, True)
|
builder.SetMode(Wire(binormal).wrapped, True)
|
||||||
|
|
||||||
return rotate
|
return rotate
|
||||||
|
|
||||||
|
|
@ -1229,6 +1233,21 @@ class Solid(Mixin3D, Shape[TopoDS_Solid]):
|
||||||
|
|
||||||
Sweep the given cross section into a prismatic solid along the provided path
|
Sweep the given cross section into a prismatic solid along the provided path
|
||||||
|
|
||||||
|
The is_frenet parameter controls how the profile orientation changes as it
|
||||||
|
follows along the sweep path. If is_frenet is False, the orientation of the
|
||||||
|
profile is kept consistent from point to point. The resulting shape has the
|
||||||
|
minimum possible twisting. Unintuitively, when a profile is swept along a
|
||||||
|
helix, this results in the orientation of the profile slowly creeping
|
||||||
|
(rotating) as it follows the helix. Setting is_frenet to True prevents this.
|
||||||
|
|
||||||
|
If is_frenet is True the orientation of the profile is based on the local
|
||||||
|
curvature and tangency vectors of the path. This keeps the orientation of the
|
||||||
|
profile consistent when sweeping along a helix (because the curvature vector of
|
||||||
|
a straight helix always points to its axis). However, when path is not a helix,
|
||||||
|
the resulting shape can have strange looking twists sometimes. For more
|
||||||
|
information, see Frenet Serret formulas
|
||||||
|
http://en.wikipedia.org/wiki/Frenet%E2%80%93Serret_formulas.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
section (Union[Face, Wire]): cross section to sweep
|
section (Union[Face, Wire]): cross section to sweep
|
||||||
path (Union[Wire, Edge]): sweep path
|
path (Union[Wire, Edge]): sweep path
|
||||||
|
|
@ -1252,7 +1271,7 @@ class Solid(Mixin3D, Shape[TopoDS_Solid]):
|
||||||
|
|
||||||
shapes = []
|
shapes = []
|
||||||
for wire in [outer_wire] + inner_wires:
|
for wire in [outer_wire] + inner_wires:
|
||||||
builder = BRepOffsetAPI_MakePipeShell(path.to_wire().wrapped)
|
builder = BRepOffsetAPI_MakePipeShell(Wire(path).wrapped)
|
||||||
|
|
||||||
rotate = False
|
rotate = False
|
||||||
|
|
||||||
|
|
@ -1294,6 +1313,21 @@ class Solid(Mixin3D, Shape[TopoDS_Solid]):
|
||||||
|
|
||||||
Sweep through a sequence of profiles following a path.
|
Sweep through a sequence of profiles following a path.
|
||||||
|
|
||||||
|
The is_frenet parameter controls how the profile orientation changes as it
|
||||||
|
follows along the sweep path. If is_frenet is False, the orientation of the
|
||||||
|
profile is kept consistent from point to point. The resulting shape has the
|
||||||
|
minimum possible twisting. Unintuitively, when a profile is swept along a
|
||||||
|
helix, this results in the orientation of the profile slowly creeping
|
||||||
|
(rotating) as it follows the helix. Setting is_frenet to True prevents this.
|
||||||
|
|
||||||
|
If is_frenet is True the orientation of the profile is based on the local
|
||||||
|
curvature and tangency vectors of the path. This keeps the orientation of the
|
||||||
|
profile consistent when sweeping along a helix (because the curvature vector of
|
||||||
|
a straight helix always points to its axis). However, when path is not a helix,
|
||||||
|
the resulting shape can have strange looking twists sometimes. For more
|
||||||
|
information, see Frenet Serret formulas
|
||||||
|
http://en.wikipedia.org/wiki/Frenet%E2%80%93Serret_formulas.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
profiles (Iterable[Union[Wire, Face]]): list of profiles
|
profiles (Iterable[Union[Wire, Face]]): list of profiles
|
||||||
path (Union[Wire, Edge]): The wire to sweep the face resulting from the wires over
|
path (Union[Wire, Edge]): The wire to sweep the face resulting from the wires over
|
||||||
|
|
@ -1305,7 +1339,7 @@ class Solid(Mixin3D, Shape[TopoDS_Solid]):
|
||||||
Returns:
|
Returns:
|
||||||
Solid: swept object
|
Solid: swept object
|
||||||
"""
|
"""
|
||||||
path_as_wire = path.to_wire().wrapped
|
path_as_wire = Wire(path).wrapped
|
||||||
|
|
||||||
builder = BRepOffsetAPI_MakePipeShell(path_as_wire)
|
builder = BRepOffsetAPI_MakePipeShell(path_as_wire)
|
||||||
|
|
||||||
|
|
@ -1391,3 +1425,62 @@ class Solid(Mixin3D, Shape[TopoDS_Solid]):
|
||||||
raise RuntimeError("Error applying thicken to given surface") from err
|
raise RuntimeError("Error applying thicken to given surface") from err
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def draft(self, faces: Iterable[Face], neutral_plane: Plane, angle: float) -> Solid:
|
||||||
|
"""Apply a draft angle to the given faces of the solid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
faces: Faces to which the draft should be applied.
|
||||||
|
neutral_plane: Plane defining the neutral direction and position.
|
||||||
|
angle: Draft angle in degrees.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Solid with the specified draft angles applied.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If draft application fails on any face or during build.
|
||||||
|
"""
|
||||||
|
valid_geom_types = {GeomType.PLANE, GeomType.CYLINDER, GeomType.CONE}
|
||||||
|
for face in faces:
|
||||||
|
if face.geom_type not in valid_geom_types:
|
||||||
|
raise ValueError(
|
||||||
|
f"Face {face} has unsupported geometry type {face.geom_type.name}. "
|
||||||
|
"Only PLANAR, CYLINDRICAL, and CONICAL faces are supported."
|
||||||
|
)
|
||||||
|
|
||||||
|
draft_angle_builder = BRepOffsetAPI_DraftAngle(self.wrapped)
|
||||||
|
|
||||||
|
for face in faces:
|
||||||
|
draft_angle_builder.Add(
|
||||||
|
face.wrapped,
|
||||||
|
neutral_plane.z_dir.to_dir(),
|
||||||
|
radians(angle),
|
||||||
|
neutral_plane.wrapped,
|
||||||
|
Flag=True,
|
||||||
|
)
|
||||||
|
if not draft_angle_builder.AddDone():
|
||||||
|
raise DraftAngleError(
|
||||||
|
"Draft could not be added to a face.",
|
||||||
|
face=face,
|
||||||
|
problematic_shape=draft_angle_builder.ProblematicShape(),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
draft_angle_builder.Build()
|
||||||
|
result = Solid(draft_angle_builder.Shape())
|
||||||
|
except StdFail_NotDone as err:
|
||||||
|
raise DraftAngleError(
|
||||||
|
"Draft build failed on the given solid.",
|
||||||
|
face=None,
|
||||||
|
problematic_shape=draft_angle_builder.ProblematicShape(),
|
||||||
|
) from err
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class DraftAngleError(RuntimeError):
|
||||||
|
"""Solid.draft custom exception"""
|
||||||
|
|
||||||
|
def __init__(self, message, face=None, problematic_shape=None):
|
||||||
|
super().__init__(message)
|
||||||
|
self.face = face
|
||||||
|
self.problematic_shape = problematic_shape
|
||||||
|
|
|
||||||
|
|
@ -58,18 +58,18 @@ from __future__ import annotations
|
||||||
import copy
|
import copy
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Any, overload, TypeVar, TYPE_CHECKING
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from collections.abc import Iterable, Sequence
|
from collections.abc import Iterable, Sequence
|
||||||
|
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
||||||
|
|
||||||
import OCP.TopAbs as ta
|
import OCP.TopAbs as ta
|
||||||
from OCP.BRep import BRep_Tool, BRep_Builder
|
from OCP.BRep import BRep_Builder, BRep_Tool
|
||||||
from OCP.BRepAdaptor import BRepAdaptor_Surface
|
from OCP.BRepAdaptor import BRepAdaptor_Surface
|
||||||
from OCP.BRepAlgo import BRepAlgo
|
from OCP.BRepAlgo import BRepAlgo
|
||||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
||||||
from OCP.BRepBuilderAPI import (
|
from OCP.BRepBuilderAPI import (
|
||||||
BRepBuilderAPI_MakeFace,
|
|
||||||
BRepBuilderAPI_MakeEdge,
|
BRepBuilderAPI_MakeEdge,
|
||||||
|
BRepBuilderAPI_MakeFace,
|
||||||
BRepBuilderAPI_MakeWire,
|
BRepBuilderAPI_MakeWire,
|
||||||
)
|
)
|
||||||
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
||||||
|
|
@ -80,30 +80,31 @@ from OCP.BRepIntCurveSurface import BRepIntCurveSurface_Inter
|
||||||
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeFilling, BRepOffsetAPI_MakePipeShell
|
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeFilling, BRepOffsetAPI_MakePipeShell
|
||||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeRevol
|
from OCP.BRepPrimAPI import BRepPrimAPI_MakeRevol
|
||||||
from OCP.BRepTools import BRepTools, BRepTools_ReShape
|
from OCP.BRepTools import BRepTools, BRepTools_ReShape
|
||||||
from OCP.GProp import GProp_GProps
|
from OCP.gce import gce_MakeLin
|
||||||
from OCP.Geom import Geom_BezierSurface, Geom_Surface, Geom_RectangularTrimmedSurface
|
from OCP.Geom import Geom_BezierSurface, Geom_RectangularTrimmedSurface, Geom_Surface
|
||||||
|
from OCP.GeomAbs import GeomAbs_C0
|
||||||
from OCP.GeomAPI import (
|
from OCP.GeomAPI import (
|
||||||
GeomAPI_ExtremaCurveCurve,
|
GeomAPI_ExtremaCurveCurve,
|
||||||
GeomAPI_PointsToBSplineSurface,
|
GeomAPI_PointsToBSplineSurface,
|
||||||
GeomAPI_ProjectPointOnSurf,
|
GeomAPI_ProjectPointOnSurf,
|
||||||
)
|
)
|
||||||
from OCP.GeomAbs import GeomAbs_C0
|
|
||||||
from OCP.GeomProjLib import GeomProjLib
|
from OCP.GeomProjLib import GeomProjLib
|
||||||
|
from OCP.gp import gp_Pnt, gp_Vec
|
||||||
|
from OCP.GProp import GProp_GProps
|
||||||
from OCP.Precision import Precision
|
from OCP.Precision import Precision
|
||||||
from OCP.ShapeFix import ShapeFix_Solid, ShapeFix_Wire
|
from OCP.ShapeFix import ShapeFix_Solid, ShapeFix_Wire
|
||||||
from OCP.Standard import (
|
from OCP.Standard import (
|
||||||
|
Standard_ConstructionError,
|
||||||
Standard_Failure,
|
Standard_Failure,
|
||||||
Standard_NoSuchObject,
|
Standard_NoSuchObject,
|
||||||
Standard_ConstructionError,
|
|
||||||
)
|
)
|
||||||
from OCP.StdFail import StdFail_NotDone
|
from OCP.StdFail import StdFail_NotDone
|
||||||
from OCP.TColStd import TColStd_HArray2OfReal
|
|
||||||
from OCP.TColgp import TColgp_HArray2OfPnt
|
from OCP.TColgp import TColgp_HArray2OfPnt
|
||||||
|
from OCP.TColStd import TColStd_HArray2OfReal
|
||||||
from OCP.TopExp import TopExp
|
from OCP.TopExp import TopExp
|
||||||
from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape, TopTools_ListOfShape
|
|
||||||
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Shell, TopoDS_Solid
|
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Shell, TopoDS_Solid
|
||||||
from OCP.gce import gce_MakeLin
|
from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape, TopTools_ListOfShape
|
||||||
from OCP.gp import gp_Pnt, gp_Vec
|
from typing_extensions import Self
|
||||||
|
|
||||||
from build123d.build_enums import CenterOf, GeomType, Keep, SortBy, Transition
|
from build123d.build_enums import CenterOf, GeomType, Keep, SortBy, Transition
|
||||||
from build123d.geometry import (
|
from build123d.geometry import (
|
||||||
|
|
@ -117,38 +118,36 @@ from build123d.geometry import (
|
||||||
Vector,
|
Vector,
|
||||||
VectorLike,
|
VectorLike,
|
||||||
)
|
)
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
from .one_d import Mixin1D, Edge, Wire
|
from .one_d import Edge, Mixin1D, Wire
|
||||||
from .shape_core import (
|
from .shape_core import (
|
||||||
Shape,
|
Shape,
|
||||||
ShapeList,
|
ShapeList,
|
||||||
SkipClean,
|
SkipClean,
|
||||||
downcast,
|
|
||||||
get_top_level_topods_shapes,
|
|
||||||
_sew_topods_faces,
|
_sew_topods_faces,
|
||||||
shapetype,
|
|
||||||
_topods_entities,
|
_topods_entities,
|
||||||
_topods_face_normal_at,
|
_topods_face_normal_at,
|
||||||
|
downcast,
|
||||||
|
get_top_level_topods_shapes,
|
||||||
|
shapetype,
|
||||||
)
|
)
|
||||||
from .utils import (
|
from .utils import (
|
||||||
_extrude_topods_shape,
|
_extrude_topods_shape,
|
||||||
find_max_dimension,
|
|
||||||
_make_loft,
|
_make_loft,
|
||||||
_make_topods_face_from_wires,
|
_make_topods_face_from_wires,
|
||||||
_topods_bool_op,
|
_topods_bool_op,
|
||||||
|
find_max_dimension,
|
||||||
)
|
)
|
||||||
from .zero_d import Vertex
|
from .zero_d import Vertex
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from .three_d import Solid # pylint: disable=R0801
|
|
||||||
from .composite import Compound, Curve # pylint: disable=R0801
|
from .composite import Compound, Curve # pylint: disable=R0801
|
||||||
|
from .three_d import Solid # pylint: disable=R0801
|
||||||
|
|
||||||
T = TypeVar("T", Edge, Wire, "Face")
|
T = TypeVar("T", Edge, Wire, "Face")
|
||||||
|
|
||||||
|
|
||||||
class Mixin2D(Shape):
|
class Mixin2D(ABC, Shape):
|
||||||
"""Additional methods to add to Face and Shell class"""
|
"""Additional methods to add to Face and Shell class"""
|
||||||
|
|
||||||
project_to_viewport = Mixin1D.project_to_viewport
|
project_to_viewport = Mixin1D.project_to_viewport
|
||||||
|
|
@ -201,6 +200,9 @@ class Mixin2D(Shape):
|
||||||
new_surface = copy.deepcopy(self)
|
new_surface = copy.deepcopy(self)
|
||||||
new_surface.wrapped = downcast(self.wrapped.Complemented())
|
new_surface.wrapped = downcast(self.wrapped.Complemented())
|
||||||
|
|
||||||
|
# As the surface has been modified, the parent is no longer valid
|
||||||
|
new_surface.topo_parent = None
|
||||||
|
|
||||||
return new_surface
|
return new_surface
|
||||||
|
|
||||||
def face(self) -> Face | None:
|
def face(self) -> Face | None:
|
||||||
|
|
@ -235,7 +237,7 @@ class Mixin2D(Shape):
|
||||||
while intersect_maker.More():
|
while intersect_maker.More():
|
||||||
inter_pt = intersect_maker.Pnt()
|
inter_pt = intersect_maker.Pnt()
|
||||||
# Calculate distance along axis
|
# Calculate distance along axis
|
||||||
distance = other.to_plane().to_local_coords(Vector(inter_pt)).Z
|
distance = Plane(other).to_local_coords(Vector(inter_pt)).Z
|
||||||
intersections.append(
|
intersections.append(
|
||||||
(
|
(
|
||||||
intersect_maker.Face(), # TopoDS_Face
|
intersect_maker.Face(), # TopoDS_Face
|
||||||
|
|
@ -258,6 +260,11 @@ class Mixin2D(Shape):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def location_at(self, *args: Any, **kwargs: Any) -> Location:
|
||||||
|
"""A location from a face or shell"""
|
||||||
|
pass
|
||||||
|
|
||||||
def offset(self, amount: float) -> Self:
|
def offset(self, amount: float) -> Self:
|
||||||
"""Return a copy of self moved along the normal by amount"""
|
"""Return a copy of self moved along the normal by amount"""
|
||||||
return copy.deepcopy(self).moved(Location(self.normal_at() * amount))
|
return copy.deepcopy(self).moved(Location(self.normal_at() * amount))
|
||||||
|
|
@ -292,7 +299,7 @@ class Mixin2D(Shape):
|
||||||
Raises:
|
Raises:
|
||||||
RuntimeError: wrapping over surface boundary, try difference surface_loc
|
RuntimeError: wrapping over surface boundary, try difference surface_loc
|
||||||
Returns:
|
Returns:
|
||||||
Edge: wraped edge
|
Edge: wrapped edge
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _intersect_surface_normal(
|
def _intersect_surface_normal(
|
||||||
|
|
@ -325,6 +332,9 @@ class Mixin2D(Shape):
|
||||||
world_point, world_point - target_object_center
|
world_point, world_point - target_object_center
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.wrapped is None:
|
||||||
|
raise ValueError("Can't wrap around an empty face")
|
||||||
|
|
||||||
# Initial setup
|
# Initial setup
|
||||||
target_object_center = self.center(CenterOf.BOUNDING_BOX)
|
target_object_center = self.center(CenterOf.BOUNDING_BOX)
|
||||||
|
|
||||||
|
|
@ -338,17 +348,26 @@ class Mixin2D(Shape):
|
||||||
loop_count = 0
|
loop_count = 0
|
||||||
length_error = sys.float_info.max
|
length_error = sys.float_info.max
|
||||||
|
|
||||||
while length_error > tolerance and loop_count < max_loops:
|
# Find the location on the surface to start
|
||||||
# Get starting point and normal
|
if planar_edge.position_at(0).length > tolerance:
|
||||||
surface_origin = surface_loc.position
|
# The start point isn't at the surface_loc so wrap a line to find it
|
||||||
surface_normal = surface_loc.z_axis.direction
|
to_start_edge = Edge.make_line((0, 0), planar_edge @ 0)
|
||||||
|
wrapped_to_start_edge = self._wrap_edge(
|
||||||
|
to_start_edge, surface_loc, snap_to_face=True, tolerance=tolerance
|
||||||
|
)
|
||||||
|
start_pnt = wrapped_to_start_edge @ 1
|
||||||
|
_, start_normal = _intersect_surface_normal(
|
||||||
|
start_pnt, (start_pnt - target_object_center)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# The start point is at the surface location
|
||||||
|
start_pnt = surface_loc.position
|
||||||
|
start_normal = surface_loc.z_axis.direction
|
||||||
|
|
||||||
|
while length_error > tolerance and loop_count < max_loops:
|
||||||
# Seed the wrapped path
|
# Seed the wrapped path
|
||||||
wrapped_edge_points: list[VectorLike] = []
|
wrapped_edge_points: list[VectorLike] = []
|
||||||
planar_position = planar_edge.position_at(0)
|
current_point, current_normal = start_pnt, start_normal
|
||||||
current_point, current_normal = _find_point_on_surface(
|
|
||||||
surface_origin, surface_normal, planar_position
|
|
||||||
)
|
|
||||||
wrapped_edge_points.append(current_point)
|
wrapped_edge_points.append(current_point)
|
||||||
|
|
||||||
# Subdivide and propagate
|
# Subdivide and propagate
|
||||||
|
|
@ -374,8 +393,8 @@ class Mixin2D(Shape):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Length error of {length_error:.6f} exceeds tolerance {tolerance}"
|
f"Length error of {length_error:.6f} exceeds tolerance {tolerance}"
|
||||||
)
|
)
|
||||||
if not wrapped_edge.is_valid():
|
if wrapped_edge.wrapped is None or not wrapped_edge.is_valid:
|
||||||
raise RuntimeError("Wraped edge is invalid")
|
raise RuntimeError("Wrapped edge is invalid")
|
||||||
|
|
||||||
if not snap_to_face:
|
if not snap_to_face:
|
||||||
return wrapped_edge
|
return wrapped_edge
|
||||||
|
|
@ -520,10 +539,12 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.geom_type == GeomType.CYLINDER:
|
if self.geom_type == GeomType.CYLINDER:
|
||||||
return Axis(self.geom_adaptor().Cylinder().Axis())
|
return Axis(
|
||||||
|
self.geom_adaptor().Cylinder().Axis() # type:ignore[attr-defined]
|
||||||
|
)
|
||||||
|
|
||||||
if self.geom_type == GeomType.TORUS:
|
if self.geom_type == GeomType.TORUS:
|
||||||
return Axis(self.geom_adaptor().Torus().Axis())
|
return Axis(self.geom_adaptor().Torus().Axis()) # type:ignore[attr-defined]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -729,7 +750,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
axis = self.axis_of_rotation
|
axis = self.axis_of_rotation
|
||||||
if axis is None or self.radii is None:
|
if axis is None or self.radii is None:
|
||||||
raise ValueError("Can't find curvature of empty object")
|
raise ValueError("Can't find curvature of empty object")
|
||||||
loc = Location(axis.to_plane())
|
loc = Location(Plane(axis))
|
||||||
axis_circle = Edge.make_circle(self.radii[0]).locate(loc)
|
axis_circle = Edge.make_circle(self.radii[0]).locate(loc)
|
||||||
_, pnt_on_axis_circle, _ = axis_circle.distance_to_with_closest_points(
|
_, pnt_on_axis_circle, _ = axis_circle.distance_to_with_closest_points(
|
||||||
self.center()
|
self.center()
|
||||||
|
|
@ -781,8 +802,8 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
"""Return the major and minor radii of a torus otherwise None"""
|
"""Return the major and minor radii of a torus otherwise None"""
|
||||||
if self.geom_type == GeomType.TORUS:
|
if self.geom_type == GeomType.TORUS:
|
||||||
return (
|
return (
|
||||||
self.geom_adaptor().MajorRadius(),
|
self.geom_adaptor().MajorRadius(), # type:ignore[attr-defined]
|
||||||
self.geom_adaptor().MinorRadius(),
|
self.geom_adaptor().MinorRadius(), # type:ignore[attr-defined]
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
@ -794,7 +815,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
self.geom_type in [GeomType.CYLINDER, GeomType.SPHERE]
|
self.geom_type in [GeomType.CYLINDER, GeomType.SPHERE]
|
||||||
and type(self.geom_adaptor()) != Geom_RectangularTrimmedSurface
|
and type(self.geom_adaptor()) != Geom_RectangularTrimmedSurface
|
||||||
):
|
):
|
||||||
return self.geom_adaptor().Radius()
|
return self.geom_adaptor().Radius() # type:ignore[attr-defined]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -832,6 +853,8 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
Returns:
|
Returns:
|
||||||
Face: extruded shape
|
Face: extruded shape
|
||||||
"""
|
"""
|
||||||
|
if obj.wrapped is None:
|
||||||
|
raise ValueError("Can't extrude empty object")
|
||||||
return Face(TopoDS.Face_s(_extrude_topods_shape(obj.wrapped, direction)))
|
return Face(TopoDS.Face_s(_extrude_topods_shape(obj.wrapped, direction)))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -978,11 +1001,13 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
raise ValueError("exterior must be a Wire or list of Edges")
|
raise ValueError("exterior must be a Wire or list of Edges")
|
||||||
|
|
||||||
for edge in outside_edges:
|
for edge in outside_edges:
|
||||||
|
if edge.wrapped is None:
|
||||||
|
raise ValueError("exterior contains empty edges")
|
||||||
surface.Add(edge.wrapped, GeomAbs_C0)
|
surface.Add(edge.wrapped, GeomAbs_C0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
surface.Build()
|
surface.Build()
|
||||||
surface_face = Face(surface.Shape())
|
surface_face = Face(surface.Shape()) # type:ignore[call-overload]
|
||||||
except (
|
except (
|
||||||
Standard_Failure,
|
Standard_Failure,
|
||||||
StdFail_NotDone,
|
StdFail_NotDone,
|
||||||
|
|
@ -994,10 +1019,10 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
) from err
|
) from err
|
||||||
if surface_point_vectors:
|
if surface_point_vectors:
|
||||||
for point in surface_point_vectors:
|
for point in surface_point_vectors:
|
||||||
surface.Add(gp_Pnt(*point.to_tuple()))
|
surface.Add(gp_Pnt(*point))
|
||||||
try:
|
try:
|
||||||
surface.Build()
|
surface.Build()
|
||||||
surface_face = Face(surface.Shape())
|
surface_face = Face(surface.Shape()) # type:ignore[call-overload]
|
||||||
except StdFail_NotDone as err:
|
except StdFail_NotDone as err:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Error building non-planar face with provided surface_points"
|
"Error building non-planar face with provided surface_points"
|
||||||
|
|
@ -1007,6 +1032,8 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
if interior_wires:
|
if interior_wires:
|
||||||
makeface_object = BRepBuilderAPI_MakeFace(surface_face.wrapped)
|
makeface_object = BRepBuilderAPI_MakeFace(surface_face.wrapped)
|
||||||
for wire in interior_wires:
|
for wire in interior_wires:
|
||||||
|
if wire.wrapped is None:
|
||||||
|
raise ValueError("interior_wires contain an empty wire")
|
||||||
makeface_object.Add(wire.wrapped)
|
makeface_object.Add(wire.wrapped)
|
||||||
try:
|
try:
|
||||||
surface_face = Face(makeface_object.Face())
|
surface_face = Face(makeface_object.Face())
|
||||||
|
|
@ -1016,7 +1043,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
surface_face = surface_face.fix()
|
surface_face = surface_face.fix()
|
||||||
if not surface_face.is_valid():
|
if not surface_face.is_valid:
|
||||||
raise RuntimeError("non planar face is invalid")
|
raise RuntimeError("non planar face is invalid")
|
||||||
|
|
||||||
return surface_face
|
return surface_face
|
||||||
|
|
@ -1158,7 +1185,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
return cls(revol_builder.Shape())
|
return cls(revol_builder.Shape()) # type:ignore[call-overload]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sew_faces(cls, faces: Iterable[Face]) -> list[ShapeList[Face]]:
|
def sew_faces(cls, faces: Iterable[Face]) -> list[ShapeList[Face]]:
|
||||||
|
|
@ -1189,7 +1216,8 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
elif isinstance(top_level_shape, TopoDS_Solid):
|
elif isinstance(top_level_shape, TopoDS_Solid):
|
||||||
sewn_faces.append(
|
sewn_faces.append(
|
||||||
ShapeList(
|
ShapeList(
|
||||||
Face(f) for f in _topods_entities(top_level_shape, "Face")
|
Face(f) # type:ignore[call-overload]
|
||||||
|
for f in _topods_entities(top_level_shape, "Face")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
@ -1236,7 +1264,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
builder.Add(profile.wrapped, False, False)
|
builder.Add(profile.wrapped, False, False)
|
||||||
builder.SetTransitionMode(Shape._transModeDict[transition])
|
builder.SetTransitionMode(Shape._transModeDict[transition])
|
||||||
builder.Build()
|
builder.Build()
|
||||||
result = Face(builder.Shape())
|
result = Face(builder.Shape()) # type:ignore[call-overload]
|
||||||
if SkipClean.clean:
|
if SkipClean.clean:
|
||||||
result = result.clean()
|
result = result.clean()
|
||||||
|
|
||||||
|
|
@ -1355,8 +1383,10 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
def inner_wires(self) -> ShapeList[Wire]:
|
def inner_wires(self) -> ShapeList[Wire]:
|
||||||
"""Extract the inner or hole wires from this Face"""
|
"""Extract the inner or hole wires from this Face"""
|
||||||
outer = self.outer_wire()
|
outer = self.outer_wire()
|
||||||
|
inners = [w for w in self.wires() if not w.is_same(outer)]
|
||||||
return ShapeList([w for w in self.wires() if not w.is_same(outer)])
|
for w in inners:
|
||||||
|
w.topo_parent = self if self.topo_parent is None else self.topo_parent
|
||||||
|
return ShapeList(inners)
|
||||||
|
|
||||||
def is_coplanar(self, plane: Plane) -> bool:
|
def is_coplanar(self, plane: Plane) -> bool:
|
||||||
"""Is this planar face coplanar with the provided plane"""
|
"""Is this planar face coplanar with the provided plane"""
|
||||||
|
|
@ -1387,23 +1417,112 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
solid_classifier = BRepClass3d_SolidClassifier(self.wrapped)
|
solid_classifier = BRepClass3d_SolidClassifier(self.wrapped)
|
||||||
solid_classifier.Perform(gp_Pnt(*Vector(point).to_tuple()), tolerance)
|
solid_classifier.Perform(gp_Pnt(*Vector(point)), tolerance)
|
||||||
return solid_classifier.IsOnAFace()
|
return solid_classifier.IsOnAFace()
|
||||||
|
|
||||||
# surface = BRep_Tool.Surface_s(self.wrapped)
|
# surface = BRep_Tool.Surface_s(self.wrapped)
|
||||||
# projector = GeomAPI_ProjectPointOnSurf(Vector(point).to_pnt(), surface)
|
# projector = GeomAPI_ProjectPointOnSurf(Vector(point).to_pnt(), surface)
|
||||||
# return projector.LowerDistance() <= TOLERANCE
|
# return projector.LowerDistance() <= TOLERANCE
|
||||||
|
|
||||||
|
@overload
|
||||||
def location_at(
|
def location_at(
|
||||||
self, u: float, v: float, x_dir: VectorLike | None = None
|
self,
|
||||||
) -> Location:
|
surface_point: VectorLike | None = None,
|
||||||
"""Location at the u/v position of face"""
|
*,
|
||||||
origin = self.position_at(u, v)
|
x_dir: VectorLike | None = None,
|
||||||
if x_dir is None:
|
) -> Location: ...
|
||||||
pln = Plane(origin, z_dir=self.normal_at(u, v))
|
|
||||||
|
@overload
|
||||||
|
def location_at(
|
||||||
|
self, u: float, v: float, *, x_dir: VectorLike | None = None
|
||||||
|
) -> Location: ...
|
||||||
|
|
||||||
|
def location_at(self, *args, **kwargs) -> Location:
|
||||||
|
"""location_at
|
||||||
|
|
||||||
|
Get the location (origin and orientation) on the surface of the face.
|
||||||
|
|
||||||
|
This method supports two overloads:
|
||||||
|
|
||||||
|
1. `location_at(u: float, v: float, *, x_dir: VectorLike | None = None) -> Location`
|
||||||
|
- Specifies the point in normalized UV parameter space of the face.
|
||||||
|
- `u` and `v` are floats between 0.0 and 1.0.
|
||||||
|
- Optionally override the local X direction using `x_dir`.
|
||||||
|
|
||||||
|
2. `location_at(surface_point: VectorLike, *, x_dir: VectorLike | None = None) -> Location`
|
||||||
|
- Projects the given 3D point onto the face surface.
|
||||||
|
- The point must be reasonably close to the face.
|
||||||
|
- Optionally override the local X direction using `x_dir`.
|
||||||
|
|
||||||
|
If no arguments are provided, the location at the center of the face
|
||||||
|
(u=0.5, v=0.5) is returned.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
u (float): Normalized horizontal surface parameter (optional).
|
||||||
|
v (float): Normalized vertical surface parameter (optional).
|
||||||
|
surface_point (VectorLike): A 3D point near the surface (optional).
|
||||||
|
x_dir (VectorLike, optional): Direction for the local X axis. If not given,
|
||||||
|
the tangent in the U direction is used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Location: A full 3D placement at the specified point on the face surface.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If only one of `u` or `v` is provided or invalid keyword args are passed.
|
||||||
|
"""
|
||||||
|
surface_point, u, v = None, -1.0, -1.0
|
||||||
|
|
||||||
|
if args:
|
||||||
|
if isinstance(args[0], (Vector, Sequence)):
|
||||||
|
surface_point = args[0]
|
||||||
|
elif isinstance(args[0], (int, float)):
|
||||||
|
u = args[0]
|
||||||
|
if len(args) == 2 and isinstance(args[1], (int, float)):
|
||||||
|
v = args[1]
|
||||||
|
|
||||||
|
unknown_args = set(kwargs.keys()).difference(
|
||||||
|
{"surface_point", "u", "v", "x_dir"}
|
||||||
|
)
|
||||||
|
if unknown_args:
|
||||||
|
raise ValueError(f"Unexpected argument(s) {', '.join(unknown_args)}")
|
||||||
|
|
||||||
|
surface_point = kwargs.get("surface_point", surface_point)
|
||||||
|
u = kwargs.get("u", u)
|
||||||
|
v = kwargs.get("v", v)
|
||||||
|
user_x_dir = kwargs.get("x_dir", None)
|
||||||
|
|
||||||
|
if surface_point is None and u < 0 and v < 0:
|
||||||
|
u, v = 0.5, 0.5
|
||||||
|
elif surface_point is None and (u < 0 or v < 0):
|
||||||
|
raise ValueError("Both u & v values must be specified")
|
||||||
|
|
||||||
|
geom_surface: Geom_Surface = self.geom_adaptor()
|
||||||
|
u_min, u_max, v_min, v_max = self._uv_bounds()
|
||||||
|
|
||||||
|
if surface_point is None:
|
||||||
|
u_val = u_min + u * (u_max - u_min)
|
||||||
|
v_val = v_min + v * (v_max - v_min)
|
||||||
else:
|
else:
|
||||||
pln = Plane(origin, x_dir=Vector(x_dir), z_dir=self.normal_at(u, v))
|
projector = GeomAPI_ProjectPointOnSurf(
|
||||||
return Location(pln)
|
Vector(surface_point).to_pnt(), geom_surface
|
||||||
|
)
|
||||||
|
u_val, v_val = projector.LowerDistanceParameters()
|
||||||
|
|
||||||
|
# Evaluate point and partials
|
||||||
|
pnt = gp_Pnt()
|
||||||
|
du = gp_Vec()
|
||||||
|
dv = gp_Vec()
|
||||||
|
geom_surface.D1(u_val, v_val, pnt, du, dv)
|
||||||
|
|
||||||
|
origin = Vector(pnt)
|
||||||
|
z_dir = Vector(du).cross(Vector(dv)).normalized()
|
||||||
|
x_dir = (
|
||||||
|
Vector(user_x_dir).normalized()
|
||||||
|
if user_x_dir is not None
|
||||||
|
else Vector(du).normalized()
|
||||||
|
)
|
||||||
|
|
||||||
|
return Location(Plane(origin=origin, x_dir=x_dir, z_dir=z_dir))
|
||||||
|
|
||||||
def make_holes(self, interior_wires: list[Wire]) -> Face:
|
def make_holes(self, interior_wires: list[Wire]) -> Face:
|
||||||
"""Make Holes in Face
|
"""Make Holes in Face
|
||||||
|
|
@ -1443,7 +1562,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
surface_face = surface_face.fix()
|
surface_face = surface_face.fix()
|
||||||
# if not surface_face.is_valid():
|
# if not surface_face.is_valid:
|
||||||
# raise RuntimeError("non planar face is invalid")
|
# raise RuntimeError("non planar face is invalid")
|
||||||
|
|
||||||
return surface_face
|
return surface_face
|
||||||
|
|
@ -1537,7 +1656,9 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
|
|
||||||
def outer_wire(self) -> Wire:
|
def outer_wire(self) -> Wire:
|
||||||
"""Extract the perimeter wire from this Face"""
|
"""Extract the perimeter wire from this Face"""
|
||||||
return Wire(BRepTools.OuterWire_s(self.wrapped))
|
outer = Wire(BRepTools.OuterWire_s(self.wrapped))
|
||||||
|
outer.topo_parent = self if self.topo_parent is None else self.topo_parent
|
||||||
|
return outer
|
||||||
|
|
||||||
def position_at(self, u: float, v: float) -> Vector:
|
def position_at(self, u: float, v: float) -> Vector:
|
||||||
"""position_at
|
"""position_at
|
||||||
|
|
@ -1600,7 +1721,9 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
(extruded_topods_self,), (target_object.wrapped,), BRepAlgoAPI_Common()
|
(extruded_topods_self,), (target_object.wrapped,), BRepAlgoAPI_Common()
|
||||||
)
|
)
|
||||||
if not topods_shape.IsNull():
|
if not topods_shape.IsNull():
|
||||||
intersected_shapes.append(Face(topods_shape))
|
intersected_shapes.append(
|
||||||
|
Face(topods_shape) # type:ignore[call-overload]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for target_shell in target_object.shells():
|
for target_shell in target_object.shells():
|
||||||
topods_shape = _topods_bool_op(
|
topods_shape = _topods_bool_op(
|
||||||
|
|
@ -1627,12 +1750,21 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
|
|
||||||
Approximate planar face with arcs and straight line segments.
|
Approximate planar face with arcs and straight line segments.
|
||||||
|
|
||||||
|
This is a utility used internally to convert or adapt a face for Boolean operations. Its
|
||||||
|
purpose is not typically for general use, but rather as a helper within the Boolean kernel
|
||||||
|
to ensure input faces are in a compatible and canonical form.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tolerance (float, optional): Approximation tolerance. Defaults to 1e-3.
|
tolerance (float, optional): Approximation tolerance. Defaults to 1e-3.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Face: approximated face
|
Face: approximated face
|
||||||
"""
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"The 'to_arcs' method is deprecated and will be removed in a future version.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
if self.wrapped is None:
|
if self.wrapped is None:
|
||||||
raise ValueError("Cannot approximate an empty shape")
|
raise ValueError("Cannot approximate an empty shape")
|
||||||
|
|
||||||
|
|
@ -1754,6 +1886,71 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
f"{type(planar_shape)}"
|
f"{type(planar_shape)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def wrap_faces(
|
||||||
|
self,
|
||||||
|
faces: Iterable[Face],
|
||||||
|
path: Wire | Edge,
|
||||||
|
start: float = 0.0,
|
||||||
|
) -> ShapeList[Face]:
|
||||||
|
"""wrap_faces
|
||||||
|
|
||||||
|
Wrap a sequence of 2D faces onto a 3D surface, aligned along a guiding path.
|
||||||
|
|
||||||
|
This method places multiple planar `Face` objects (defined in the XY plane) onto a
|
||||||
|
curved 3D surface (`self`), following a given path (Wire or Edge) that lies on or
|
||||||
|
closely follows the surface. Each face is spaced along the path according to its
|
||||||
|
original horizontal (X-axis) position, preserving the relative layout of the input
|
||||||
|
faces.
|
||||||
|
|
||||||
|
The wrapping process attempts to maintain the shape and size of each face while
|
||||||
|
minimizing distortion. Each face is repositioned to the origin, then individually
|
||||||
|
wrapped onto the surface starting at a specific point along the path. The face's
|
||||||
|
new orientation is defined using the path's tangent direction and the surface normal
|
||||||
|
at that point.
|
||||||
|
|
||||||
|
This is particularly useful for placing a series of features—such as embossed logos,
|
||||||
|
engraved labels, or patterned tiles—onto a freeform or cylindrical surface, aligned
|
||||||
|
along a reference edge or curve.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
faces (Iterable[Face]): An iterable of 2D planar faces to be wrapped.
|
||||||
|
path (Wire | Edge): A curve on the target surface that defines the alignment
|
||||||
|
direction. The X-position of each face is mapped to a relative position
|
||||||
|
along this path.
|
||||||
|
start (float, optional): The relative starting point on the path (between 0.0
|
||||||
|
and 1.0) where the first face should be placed. Defaults to 0.0.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ShapeList[Face]: A list of wrapped face objects, aligned and conformed to the
|
||||||
|
surface.
|
||||||
|
"""
|
||||||
|
path_length = path.length
|
||||||
|
|
||||||
|
face_list = list(faces)
|
||||||
|
first_face_min_x = face_list[0].bounding_box().min.X
|
||||||
|
|
||||||
|
# Position each face at the origin and wrap onto surface
|
||||||
|
wrapped_faces: ShapeList[Face] = ShapeList()
|
||||||
|
for face in face_list:
|
||||||
|
bbox = face.bounding_box()
|
||||||
|
face_center_x = (bbox.min.X + bbox.max.X) / 2
|
||||||
|
delta_x = face_center_x - first_face_min_x
|
||||||
|
relative_position_on_wire = start + delta_x / path_length
|
||||||
|
path_position = path.position_at(relative_position_on_wire)
|
||||||
|
surface_location = Location(
|
||||||
|
Plane(
|
||||||
|
path_position,
|
||||||
|
x_dir=path.tangent_at(relative_position_on_wire),
|
||||||
|
z_dir=self.normal_at(path_position),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert isinstance(face.position, Vector)
|
||||||
|
face.position -= (delta_x, 0, 0) # Shift back to origin
|
||||||
|
wrapped_face = Face.wrap(self, face, surface_location)
|
||||||
|
wrapped_faces.append(wrapped_face)
|
||||||
|
|
||||||
|
return wrapped_faces
|
||||||
|
|
||||||
def _uv_bounds(self) -> tuple[float, float, float, float]:
|
def _uv_bounds(self) -> tuple[float, float, float, float]:
|
||||||
"""Return the u min, u max, v min, v max values"""
|
"""Return the u min, u max, v min, v max values"""
|
||||||
return BRepTools.UVBounds_s(self.wrapped)
|
return BRepTools.UVBounds_s(self.wrapped)
|
||||||
|
|
@ -1787,7 +1984,9 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
for w in planar_face.inner_wires()
|
for w in planar_face.inner_wires()
|
||||||
]
|
]
|
||||||
wrapped_face = Face.make_surface(
|
wrapped_face = Face.make_surface(
|
||||||
wrapped_perimeter, interior_wires=wrapped_holes
|
wrapped_perimeter,
|
||||||
|
surface_points=[surface_loc.position],
|
||||||
|
interior_wires=wrapped_holes,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Potentially flip the wrapped face to match the surface
|
# Potentially flip the wrapped face to match the surface
|
||||||
|
|
@ -1889,7 +2088,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
return Wire(wrapped_edges)
|
return Wire(wrapped_edges)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Part 3: The first and last edges likey don't meet at this point due to
|
# Part 3: The first and last edges likely don't meet at this point due to
|
||||||
# distortion caused by following the surface, so we'll need to join
|
# distortion caused by following the surface, so we'll need to join
|
||||||
# them.
|
# them.
|
||||||
#
|
#
|
||||||
|
|
@ -1947,7 +2146,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
#
|
#
|
||||||
# Part 5: Validate
|
# Part 5: Validate
|
||||||
#
|
#
|
||||||
if not wrapped_wire.is_valid():
|
if not wrapped_wire.is_valid:
|
||||||
raise RuntimeError("wrapped wire is not valid")
|
raise RuntimeError("wrapped wire is not valid")
|
||||||
|
|
||||||
return wrapped_wire
|
return wrapped_wire
|
||||||
|
|
@ -2121,6 +2320,28 @@ class Shell(Mixin2D, Shape[TopoDS_Shell]):
|
||||||
BRepGProp.LinearProperties_s(self.wrapped, properties)
|
BRepGProp.LinearProperties_s(self.wrapped, properties)
|
||||||
return Vector(properties.CentreOfMass())
|
return Vector(properties.CentreOfMass())
|
||||||
|
|
||||||
|
def location_at(
|
||||||
|
self,
|
||||||
|
surface_point: VectorLike,
|
||||||
|
*,
|
||||||
|
x_dir: VectorLike | None = None,
|
||||||
|
) -> Location:
|
||||||
|
"""location_at
|
||||||
|
|
||||||
|
Get the location (origin and orientation) on the surface of the shell.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
surface_point (VectorLike): A 3D point near the surface.
|
||||||
|
x_dir (VectorLike, optional): Direction for the local X axis. If not given,
|
||||||
|
the tangent in the U direction is used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Location: A full 3D placement at the specified point on the shell surface.
|
||||||
|
"""
|
||||||
|
# Find the closest Face and get the location from it
|
||||||
|
face = self.faces().sort_by(lambda f: f.distance_to(surface_point))[0]
|
||||||
|
return face.location_at(surface_point, x_dir=x_dir)
|
||||||
|
|
||||||
|
|
||||||
def sort_wires_by_build_order(wire_list: list[Wire]) -> list[list[Wire]]:
|
def sort_wires_by_build_order(wire_list: list[Wire]) -> list[list[Wire]]:
|
||||||
"""Tries to determine how wires should be combined into faces.
|
"""Tries to determine how wires should be combined into faces.
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ license:
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import warnings
|
||||||
|
|
||||||
from typing import overload, TYPE_CHECKING
|
from typing import overload, TYPE_CHECKING
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
|
@ -132,7 +134,8 @@ class Vertex(Shape[TopoDS_Vertex]):
|
||||||
)
|
)
|
||||||
|
|
||||||
super().__init__(ocp_vx)
|
super().__init__(ocp_vx)
|
||||||
self.X, self.Y, self.Z = self.to_tuple()
|
pnt = BRep_Tool.Pnt_s(self.wrapped)
|
||||||
|
self.X, self.Y, self.Z = pnt.X(), pnt.Y(), pnt.Z()
|
||||||
|
|
||||||
# ---- Properties ----
|
# ---- Properties ----
|
||||||
|
|
||||||
|
|
@ -239,7 +242,7 @@ class Vertex(Shape[TopoDS_Vertex]):
|
||||||
def __sub__(self, other: Vertex | Vector | tuple) -> Vertex: # type: ignore
|
def __sub__(self, other: Vertex | Vector | tuple) -> Vertex: # type: ignore
|
||||||
"""Subtract
|
"""Subtract
|
||||||
|
|
||||||
Substract a Vertex with a Vertex, Vector or Tuple from self
|
Subtract a Vertex with a Vertex, Vector or Tuple from self
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
other: Value to add
|
other: Value to add
|
||||||
|
|
@ -272,6 +275,12 @@ class Vertex(Shape[TopoDS_Vertex]):
|
||||||
|
|
||||||
def to_tuple(self) -> tuple[float, float, float]:
|
def to_tuple(self) -> tuple[float, float, float]:
|
||||||
"""Return vertex as three tuple of floats"""
|
"""Return vertex as three tuple of floats"""
|
||||||
|
warnings.warn(
|
||||||
|
"to_tuple is deprecated and will be removed in a future version. "
|
||||||
|
"Use 'tuple(Vertex)' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
geom_point = BRep_Tool.Pnt_s(self.wrapped)
|
geom_point = BRep_Tool.Pnt_s(self.wrapped)
|
||||||
return (geom_point.X(), geom_point.Y(), geom_point.Z())
|
return (geom_point.X(), geom_point.Y(), geom_point.Z())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -237,18 +237,16 @@ class TestCommonOperations(unittest.TestCase):
|
||||||
|
|
||||||
def test_matmul(self):
|
def test_matmul(self):
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(Edge.make_line((0, 0, 0), (1, 1, 1)) @ 0.5).to_tuple(), (0.5, 0.5, 0.5), 5
|
Edge.make_line((0, 0, 0), (1, 1, 1)) @ 0.5, (0.5, 0.5, 0.5), 5
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_mod(self):
|
def test_mod(self):
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(Wire.make_circle(10) % 0.5, (0, -1, 0), 5)
|
||||||
(Wire.make_circle(10) % 0.5).to_tuple(), (0, -1, 0), 5
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_xor(self):
|
def test_xor(self):
|
||||||
helix_loc = Edge.make_helix(2 * pi, 1, 1) ^ 0
|
helix_loc = Edge.make_helix(2 * pi, 1, 1) ^ 0
|
||||||
self.assertTupleAlmostEquals(helix_loc.position.to_tuple(), (1, 0, 0), 5)
|
self.assertTupleAlmostEquals(helix_loc.position, (1, 0, 0), 5)
|
||||||
self.assertTupleAlmostEquals(helix_loc.orientation.to_tuple(), (-45, 0, 180), 5)
|
self.assertTupleAlmostEquals(helix_loc.orientation, (-45, 0, 180), 5)
|
||||||
|
|
||||||
|
|
||||||
class TestLocations(unittest.TestCase):
|
class TestLocations(unittest.TestCase):
|
||||||
|
|
@ -256,11 +254,11 @@ class TestLocations(unittest.TestCase):
|
||||||
locs = PolarLocations(1, 5, 45, 90, False).local_locations
|
locs = PolarLocations(1, 5, 45, 90, False).local_locations
|
||||||
for i, angle in enumerate(range(45, 135, 18)):
|
for i, angle in enumerate(range(45, 135, 18)):
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
locs[i].position.to_tuple(),
|
locs[i].position,
|
||||||
Vector(1, 0).rotate(Axis.Z, angle).to_tuple(),
|
Vector(1, 0).rotate(Axis.Z, angle),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals(locs[i].orientation.to_tuple(), (0, 0, 0), 5)
|
self.assertTupleAlmostEquals(locs[i].orientation, (0, 0, 0), 5)
|
||||||
|
|
||||||
def test_polar_endpoint(self):
|
def test_polar_endpoint(self):
|
||||||
locs = PolarLocations(
|
locs = PolarLocations(
|
||||||
|
|
@ -284,7 +282,7 @@ class TestLocations(unittest.TestCase):
|
||||||
def test_no_centering(self):
|
def test_no_centering(self):
|
||||||
with BuildSketch():
|
with BuildSketch():
|
||||||
with GridLocations(4, 4, 2, 2, align=(Align.MIN, Align.MIN)) as l:
|
with GridLocations(4, 4, 2, 2, align=(Align.MIN, Align.MIN)) as l:
|
||||||
pts = [loc.to_tuple()[0] for loc in l.locations]
|
pts = [tuple(loc)[0] for loc in l.locations]
|
||||||
self.assertTupleAlmostEquals(pts[0], (0, 0, 0), 5)
|
self.assertTupleAlmostEquals(pts[0], (0, 0, 0), 5)
|
||||||
self.assertTupleAlmostEquals(pts[1], (0, 4, 0), 5)
|
self.assertTupleAlmostEquals(pts[1], (0, 4, 0), 5)
|
||||||
self.assertTupleAlmostEquals(pts[2], (4, 0, 0), 5)
|
self.assertTupleAlmostEquals(pts[2], (4, 0, 0), 5)
|
||||||
|
|
@ -333,7 +331,7 @@ class TestLocations(unittest.TestCase):
|
||||||
def test_centering(self):
|
def test_centering(self):
|
||||||
with BuildSketch():
|
with BuildSketch():
|
||||||
with GridLocations(4, 4, 2, 2, align=(Align.CENTER, Align.CENTER)) as l:
|
with GridLocations(4, 4, 2, 2, align=(Align.CENTER, Align.CENTER)) as l:
|
||||||
pts = [loc.to_tuple()[0] for loc in l.locations]
|
pts = [tuple(loc)[0] for loc in l.locations]
|
||||||
self.assertTupleAlmostEquals(pts[0], (-2, -2, 0), 5)
|
self.assertTupleAlmostEquals(pts[0], (-2, -2, 0), 5)
|
||||||
self.assertTupleAlmostEquals(pts[1], (-2, 2, 0), 5)
|
self.assertTupleAlmostEquals(pts[1], (-2, 2, 0), 5)
|
||||||
self.assertTupleAlmostEquals(pts[2], (2, -2, 0), 5)
|
self.assertTupleAlmostEquals(pts[2], (2, -2, 0), 5)
|
||||||
|
|
@ -343,7 +341,7 @@ class TestLocations(unittest.TestCase):
|
||||||
with BuildSketch():
|
with BuildSketch():
|
||||||
with Locations((-2, -2), (2, 2)):
|
with Locations((-2, -2), (2, 2)):
|
||||||
with GridLocations(1, 1, 2, 2) as nested_grid:
|
with GridLocations(1, 1, 2, 2) as nested_grid:
|
||||||
pts = [loc.to_tuple()[0] for loc in nested_grid.local_locations]
|
pts = [tuple(loc)[0] for loc in nested_grid.local_locations]
|
||||||
self.assertTupleAlmostEquals(pts[0], (-2.50, -2.50, 0.00), 5)
|
self.assertTupleAlmostEquals(pts[0], (-2.50, -2.50, 0.00), 5)
|
||||||
self.assertTupleAlmostEquals(pts[1], (-2.50, -1.50, 0.00), 5)
|
self.assertTupleAlmostEquals(pts[1], (-2.50, -1.50, 0.00), 5)
|
||||||
self.assertTupleAlmostEquals(pts[2], (-1.50, -2.50, 0.00), 5)
|
self.assertTupleAlmostEquals(pts[2], (-1.50, -2.50, 0.00), 5)
|
||||||
|
|
@ -357,8 +355,8 @@ class TestLocations(unittest.TestCase):
|
||||||
with BuildSketch():
|
with BuildSketch():
|
||||||
with PolarLocations(6, 3):
|
with PolarLocations(6, 3):
|
||||||
with GridLocations(1, 1, 2, 2) as polar_grid:
|
with GridLocations(1, 1, 2, 2) as polar_grid:
|
||||||
pts = [loc.to_tuple()[0] for loc in polar_grid.local_locations]
|
pts = [tuple(loc)[0] for loc in polar_grid.local_locations]
|
||||||
ort = [loc.to_tuple()[1] for loc in polar_grid.local_locations]
|
ort = [tuple(loc)[1] for loc in polar_grid.local_locations]
|
||||||
|
|
||||||
self.assertTupleAlmostEquals(pts[0], (5.50, -0.50, 0.00), 2)
|
self.assertTupleAlmostEquals(pts[0], (5.50, -0.50, 0.00), 2)
|
||||||
self.assertTupleAlmostEquals(pts[1], (5.50, 0.50, 0.00), 2)
|
self.assertTupleAlmostEquals(pts[1], (5.50, 0.50, 0.00), 2)
|
||||||
|
|
@ -390,22 +388,18 @@ class TestLocations(unittest.TestCase):
|
||||||
square = Face.make_rect(1, 1, Plane.XZ)
|
square = Face.make_rect(1, 1, Plane.XZ)
|
||||||
with BuildPart():
|
with BuildPart():
|
||||||
loc = Locations(square).locations[0]
|
loc = Locations(square).locations[0]
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(loc.position, Location(Plane.XZ).position, 5)
|
||||||
loc.position.to_tuple(), Location(Plane.XZ).position.to_tuple(), 5
|
self.assertTupleAlmostEquals(loc.orientation, Location(Plane.XZ).orientation, 5)
|
||||||
)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
loc.orientation.to_tuple(), Location(Plane.XZ).orientation.to_tuple(), 5
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_from_plane(self):
|
def test_from_plane(self):
|
||||||
with BuildPart():
|
with BuildPart():
|
||||||
loc = Locations(Plane.XY.offset(1)).locations[0]
|
loc = Locations(Plane.XY.offset(1)).locations[0]
|
||||||
self.assertTupleAlmostEquals(loc.position.to_tuple(), (0, 0, 1), 5)
|
self.assertTupleAlmostEquals(loc.position, (0, 0, 1), 5)
|
||||||
|
|
||||||
def test_from_axis(self):
|
def test_from_axis(self):
|
||||||
with BuildPart():
|
with BuildPart():
|
||||||
loc = Locations(Axis((1, 1, 1), (0, 0, 1))).locations[0]
|
loc = Locations(Axis((1, 1, 1), (0, 0, 1))).locations[0]
|
||||||
self.assertTupleAlmostEquals(loc.position.to_tuple(), (1, 1, 1), 5)
|
self.assertTupleAlmostEquals(loc.position, (1, 1, 1), 5)
|
||||||
|
|
||||||
def test_multiplication(self):
|
def test_multiplication(self):
|
||||||
circles = GridLocations(2, 2, 2, 2) * Circle(1)
|
circles = GridLocations(2, 2, 2, 2) * Circle(1)
|
||||||
|
|
@ -416,25 +410,17 @@ class TestLocations(unittest.TestCase):
|
||||||
|
|
||||||
def test_grid_attributes(self):
|
def test_grid_attributes(self):
|
||||||
grid = GridLocations(5, 10, 3, 4)
|
grid = GridLocations(5, 10, 3, 4)
|
||||||
self.assertTupleAlmostEquals(grid.size.to_tuple(), (10, 30, 0), 5)
|
self.assertTupleAlmostEquals(grid.size, (10, 30, 0), 5)
|
||||||
self.assertTupleAlmostEquals(grid.min.to_tuple(), (-5, -15, 0), 5)
|
self.assertTupleAlmostEquals(grid.min, (-5, -15, 0), 5)
|
||||||
self.assertTupleAlmostEquals(grid.max.to_tuple(), (5, 15, 0), 5)
|
self.assertTupleAlmostEquals(grid.max, (5, 15, 0), 5)
|
||||||
|
|
||||||
def test_mixed_sequence_list(self):
|
def test_mixed_sequence_list(self):
|
||||||
locs = Locations((0, 1), [(2, 3), (4, 5)], (6, 7))
|
locs = Locations((0, 1), [(2, 3), (4, 5)], (6, 7))
|
||||||
self.assertEqual(len(locs.locations), 4)
|
self.assertEqual(len(locs.locations), 4)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(locs.locations[0].position, (0, 1, 0), 5)
|
||||||
locs.locations[0].position.to_tuple(), (0, 1, 0), 5
|
self.assertTupleAlmostEquals(locs.locations[1].position, (2, 3, 0), 5)
|
||||||
)
|
self.assertTupleAlmostEquals(locs.locations[2].position, (4, 5, 0), 5)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(locs.locations[3].position, (6, 7, 0), 5)
|
||||||
locs.locations[1].position.to_tuple(), (2, 3, 0), 5
|
|
||||||
)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
locs.locations[2].position.to_tuple(), (4, 5, 0), 5
|
|
||||||
)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
locs.locations[3].position.to_tuple(), (6, 7, 0), 5
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestProperties(unittest.TestCase):
|
class TestProperties(unittest.TestCase):
|
||||||
|
|
@ -449,27 +435,25 @@ class TestRotation(unittest.TestCase):
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
thirty_by_three = Rotation(30, 30, 30)
|
thirty_by_three = Rotation(30, 30, 30)
|
||||||
box_vertices = Solid.make_box(1, 1, 1).moved(thirty_by_three).vertices()
|
box_vertices = Solid.make_box(1, 1, 1).moved(thirty_by_three).vertices()
|
||||||
|
self.assertTupleAlmostEquals(tuple(box_vertices[0]), (0.5, -0.4330127, 0.75), 5)
|
||||||
|
self.assertTupleAlmostEquals(tuple(box_vertices[1]), (0.0, 0.0, 0.0), 7)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
box_vertices[0].to_tuple(), (0.5, -0.4330127, 0.75), 5
|
tuple(box_vertices[2]), (0.0669872, 0.191987, 1.399519), 5
|
||||||
)
|
|
||||||
self.assertTupleAlmostEquals(box_vertices[1].to_tuple(), (0.0, 0.0, 0.0), 7)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
box_vertices[2].to_tuple(), (0.0669872, 0.191987, 1.399519), 5
|
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
box_vertices[3].to_tuple(), (-0.4330127, 0.625, 0.6495190), 5
|
tuple(box_vertices[3]), (-0.4330127, 0.625, 0.6495190), 5
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
box_vertices[4].to_tuple(), (1.25, 0.2165063, 0.625), 5
|
tuple(box_vertices[4]), (1.25, 0.2165063, 0.625), 5
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
box_vertices[5].to_tuple(), (0.75, 0.649519, -0.125), 5
|
tuple(box_vertices[5]), (0.75, 0.649519, -0.125), 5
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
box_vertices[6].to_tuple(), (0.816987, 0.841506, 1.274519), 5
|
tuple(box_vertices[6]), (0.816987, 0.841506, 1.274519), 5
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
box_vertices[7].to_tuple(), (0.3169872, 1.2745190, 0.52451905), 5
|
tuple(box_vertices[7]), (0.3169872, 1.2745190, 0.52451905), 5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -706,7 +690,7 @@ class TestShapeList(unittest.TestCase):
|
||||||
def test_shapes(self):
|
def test_shapes(self):
|
||||||
with BuildPart() as test:
|
with BuildPart() as test:
|
||||||
Box(1, 1, 1)
|
Box(1, 1, 1)
|
||||||
self.assertIsNone(test._shapes(Compound))
|
self.assertEqual(test._shapes(Compound), [])
|
||||||
|
|
||||||
def test_operators(self):
|
def test_operators(self):
|
||||||
with BuildPart() as test:
|
with BuildPart() as test:
|
||||||
|
|
@ -744,12 +728,12 @@ class TestValidateInputs(unittest.TestCase):
|
||||||
class TestVectorExtensions(unittest.TestCase):
|
class TestVectorExtensions(unittest.TestCase):
|
||||||
def test_vector_localization(self):
|
def test_vector_localization(self):
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(Vector(1, 1, 1) + (1, 2)).to_tuple(),
|
(Vector(1, 1, 1) + (1, 2)),
|
||||||
(2, 3, 1),
|
(2, 3, 1),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(Vector(3, 3, 3) - (1, 2)).to_tuple(),
|
(Vector(3, 3, 3) - (1, 2)),
|
||||||
(2, 1, 3),
|
(2, 1, 3),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
|
|
@ -759,16 +743,14 @@ class TestVectorExtensions(unittest.TestCase):
|
||||||
Vector(1, 2, 3) - "four"
|
Vector(1, 2, 3) - "four"
|
||||||
|
|
||||||
with BuildLine(Plane.YZ):
|
with BuildLine(Plane.YZ):
|
||||||
|
self.assertTupleAlmostEquals(WorkplaneList.localize((1, 2)), (0, 1, 2), 5)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
WorkplaneList.localize((1, 2)).to_tuple(), (0, 1, 2), 5
|
WorkplaneList.localize(Vector(1, 1, 1) + (1, 2)),
|
||||||
)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
WorkplaneList.localize(Vector(1, 1, 1) + (1, 2)).to_tuple(),
|
|
||||||
(1, 2, 3),
|
(1, 2, 3),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
WorkplaneList.localize(Vector(3, 3, 3) - (1, 2)).to_tuple(),
|
WorkplaneList.localize(Vector(3, 3, 3) - (1, 2)),
|
||||||
(3, 2, 1),
|
(3, 2, 1),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
|
|
@ -780,7 +762,7 @@ class TestVectorExtensions(unittest.TestCase):
|
||||||
with BuildLine(pln):
|
with BuildLine(pln):
|
||||||
n3 = Line((-50, -40), (0, 0))
|
n3 = Line((-50, -40), (0, 0))
|
||||||
n4 = Line(n3 @ 1, n3 @ 1 + (0, 10))
|
n4 = Line(n3 @ 1, n3 @ 1 + (0, 10))
|
||||||
self.assertTupleAlmostEquals((n4 @ 1).to_tuple(), (0, 0, -25), 5)
|
self.assertTupleAlmostEquals((n4 @ 1), (0, 0, -25), 5)
|
||||||
|
|
||||||
|
|
||||||
class TestWorkplaneList(unittest.TestCase):
|
class TestWorkplaneList(unittest.TestCase):
|
||||||
|
|
@ -794,8 +776,8 @@ class TestWorkplaneList(unittest.TestCase):
|
||||||
def test_localize(self):
|
def test_localize(self):
|
||||||
with BuildLine(Plane.YZ):
|
with BuildLine(Plane.YZ):
|
||||||
pnts = WorkplaneList.localize((1, 2), (2, 3))
|
pnts = WorkplaneList.localize((1, 2), (2, 3))
|
||||||
self.assertTupleAlmostEquals(pnts[0].to_tuple(), (0, 1, 2), 5)
|
self.assertTupleAlmostEquals(pnts[0], (0, 1, 2), 5)
|
||||||
self.assertTupleAlmostEquals(pnts[1].to_tuple(), (0, 2, 3), 5)
|
self.assertTupleAlmostEquals(pnts[1], (0, 2, 3), 5)
|
||||||
|
|
||||||
def test_invalid_workplane(self):
|
def test_invalid_workplane(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class AddTests(unittest.TestCase):
|
||||||
# Add Edge
|
# Add Edge
|
||||||
with BuildLine() as test:
|
with BuildLine() as test:
|
||||||
add(Edge.make_line((0, 0, 0), (1, 1, 1)))
|
add(Edge.make_line((0, 0, 0), (1, 1, 1)))
|
||||||
self.assertTupleAlmostEquals((test.wires()[0] @ 1).to_tuple(), (1, 1, 1), 5)
|
self.assertTupleAlmostEquals(test.wires()[0] @ 1, (1, 1, 1), 5)
|
||||||
# Add Wire
|
# Add Wire
|
||||||
with BuildLine() as wire:
|
with BuildLine() as wire:
|
||||||
Polyline((0, 0, 0), (1, 1, 1), (2, 0, 0), (3, 1, 1))
|
Polyline((0, 0, 0), (1, 1, 1), (2, 0, 0), (3, 1, 1))
|
||||||
|
|
@ -94,13 +94,11 @@ class AddTests(unittest.TestCase):
|
||||||
add(Solid.make_box(10, 10, 10), rotation=(0, 0, 45))
|
add(Solid.make_box(10, 10, 10), rotation=(0, 0, 45))
|
||||||
self.assertAlmostEqual(test.part.volume, 1000, 5)
|
self.assertAlmostEqual(test.part.volume, 1000, 5)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(
|
|
||||||
test.part.edges()
|
test.part.edges()
|
||||||
.group_by(Axis.Z)[-1]
|
.group_by(Axis.Z)[-1]
|
||||||
.group_by(Axis.X)[-1]
|
.group_by(Axis.X)[-1]
|
||||||
.sort_by(Axis.Y)[0]
|
.sort_by(Axis.Y)[0]
|
||||||
% 1
|
% 1,
|
||||||
).to_tuple(),
|
|
||||||
(sqrt(2) / 2, sqrt(2) / 2, 0),
|
(sqrt(2) / 2, sqrt(2) / 2, 0),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
|
|
@ -405,7 +403,7 @@ class LocationsTests(unittest.TestCase):
|
||||||
with BuildPart():
|
with BuildPart():
|
||||||
with Locations(Location(Vector())):
|
with Locations(Location(Vector())):
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
LocationList._get_context().locations[0].to_tuple()[0], (0, 0, 0), 5
|
tuple(LocationList._get_context().locations[0])[0], (0, 0, 0), 5
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_errors(self):
|
def test_errors(self):
|
||||||
|
|
@ -524,7 +522,7 @@ class OffsetTests(unittest.TestCase):
|
||||||
def test_face_offset_with_holes(self):
|
def test_face_offset_with_holes(self):
|
||||||
sk = Rectangle(100, 100) - GridLocations(80, 80, 2, 2) * Circle(5)
|
sk = Rectangle(100, 100) - GridLocations(80, 80, 2, 2) * Circle(5)
|
||||||
sk2 = offset(sk, -5)
|
sk2 = offset(sk, -5)
|
||||||
self.assertTrue(sk2.face().is_valid())
|
self.assertTrue(sk2.face().is_valid)
|
||||||
self.assertLess(sk2.area, sk.area)
|
self.assertLess(sk2.area, sk.area)
|
||||||
self.assertEqual(len(sk2), 1)
|
self.assertEqual(len(sk2), 1)
|
||||||
|
|
||||||
|
|
@ -680,12 +678,12 @@ class ProjectionTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_project_point(self):
|
def test_project_point(self):
|
||||||
pnt: Vector = project(Vector(1, 2, 3), Plane.XY)[0]
|
pnt: Vector = project(Vector(1, 2, 3), Plane.XY)[0]
|
||||||
self.assertTupleAlmostEquals(pnt.to_tuple(), (1, 2, 0), 5)
|
self.assertTupleAlmostEquals(pnt, (1, 2, 0), 5)
|
||||||
pnt: Vector = project(Vertex(1, 2, 3), Plane.XZ)[0]
|
pnt: Vector = project(Vertex(1, 2, 3), Plane.XZ)[0]
|
||||||
self.assertTupleAlmostEquals(pnt.to_tuple(), (1, 3, 0), 5)
|
self.assertTupleAlmostEquals(pnt, (1, 3, 0), 5)
|
||||||
with BuildSketch(Plane.YZ) as s1:
|
with BuildSketch(Plane.YZ) as s1:
|
||||||
pnt = project(Vertex(1, 2, 3), mode=Mode.PRIVATE)[0]
|
pnt = project(Vertex(1, 2, 3), mode=Mode.PRIVATE)[0]
|
||||||
self.assertTupleAlmostEquals(pnt.to_tuple(), (2, 3, 0), 5)
|
self.assertTupleAlmostEquals(pnt, (2, 3, 0), 5)
|
||||||
|
|
||||||
def test_multiple_results(self):
|
def test_multiple_results(self):
|
||||||
with BuildLine() as l1:
|
with BuildLine() as l1:
|
||||||
|
|
@ -883,7 +881,7 @@ class TestSweep(unittest.TestCase):
|
||||||
Rectangle(2 * lip, 2 * lip, align=(Align.CENTER, Align.CENTER))
|
Rectangle(2 * lip, 2 * lip, align=(Align.CENTER, Align.CENTER))
|
||||||
sweep(sections=sk2.sketch, path=topedgs, mode=Mode.SUBTRACT)
|
sweep(sections=sk2.sketch, path=topedgs, mode=Mode.SUBTRACT)
|
||||||
|
|
||||||
self.assertTrue(p.part.is_valid())
|
self.assertTrue(p.part.is_valid)
|
||||||
|
|
||||||
def test_path_error(self):
|
def test_path_error(self):
|
||||||
e1 = Edge.make_line((0, 0), (1, 0))
|
e1 = Edge.make_line((0, 0), (1, 0))
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ class BuildLineTests(unittest.TestCase):
|
||||||
|
|
||||||
l3 = Line((0, 0), (10, 10))
|
l3 = Line((0, 0), (10, 10))
|
||||||
l4 = IntersectingLine((0, 10), (1, -1), l3)
|
l4 = IntersectingLine((0, 10), (1, -1), l3)
|
||||||
self.assertTupleAlmostEquals((l4 @ 1).to_tuple(), (5, 5, 0), 5)
|
self.assertTupleAlmostEquals(l4 @ 1, (5, 5, 0), 5)
|
||||||
self.assertTrue(isinstance(l4, Edge))
|
self.assertTrue(isinstance(l4, Edge))
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
|
|
@ -214,22 +214,20 @@ class BuildLineTests(unittest.TestCase):
|
||||||
def test_jern_arc(self):
|
def test_jern_arc(self):
|
||||||
with BuildLine() as jern:
|
with BuildLine() as jern:
|
||||||
j1 = JernArc((1, 0), (0, 1), 1, 90)
|
j1 = JernArc((1, 0), (0, 1), 1, 90)
|
||||||
self.assertTupleAlmostEquals((jern.line @ 1).to_tuple(), (0, 1, 0), 5)
|
self.assertTupleAlmostEquals(jern.line @ 1, (0, 1, 0), 5)
|
||||||
self.assertAlmostEqual(j1.radius, 1)
|
self.assertAlmostEqual(j1.radius, 1)
|
||||||
self.assertAlmostEqual(j1.length, pi / 2)
|
self.assertAlmostEqual(j1.length, pi / 2)
|
||||||
|
|
||||||
with BuildLine(Plane.XY.offset(1)) as offset_l:
|
with BuildLine(Plane.XY.offset(1)) as offset_l:
|
||||||
off1 = JernArc((1, 0), (0, 1), 1, 90)
|
off1 = JernArc((1, 0), (0, 1), 1, 90)
|
||||||
self.assertTupleAlmostEquals((offset_l.line @ 1).to_tuple(), (0, 1, 1), 5)
|
self.assertTupleAlmostEquals(offset_l.line @ 1, (0, 1, 1), 5)
|
||||||
self.assertAlmostEqual(off1.radius, 1)
|
self.assertAlmostEqual(off1.radius, 1)
|
||||||
self.assertAlmostEqual(off1.length, pi / 2)
|
self.assertAlmostEqual(off1.length, pi / 2)
|
||||||
|
|
||||||
plane_iso = Plane(origin=(0, 0, 0), x_dir=(1, 1, 0), z_dir=(1, -1, 1))
|
plane_iso = Plane(origin=(0, 0, 0), x_dir=(1, 1, 0), z_dir=(1, -1, 1))
|
||||||
with BuildLine(plane_iso) as iso_l:
|
with BuildLine(plane_iso) as iso_l:
|
||||||
iso1 = JernArc((0, 0), (0, 1), 1, 180)
|
iso1 = JernArc((0, 0), (0, 1), 1, 180)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(iso_l.line @ 1, (-sqrt(2), -sqrt(2), 0), 5)
|
||||||
(iso_l.line @ 1).to_tuple(), (-sqrt(2), -sqrt(2), 0), 5
|
|
||||||
)
|
|
||||||
self.assertAlmostEqual(iso1.radius, 1)
|
self.assertAlmostEqual(iso1.radius, 1)
|
||||||
self.assertAlmostEqual(iso1.length, pi)
|
self.assertAlmostEqual(iso1.length, pi)
|
||||||
|
|
||||||
|
|
@ -240,11 +238,11 @@ class BuildLineTests(unittest.TestCase):
|
||||||
self.assertFalse(l2.is_closed)
|
self.assertFalse(l2.is_closed)
|
||||||
circle_face = Face(Wire([l1]))
|
circle_face = Face(Wire([l1]))
|
||||||
self.assertAlmostEqual(circle_face.area, pi, 5)
|
self.assertAlmostEqual(circle_face.area, pi, 5)
|
||||||
self.assertTupleAlmostEquals(circle_face.center().to_tuple(), (0, 1, 0), 5)
|
self.assertTupleAlmostEquals(circle_face.center(), (0, 1, 0), 5)
|
||||||
self.assertTupleAlmostEquals(l1.vertex().to_tuple(), l2.start.to_tuple(), 5)
|
self.assertTupleAlmostEquals(l1.vertex(), l2.start, 5)
|
||||||
|
|
||||||
l1 = JernArc((0, 0), (1, 0), 1, 90)
|
l1 = JernArc((0, 0), (1, 0), 1, 90)
|
||||||
self.assertTupleAlmostEquals((l1 @ 1).to_tuple(), (1, 1, 0), 5)
|
self.assertTupleAlmostEquals(l1 @ 1, (1, 1, 0), 5)
|
||||||
self.assertTrue(isinstance(l1, Edge))
|
self.assertTrue(isinstance(l1, Edge))
|
||||||
|
|
||||||
def test_polar_line(self):
|
def test_polar_line(self):
|
||||||
|
|
@ -252,38 +250,38 @@ class BuildLineTests(unittest.TestCase):
|
||||||
with BuildLine():
|
with BuildLine():
|
||||||
a1 = PolarLine((0, 0), sqrt(2), 45)
|
a1 = PolarLine((0, 0), sqrt(2), 45)
|
||||||
d1 = PolarLine((0, 0), sqrt(2), direction=(1, 1))
|
d1 = PolarLine((0, 0), sqrt(2), direction=(1, 1))
|
||||||
self.assertTupleAlmostEquals((a1 @ 1).to_tuple(), (1, 1, 0), 5)
|
self.assertTupleAlmostEquals(a1 @ 1, (1, 1, 0), 5)
|
||||||
self.assertTupleAlmostEquals((a1 @ 1).to_tuple(), (d1 @ 1).to_tuple(), 5)
|
self.assertTupleAlmostEquals(a1 @ 1, d1 @ 1, 5)
|
||||||
self.assertTrue(isinstance(a1, Edge))
|
self.assertTrue(isinstance(a1, Edge))
|
||||||
self.assertTrue(isinstance(d1, Edge))
|
self.assertTrue(isinstance(d1, Edge))
|
||||||
|
|
||||||
with BuildLine():
|
with BuildLine():
|
||||||
a2 = PolarLine((0, 0), 1, 30)
|
a2 = PolarLine((0, 0), 1, 30)
|
||||||
d2 = PolarLine((0, 0), 1, direction=(sqrt(3), 1))
|
d2 = PolarLine((0, 0), 1, direction=(sqrt(3), 1))
|
||||||
self.assertTupleAlmostEquals((a2 @ 1).to_tuple(), (sqrt(3) / 2, 0.5, 0), 5)
|
self.assertTupleAlmostEquals(a2 @ 1, (sqrt(3) / 2, 0.5, 0), 5)
|
||||||
self.assertTupleAlmostEquals((a2 @ 1).to_tuple(), (d2 @ 1).to_tuple(), 5)
|
self.assertTupleAlmostEquals(a2 @ 1, d2 @ 1, 5)
|
||||||
|
|
||||||
with BuildLine():
|
with BuildLine():
|
||||||
a3 = PolarLine((0, 0), 1, 150)
|
a3 = PolarLine((0, 0), 1, 150)
|
||||||
d3 = PolarLine((0, 0), 1, direction=(-sqrt(3), 1))
|
d3 = PolarLine((0, 0), 1, direction=(-sqrt(3), 1))
|
||||||
self.assertTupleAlmostEquals((a3 @ 1).to_tuple(), (-sqrt(3) / 2, 0.5, 0), 5)
|
self.assertTupleAlmostEquals(a3 @ 1, (-sqrt(3) / 2, 0.5, 0), 5)
|
||||||
self.assertTupleAlmostEquals((a3 @ 1).to_tuple(), (d3 @ 1).to_tuple(), 5)
|
self.assertTupleAlmostEquals(a3 @ 1, d3 @ 1, 5)
|
||||||
|
|
||||||
with BuildLine():
|
with BuildLine():
|
||||||
a4 = PolarLine((0, 0), 1, angle=30, length_mode=LengthMode.HORIZONTAL)
|
a4 = PolarLine((0, 0), 1, angle=30, length_mode=LengthMode.HORIZONTAL)
|
||||||
d4 = PolarLine(
|
d4 = PolarLine(
|
||||||
(0, 0), 1, direction=(sqrt(3), 1), length_mode=LengthMode.HORIZONTAL
|
(0, 0), 1, direction=(sqrt(3), 1), length_mode=LengthMode.HORIZONTAL
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals((a4 @ 1).to_tuple(), (1, 1 / sqrt(3), 0), 5)
|
self.assertTupleAlmostEquals(a4 @ 1, (1, 1 / sqrt(3), 0), 5)
|
||||||
self.assertTupleAlmostEquals((a4 @ 1).to_tuple(), (d4 @ 1).to_tuple(), 5)
|
self.assertTupleAlmostEquals(a4 @ 1, d4 @ 1, 5)
|
||||||
|
|
||||||
with BuildLine(Plane.XZ):
|
with BuildLine(Plane.XZ):
|
||||||
a5 = PolarLine((0, 0), 1, angle=30, length_mode=LengthMode.VERTICAL)
|
a5 = PolarLine((0, 0), 1, angle=30, length_mode=LengthMode.VERTICAL)
|
||||||
d5 = PolarLine(
|
d5 = PolarLine(
|
||||||
(0, 0), 1, direction=(sqrt(3), 1), length_mode=LengthMode.VERTICAL
|
(0, 0), 1, direction=(sqrt(3), 1), length_mode=LengthMode.VERTICAL
|
||||||
)
|
)
|
||||||
self.assertTupleAlmostEquals((a5 @ 1).to_tuple(), (sqrt(3), 0, 1), 5)
|
self.assertTupleAlmostEquals(a5 @ 1, (sqrt(3), 0, 1), 5)
|
||||||
self.assertTupleAlmostEquals((a5 @ 1).to_tuple(), (d5 @ 1).to_tuple(), 5)
|
self.assertTupleAlmostEquals(a5 @ 1, d5 @ 1, 5)
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
PolarLine((0, 0), 1)
|
PolarLine((0, 0), 1)
|
||||||
|
|
@ -292,7 +290,7 @@ class BuildLineTests(unittest.TestCase):
|
||||||
"""Test spline with no tangents"""
|
"""Test spline with no tangents"""
|
||||||
with BuildLine() as test:
|
with BuildLine() as test:
|
||||||
s1 = Spline((0, 0), (1, 1), (2, 0))
|
s1 = Spline((0, 0), (1, 1), (2, 0))
|
||||||
self.assertTupleAlmostEquals((test.edges()[0] @ 1).to_tuple(), (2, 0, 0), 5)
|
self.assertTupleAlmostEquals(test.edges()[0] @ 1, (2, 0, 0), 5)
|
||||||
self.assertTrue(isinstance(s1, Edge))
|
self.assertTrue(isinstance(s1, Edge))
|
||||||
|
|
||||||
def test_radius_arc(self):
|
def test_radius_arc(self):
|
||||||
|
|
@ -333,19 +331,17 @@ class BuildLineTests(unittest.TestCase):
|
||||||
"""Test center arc as arc and circle"""
|
"""Test center arc as arc and circle"""
|
||||||
with BuildLine() as arc:
|
with BuildLine() as arc:
|
||||||
CenterArc((0, 0), 10, 0, 180)
|
CenterArc((0, 0), 10, 0, 180)
|
||||||
self.assertTupleAlmostEquals((arc.edges()[0] @ 1).to_tuple(), (-10, 0, 0), 5)
|
self.assertTupleAlmostEquals(arc.edges()[0] @ 1, (-10, 0, 0), 5)
|
||||||
with BuildLine() as arc:
|
with BuildLine() as arc:
|
||||||
CenterArc((0, 0), 10, 0, 360)
|
CenterArc((0, 0), 10, 0, 360)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(arc.edges()[0] @ 0, arc.edges()[0] @ 1, 5)
|
||||||
(arc.edges()[0] @ 0).to_tuple(), (arc.edges()[0] @ 1).to_tuple(), 5
|
|
||||||
)
|
|
||||||
with BuildLine(Plane.XZ) as arc:
|
with BuildLine(Plane.XZ) as arc:
|
||||||
CenterArc((0, 0), 10, 0, 360)
|
CenterArc((0, 0), 10, 0, 360)
|
||||||
self.assertTrue(Face(arc.wires()[0]).is_coplanar(Plane.XZ))
|
self.assertTrue(Face(arc.wires()[0]).is_coplanar(Plane.XZ))
|
||||||
|
|
||||||
with BuildLine(Plane.XZ) as arc:
|
with BuildLine(Plane.XZ) as arc:
|
||||||
CenterArc((-100, 0), 100, -45, 90)
|
CenterArc((-100, 0), 100, -45, 90)
|
||||||
self.assertTupleAlmostEquals((arc.edges()[0] @ 0.5).to_tuple(), (0, 0, 0), 5)
|
self.assertTupleAlmostEquals(arc.edges()[0] @ 0.5, (0, 0, 0), 5)
|
||||||
|
|
||||||
arc = CenterArc((-100, 0), 100, 0, 360)
|
arc = CenterArc((-100, 0), 100, 0, 360)
|
||||||
self.assertTrue(Face(Wire([arc])).is_coplanar(Plane.XY))
|
self.assertTrue(Face(Wire([arc])).is_coplanar(Plane.XY))
|
||||||
|
|
@ -729,6 +725,15 @@ class BuildLineTests(unittest.TestCase):
|
||||||
self.assertGreater(side_sign * coincident_dir, 0)
|
self.assertGreater(side_sign * coincident_dir, 0)
|
||||||
self.assertGreater(center_dir, 0)
|
self.assertGreater(center_dir, 0)
|
||||||
|
|
||||||
|
# Verify arc is tangent for a reversed start arc
|
||||||
|
c1 = CenterArc((0, 80), 40, 0, -180)
|
||||||
|
c2 = CenterArc((80, 0), 40, 90, 180)
|
||||||
|
arc = ArcArcTangentArc(c1, c2, 25, side=Side.RIGHT)
|
||||||
|
_, _, point = c1.distance_to_with_closest_points(arc)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
c1.tangent_at(point).cross(arc.tangent_at(point)).length, 0, 5
|
||||||
|
)
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
start_arc = CenterArc(start_point, start_r, 0, 360)
|
start_arc = CenterArc(start_point, start_r, 0, 360)
|
||||||
end_arc = CenterArc(end_point, end_r, 0, 360)
|
end_arc = CenterArc(end_point, end_r, 0, 360)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ license:
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from math import pi, sin
|
from math import pi, sin
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from build123d import *
|
from build123d import *
|
||||||
from build123d import LocationList, WorkplaneList
|
from build123d import LocationList, WorkplaneList
|
||||||
|
|
||||||
|
|
@ -56,7 +58,6 @@ class TestAlign(unittest.TestCase):
|
||||||
|
|
||||||
class TestMakeBrakeFormed(unittest.TestCase):
|
class TestMakeBrakeFormed(unittest.TestCase):
|
||||||
def test_make_brake_formed(self):
|
def test_make_brake_formed(self):
|
||||||
# TODO: Fix so this test doesn't raise a DeprecationWarning from NumPy
|
|
||||||
with BuildPart() as bp:
|
with BuildPart() as bp:
|
||||||
with BuildLine() as bl:
|
with BuildLine() as bl:
|
||||||
Polyline((0, 0), (5, 6), (10, 1))
|
Polyline((0, 0), (5, 6), (10, 1))
|
||||||
|
|
@ -71,6 +72,67 @@ class TestMakeBrakeFormed(unittest.TestCase):
|
||||||
self.assertAlmostEqual(sheet_metal.bounding_box().max.Z, 1, 2)
|
self.assertAlmostEqual(sheet_metal.bounding_box().max.Z, 1, 2)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPartOperationDraft(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.box = Box(10, 10, 10).solid()
|
||||||
|
self.sides = self.box.faces().filter_by(Axis.Z, reverse=True)
|
||||||
|
self.bottom_face = self.box.faces().sort_by(Axis.Z)[0]
|
||||||
|
self.neutral_plane = Plane(self.bottom_face)
|
||||||
|
|
||||||
|
def test_successful_draft(self):
|
||||||
|
"""Test that a draft operation completes successfully"""
|
||||||
|
result = draft(self.sides, self.neutral_plane, 5)
|
||||||
|
self.assertIsInstance(result, Part)
|
||||||
|
self.assertLess(self.box.volume, result.volume)
|
||||||
|
|
||||||
|
with BuildPart() as draft_box:
|
||||||
|
Box(10, 10, 10)
|
||||||
|
draft(
|
||||||
|
draft_box.faces().filter_by(Axis.Z, reverse=True),
|
||||||
|
Plane.XY.offset(-5),
|
||||||
|
5,
|
||||||
|
)
|
||||||
|
self.assertLess(draft_box.part.volume, 1000)
|
||||||
|
|
||||||
|
def test_invalid_face_type(self):
|
||||||
|
"""Test that a ValueError is raised for unsupported face types"""
|
||||||
|
torus = Torus(5, 1).solid()
|
||||||
|
with self.assertRaises(ValueError) as cm:
|
||||||
|
draft([torus.faces()[0]], self.neutral_plane, 5)
|
||||||
|
|
||||||
|
def test_faces_from_multiple_solids(self):
|
||||||
|
"""Test that using faces from different solids raises an error"""
|
||||||
|
box2 = Box(5, 5, 5).solid()
|
||||||
|
mixed = [self.sides[0], box2.faces()[0]]
|
||||||
|
with self.assertRaises(ValueError) as cm:
|
||||||
|
draft(mixed, self.neutral_plane, 5)
|
||||||
|
self.assertIn("same topological parent", str(cm.exception))
|
||||||
|
|
||||||
|
def test_faces_from_multiple_parts(self):
|
||||||
|
"""Test that using faces from different solids raises an error"""
|
||||||
|
box2 = Box(5, 5, 5).solid()
|
||||||
|
part: Part = Part() + [self.box, Pos(X=10) * box2]
|
||||||
|
mixed = [part.faces().sort_by(Axis.X)[0], part.faces().sort_by(Axis.X)[-1]]
|
||||||
|
with self.assertRaises(ValueError) as cm:
|
||||||
|
draft(mixed, self.neutral_plane, 5)
|
||||||
|
|
||||||
|
def test_bad_draft_faces(self):
|
||||||
|
with self.assertRaises(DraftAngleError):
|
||||||
|
draft(self.bottom_face, self.neutral_plane, 10)
|
||||||
|
|
||||||
|
@patch("build123d.topology.three_d.BRepOffsetAPI_DraftAngle")
|
||||||
|
def test_draftangleerror_from_solid_draft(self, mock_draft_angle):
|
||||||
|
"""Simulate a failure in AddDone and catch DraftAngleError"""
|
||||||
|
mock_builder = MagicMock()
|
||||||
|
mock_builder.AddDone.return_value = False
|
||||||
|
mock_builder.ProblematicShape.return_value = "ShapeX"
|
||||||
|
mock_draft_angle.return_value = mock_builder
|
||||||
|
|
||||||
|
with self.assertRaises(DraftAngleError) as cm:
|
||||||
|
draft(self.sides, self.neutral_plane, 5)
|
||||||
|
|
||||||
|
|
||||||
class TestBuildPart(unittest.TestCase):
|
class TestBuildPart(unittest.TestCase):
|
||||||
"""Test the BuildPart Builder derived class"""
|
"""Test the BuildPart Builder derived class"""
|
||||||
|
|
||||||
|
|
@ -171,7 +233,7 @@ class TestBuildPart(unittest.TestCase):
|
||||||
def test_named_plane(self):
|
def test_named_plane(self):
|
||||||
with BuildPart(Plane.YZ) as test:
|
with BuildPart(Plane.YZ) as test:
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
WorkplaneList._get_context().workplanes[0].z_dir.to_tuple(),
|
WorkplaneList._get_context().workplanes[0].z_dir,
|
||||||
(1, 0, 0),
|
(1, 0, 0),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -92,9 +92,7 @@ class TestBuildSketch(unittest.TestCase):
|
||||||
with BuildLine():
|
with BuildLine():
|
||||||
l1 = Line((0, 0), (10, 0))
|
l1 = Line((0, 0), (10, 0))
|
||||||
Line(l1 @ 1, (10, 10))
|
Line(l1 @ 1, (10, 10))
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(test.consolidate_edges() @ 1, (10, 10, 0), 5)
|
||||||
(test.consolidate_edges() @ 1).to_tuple(), (10, 10, 0), 5
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_mode_intersect(self):
|
def test_mode_intersect(self):
|
||||||
with BuildSketch() as test:
|
with BuildSketch() as test:
|
||||||
|
|
@ -224,6 +222,11 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
self.assertAlmostEqual(test.sketch.area, 0.5, 5)
|
self.assertAlmostEqual(test.sketch.area, 0.5, 5)
|
||||||
self.assertEqual(p.faces()[0].normal_at(), Vector(0, 0, 1))
|
self.assertEqual(p.faces()[0].normal_at(), Vector(0, 0, 1))
|
||||||
|
|
||||||
|
# test iterable input
|
||||||
|
points_nervure = [(0.0, 0.0), (10.0, 0.0), (0.0, 5.0)]
|
||||||
|
riri = Polygon(points_nervure, align=Align.NONE)
|
||||||
|
self.assertEqual(len(riri.vertices()), 3)
|
||||||
|
|
||||||
def test_rectangle(self):
|
def test_rectangle(self):
|
||||||
with BuildSketch() as test:
|
with BuildSketch() as test:
|
||||||
r = Rectangle(20, 10)
|
r = Rectangle(20, 10)
|
||||||
|
|
@ -263,9 +266,7 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
self.assertEqual(r.align, (Align.CENTER, Align.CENTER))
|
self.assertEqual(r.align, (Align.CENTER, Align.CENTER))
|
||||||
self.assertEqual(r.mode, Mode.ADD)
|
self.assertEqual(r.mode, Mode.ADD)
|
||||||
self.assertAlmostEqual(test.sketch.area, (3 * sqrt(3) / 2) * 2**2, 5)
|
self.assertAlmostEqual(test.sketch.area, (3 * sqrt(3) / 2) * 2**2, 5)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(test.sketch.faces()[0].normal_at(), (0, 0, 1), 5)
|
||||||
test.sketch.faces()[0].normal_at().to_tuple(), (0, 0, 1), 5
|
|
||||||
)
|
|
||||||
self.assertAlmostEqual(r.apothem, 2 * sqrt(3) / 2)
|
self.assertAlmostEqual(r.apothem, 2 * sqrt(3) / 2)
|
||||||
|
|
||||||
def test_regular_polygon_minor_radius(self):
|
def test_regular_polygon_minor_radius(self):
|
||||||
|
|
@ -277,9 +278,7 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
self.assertEqual(r.align, (Align.CENTER, Align.CENTER))
|
self.assertEqual(r.align, (Align.CENTER, Align.CENTER))
|
||||||
self.assertEqual(r.mode, Mode.ADD)
|
self.assertEqual(r.mode, Mode.ADD)
|
||||||
self.assertAlmostEqual(test.sketch.area, (3 * sqrt(3) / 4) * (0.5 * 2) ** 2, 5)
|
self.assertAlmostEqual(test.sketch.area, (3 * sqrt(3) / 4) * (0.5 * 2) ** 2, 5)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(test.sketch.faces()[0].normal_at(), (0, 0, 1), 5)
|
||||||
test.sketch.faces()[0].normal_at().to_tuple(), (0, 0, 1), 5
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_regular_polygon_align(self):
|
def test_regular_polygon_align(self):
|
||||||
with BuildSketch() as align:
|
with BuildSketch() as align:
|
||||||
|
|
@ -303,7 +302,7 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
poly_pts = [Vector(v) for v in regular_poly.vertices()]
|
poly_pts = [Vector(v) for v in regular_poly.vertices()]
|
||||||
polar_pts = [p.position for p in PolarLocations(1, side_count)]
|
polar_pts = [p.position for p in PolarLocations(1, side_count)]
|
||||||
for poly_pt, polar_pt in zip(poly_pts, polar_pts):
|
for poly_pt, polar_pt in zip(poly_pts, polar_pts):
|
||||||
self.assertTupleAlmostEquals(poly_pt.to_tuple(), polar_pt.to_tuple(), 5)
|
self.assertTupleAlmostEquals(poly_pt, polar_pt, 5)
|
||||||
|
|
||||||
def test_regular_polygon_min_sides(self):
|
def test_regular_polygon_min_sides(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
|
|
@ -325,8 +324,8 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
def test_slot_center_point(self):
|
def test_slot_center_point(self):
|
||||||
with BuildSketch() as test:
|
with BuildSketch() as test:
|
||||||
s = SlotCenterPoint((0, 0), (2, 0), 2)
|
s = SlotCenterPoint((0, 0), (2, 0), 2)
|
||||||
self.assertTupleAlmostEquals(s.slot_center.to_tuple(), (0, 0, 0), 5)
|
self.assertTupleAlmostEquals(s.slot_center, (0, 0, 0), 5)
|
||||||
self.assertTupleAlmostEquals(s.point.to_tuple(), (2, 0, 0), 5)
|
self.assertTupleAlmostEquals(s.point, (2, 0, 0), 5)
|
||||||
self.assertEqual(s.slot_height, 2)
|
self.assertEqual(s.slot_height, 2)
|
||||||
self.assertEqual(s.rotation, 0)
|
self.assertEqual(s.rotation, 0)
|
||||||
self.assertEqual(s.mode, Mode.ADD)
|
self.assertEqual(s.mode, Mode.ADD)
|
||||||
|
|
@ -334,25 +333,39 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
self.assertEqual(s.faces()[0].normal_at(), Vector(0, 0, 1))
|
self.assertEqual(s.faces()[0].normal_at(), Vector(0, 0, 1))
|
||||||
|
|
||||||
def test_slot_center_to_center(self):
|
def test_slot_center_to_center(self):
|
||||||
|
height = 2
|
||||||
with BuildSketch() as test:
|
with BuildSketch() as test:
|
||||||
s = SlotCenterToCenter(4, 2)
|
s = SlotCenterToCenter(4, height)
|
||||||
self.assertEqual(s.center_separation, 4)
|
self.assertEqual(s.center_separation, 4)
|
||||||
self.assertEqual(s.slot_height, 2)
|
self.assertEqual(s.slot_height, height)
|
||||||
self.assertEqual(s.rotation, 0)
|
self.assertEqual(s.rotation, 0)
|
||||||
self.assertEqual(s.mode, Mode.ADD)
|
self.assertEqual(s.mode, Mode.ADD)
|
||||||
self.assertAlmostEqual(test.sketch.area, pi + 4 * 2, 5)
|
self.assertAlmostEqual(test.sketch.area, pi + 4 * height, 5)
|
||||||
self.assertEqual(s.faces()[0].normal_at(), Vector(0, 0, 1))
|
self.assertEqual(s.faces()[0].normal_at(), Vector(0, 0, 1))
|
||||||
|
|
||||||
|
# Circle degenerate
|
||||||
|
s1 = SlotCenterToCenter(0, height)
|
||||||
|
self.assertTrue(len(s1.edges()) == 1)
|
||||||
|
self.assertEqual(s1.edge().geom_type, GeomType.CIRCLE)
|
||||||
|
self.assertAlmostEqual(s1.edge().radius, height / 2)
|
||||||
|
|
||||||
def test_slot_overall(self):
|
def test_slot_overall(self):
|
||||||
|
height = 2
|
||||||
with BuildSketch() as test:
|
with BuildSketch() as test:
|
||||||
s = SlotOverall(6, 2)
|
s = SlotOverall(6, height)
|
||||||
self.assertEqual(s.width, 6)
|
self.assertEqual(s.width, 6)
|
||||||
self.assertEqual(s.slot_height, 2)
|
self.assertEqual(s.slot_height, height)
|
||||||
self.assertEqual(s.rotation, 0)
|
self.assertEqual(s.rotation, 0)
|
||||||
self.assertEqual(s.mode, Mode.ADD)
|
self.assertEqual(s.mode, Mode.ADD)
|
||||||
self.assertAlmostEqual(test.sketch.area, pi + 4 * 2, 5)
|
self.assertAlmostEqual(test.sketch.area, pi + 4 * height, 5)
|
||||||
self.assertEqual(s.faces()[0].normal_at(), Vector(0, 0, 1))
|
self.assertEqual(s.faces()[0].normal_at(), Vector(0, 0, 1))
|
||||||
|
|
||||||
|
# Circle degenerat
|
||||||
|
s1 = SlotOverall(2, height)
|
||||||
|
self.assertTrue(len(s1.edges()) == 1)
|
||||||
|
self.assertEqual(s1.edge().geom_type, GeomType.CIRCLE)
|
||||||
|
self.assertAlmostEqual(s1.edge().radius, height / 2)
|
||||||
|
|
||||||
def test_text(self):
|
def test_text(self):
|
||||||
with BuildSketch() as test:
|
with BuildSketch() as test:
|
||||||
t = Text("test", 2)
|
t = Text("test", 2)
|
||||||
|
|
@ -419,6 +432,9 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
self.assertTupleAlmostEquals(tri.vertex_A, (3, 4, 0), 5)
|
self.assertTupleAlmostEquals(tri.vertex_A, (3, 4, 0), 5)
|
||||||
self.assertTupleAlmostEquals(tri.vertex_B, (0, 0, 0), 5)
|
self.assertTupleAlmostEquals(tri.vertex_B, (0, 0, 0), 5)
|
||||||
self.assertTupleAlmostEquals(tri.vertex_C, (3, 0, 0), 5)
|
self.assertTupleAlmostEquals(tri.vertex_C, (3, 0, 0), 5)
|
||||||
|
self.assertEqual(tri.vertex_A.topo_parent, tri)
|
||||||
|
self.assertEqual(tri.vertex_B.topo_parent, tri)
|
||||||
|
self.assertEqual(tri.vertex_C.topo_parent, tri)
|
||||||
|
|
||||||
tri = Triangle(c=5, C=90, a=3)
|
tri = Triangle(c=5, C=90, a=3)
|
||||||
self.assertAlmostEqual(tri.area, (3 * 4) / 2, 5)
|
self.assertAlmostEqual(tri.area, (3 * 4) / 2, 5)
|
||||||
|
|
@ -525,7 +541,7 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
self.assertLess(tri_round.area, tri.area)
|
self.assertLess(tri_round.area, tri.area)
|
||||||
|
|
||||||
# Test flipping the face
|
# Test flipping the face
|
||||||
flipped = -Rectangle(34, 10).face()
|
flipped = -Face.make_rect(34, 10)
|
||||||
rounded = full_round((flipped.edges() << Axis.X)[0]).face()
|
rounded = full_round((flipped.edges() << Axis.X)[0]).face()
|
||||||
self.assertEqual(flipped.normal_at(), rounded.normal_at())
|
self.assertEqual(flipped.normal_at(), rounded.normal_at())
|
||||||
|
|
||||||
|
|
@ -533,9 +549,9 @@ class TestBuildSketchObjects(unittest.TestCase):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"slot,args",
|
"slot,args",
|
||||||
[
|
[
|
||||||
(SlotOverall, (5, 10)),
|
(SlotOverall, (9, 10)),
|
||||||
(SlotCenterToCenter, (-1, 10)),
|
(SlotCenterToCenter, (-1, 10)),
|
||||||
(SlotCenterPoint, ((0, 0, 0), (2, 0, 0), 10)),
|
(SlotCenterPoint, ((0, 0, 0), (0, 0, 0), 10)),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_invalid_slots(slot, args):
|
def test_invalid_slots(slot, args):
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import unittest
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from OCP.gp import gp_Ax1, gp_Dir, gp_Pnt
|
from OCP.gp import gp_Ax1, gp_Dir, gp_Pnt
|
||||||
from build123d.geometry import Axis, Location, Plane, Vector
|
from build123d.geometry import Axis, Location, Plane, Vector
|
||||||
from build123d.topology import Edge
|
from build123d.topology import Edge, Vertex
|
||||||
|
|
||||||
|
|
||||||
class AlwaysEqual:
|
class AlwaysEqual:
|
||||||
|
|
@ -65,10 +65,18 @@ class TestAxis(unittest.TestCase):
|
||||||
self.assertAlmostEqual(test_axis.position, (1, 2, 3), 5)
|
self.assertAlmostEqual(test_axis.position, (1, 2, 3), 5)
|
||||||
self.assertAlmostEqual(test_axis.direction, (0, 0, 1), 5)
|
self.assertAlmostEqual(test_axis.direction, (0, 0, 1), 5)
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Axis("one")
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Axis("one", "up")
|
Axis("one", "up")
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Axis(one="up")
|
Axis(one="up")
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
bad_edge = Edge()
|
||||||
|
bad_edge.wrapped = Vertex(0, 1, 2).wrapped
|
||||||
|
Axis(edge=bad_edge)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Axis(gp_ax1=Edge.make_line((0, 0), (1, 0)))
|
||||||
|
|
||||||
def test_axis_from_occt(self):
|
def test_axis_from_occt(self):
|
||||||
occt_axis = gp_Ax1(gp_Pnt(1, 1, 1), gp_Dir(0, 1, 0))
|
occt_axis = gp_Ax1(gp_Pnt(1, 1, 1), gp_Dir(0, 1, 0))
|
||||||
|
|
@ -100,11 +108,16 @@ class TestAxis(unittest.TestCase):
|
||||||
self.assertAlmostEqual(y_axis.position, (0, 0, 1), 5)
|
self.assertAlmostEqual(y_axis.position, (0, 0, 1), 5)
|
||||||
self.assertAlmostEqual(y_axis.direction, (0, 1, 0), 5)
|
self.assertAlmostEqual(y_axis.direction, (0, 1, 0), 5)
|
||||||
|
|
||||||
def test_axis_to_plane(self):
|
def test_from_location(self):
|
||||||
x_plane = Axis.X.to_plane()
|
axis = Axis(Location((1, 2, 3), (-90, 0, 0)))
|
||||||
self.assertTrue(isinstance(x_plane, Plane))
|
self.assertAlmostEqual(axis.position, (1, 2, 3), 6)
|
||||||
self.assertAlmostEqual(x_plane.origin, (0, 0, 0), 5)
|
self.assertAlmostEqual(axis.direction, (0, 1, 0), 6)
|
||||||
self.assertAlmostEqual(x_plane.z_dir, (1, 0, 0), 5)
|
|
||||||
|
# def test_axis_to_plane(self):
|
||||||
|
# x_plane = Axis.X.to_plane()
|
||||||
|
# self.assertTrue(isinstance(x_plane, Plane))
|
||||||
|
# self.assertAlmostEqual(x_plane.origin, (0, 0, 0), 5)
|
||||||
|
# self.assertAlmostEqual(x_plane.z_dir, (1, 0, 0), 5)
|
||||||
|
|
||||||
def test_axis_is_coaxial(self):
|
def test_axis_is_coaxial(self):
|
||||||
self.assertTrue(Axis.X.is_coaxial(Axis((0, 0, 0), (1, 0, 0))))
|
self.assertTrue(Axis.X.is_coaxial(Axis((0, 0, 0), (1, 0, 0))))
|
||||||
|
|
@ -179,7 +192,7 @@ class TestAxis(unittest.TestCase):
|
||||||
self.assertIsNone(Axis.X.intersect(Axis((0, 1, 1), (0, 0, 1))))
|
self.assertIsNone(Axis.X.intersect(Axis((0, 1, 1), (0, 0, 1))))
|
||||||
|
|
||||||
intersection = Axis((1, 2, 3), (0, 0, 1)) & Plane.XY
|
intersection = Axis((1, 2, 3), (0, 0, 1)) & Plane.XY
|
||||||
self.assertAlmostEqual(intersection.to_tuple(), (1, 2, 0), 5)
|
self.assertAlmostEqual(intersection, (1, 2, 0), 5)
|
||||||
|
|
||||||
arc = Edge.make_circle(20, start_angle=0, end_angle=180)
|
arc = Edge.make_circle(20, start_angle=0, end_angle=180)
|
||||||
ax0 = Axis((-20, 30, 0), (4, -3, 0))
|
ax0 = Axis((-20, 30, 0), (4, -3, 0))
|
||||||
|
|
@ -213,10 +226,10 @@ class TestAxis(unittest.TestCase):
|
||||||
|
|
||||||
# self.assertTrue(len(intersections.vertices(), 2))
|
# self.assertTrue(len(intersections.vertices(), 2))
|
||||||
# np.testing.assert_allclose(
|
# np.testing.assert_allclose(
|
||||||
# intersection.vertices()[0].to_tuple(), (-1, 0, 5), 5
|
# intersection.vertices()[0], (-1, 0, 5), 5
|
||||||
# )
|
# )
|
||||||
# np.testing.assert_allclose(
|
# np.testing.assert_allclose(
|
||||||
# intersection.vertices()[1].to_tuple(), (1, 0, 5), 5
|
# intersection.vertices()[1], (1, 0, 5), 5
|
||||||
# )
|
# )
|
||||||
|
|
||||||
def test_axis_equal(self):
|
def test_axis_equal(self):
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,10 @@ class TestCompound(unittest.TestCase):
|
||||||
box1 = Solid.make_box(1, 1, 1)
|
box1 = Solid.make_box(1, 1, 1)
|
||||||
box2 = Solid.make_box(1, 1, 1, Plane((1, 0, 0)))
|
box2 = Solid.make_box(1, 1, 1, Plane((1, 0, 0)))
|
||||||
combined = Compound([box1]).fuse(box2, glue=True)
|
combined = Compound([box1]).fuse(box2, glue=True)
|
||||||
self.assertTrue(combined.is_valid())
|
self.assertTrue(combined.is_valid)
|
||||||
self.assertAlmostEqual(combined.volume, 2, 5)
|
self.assertAlmostEqual(combined.volume, 2, 5)
|
||||||
fuzzy = Compound([box1]).fuse(box2, tol=1e-6)
|
fuzzy = Compound([box1]).fuse(box2, tol=1e-6)
|
||||||
self.assertTrue(fuzzy.is_valid())
|
self.assertTrue(fuzzy.is_valid)
|
||||||
self.assertAlmostEqual(fuzzy.volume, 2, 5)
|
self.assertAlmostEqual(fuzzy.volume, 2, 5)
|
||||||
|
|
||||||
def test_remove(self):
|
def test_remove(self):
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ license:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
import numpy as np
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from unittest.mock import patch, PropertyMock
|
from unittest.mock import patch, PropertyMock
|
||||||
|
|
@ -36,7 +37,7 @@ from build123d.geometry import Axis, Plane, Vector
|
||||||
from build123d.objects_curve import CenterArc, EllipticalCenterArc
|
from build123d.objects_curve import CenterArc, EllipticalCenterArc
|
||||||
from build123d.objects_sketch import Circle, Rectangle, RegularPolygon
|
from build123d.objects_sketch import Circle, Rectangle, RegularPolygon
|
||||||
from build123d.operations_generic import sweep
|
from build123d.operations_generic import sweep
|
||||||
from build123d.topology import Edge, Face
|
from build123d.topology import Edge, Face, Wire
|
||||||
from OCP.GeomProjLib import GeomProjLib
|
from OCP.GeomProjLib import GeomProjLib
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -121,7 +122,7 @@ class TestEdge(unittest.TestCase):
|
||||||
for end in [0, 1]:
|
for end in [0, 1]:
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
edge.position_at(end),
|
edge.position_at(end),
|
||||||
edge.to_wire().position_at(end),
|
Wire(edge).position_at(end),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -233,7 +234,7 @@ class TestEdge(unittest.TestCase):
|
||||||
for i, loc in enumerate(locs):
|
for i, loc in enumerate(locs):
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
loc.position,
|
loc.position,
|
||||||
Vector(1, 0, 0).rotate(Axis.Z, i * 90).to_tuple(),
|
Vector(1, 0, 0).rotate(Axis.Z, i * 90),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
self.assertAlmostEqual(loc.orientation, (0, 0, 0), 5)
|
self.assertAlmostEqual(loc.orientation, (0, 0, 0), 5)
|
||||||
|
|
@ -272,6 +273,27 @@ class TestEdge(unittest.TestCase):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
edge.param_at_point((-1, 1))
|
edge.param_at_point((-1, 1))
|
||||||
|
|
||||||
|
def test_param_at_point_bspline(self):
|
||||||
|
# Define a complex spline with inflections and non-monotonic behavior
|
||||||
|
curve = Edge.make_spline(
|
||||||
|
[
|
||||||
|
(-2, 0, 0),
|
||||||
|
(-10, 1, 0),
|
||||||
|
(0, 0, 0),
|
||||||
|
(1, -2, 0),
|
||||||
|
(2, 0, 0),
|
||||||
|
(1, 1, 0),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sample N points along the curve using position_at and check that
|
||||||
|
# param_at_point returns approximately the same param (inverted)
|
||||||
|
N = 20
|
||||||
|
for u in np.linspace(0.0, 1.0, N):
|
||||||
|
p = curve.position_at(u)
|
||||||
|
u_back = curve.param_at_point(p)
|
||||||
|
self.assertAlmostEqual(u, u_back, delta=1e-6, msg=f"u={u}, u_back={u_back}")
|
||||||
|
|
||||||
def test_conical_helix(self):
|
def test_conical_helix(self):
|
||||||
helix = Edge.make_helix(1, 4, 1, normal=(-1, 0, 0), angle=10, lefthand=True)
|
helix = Edge.make_helix(1, 4, 1, normal=(-1, 0, 0), angle=10, lefthand=True)
|
||||||
self.assertAlmostEqual(helix.bounding_box().min.X, -4, 5)
|
self.assertAlmostEqual(helix.bounding_box().min.X, -4, 5)
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ from build123d.objects_sketch import (
|
||||||
Polygon,
|
Polygon,
|
||||||
Rectangle,
|
Rectangle,
|
||||||
RegularPolygon,
|
RegularPolygon,
|
||||||
|
Text,
|
||||||
Triangle,
|
Triangle,
|
||||||
)
|
)
|
||||||
from build123d.operations_generic import fillet, offset
|
from build123d.operations_generic import fillet, offset
|
||||||
|
|
@ -64,7 +65,7 @@ class TestFace(unittest.TestCase):
|
||||||
bottom_edge = Edge.make_circle(radius=1, end_angle=90)
|
bottom_edge = Edge.make_circle(radius=1, end_angle=90)
|
||||||
top_edge = Edge.make_circle(radius=1, plane=Plane((0, 0, 1)), end_angle=90)
|
top_edge = Edge.make_circle(radius=1, plane=Plane((0, 0, 1)), end_angle=90)
|
||||||
curved = Face.make_surface_from_curves(bottom_edge, top_edge)
|
curved = Face.make_surface_from_curves(bottom_edge, top_edge)
|
||||||
self.assertTrue(curved.is_valid())
|
self.assertTrue(curved.is_valid)
|
||||||
self.assertAlmostEqual(curved.area, math.pi / 2, 5)
|
self.assertAlmostEqual(curved.area, math.pi / 2, 5)
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
curved.normal_at(), (math.sqrt(2) / 2, math.sqrt(2) / 2, 0), 5
|
curved.normal_at(), (math.sqrt(2) / 2, math.sqrt(2) / 2, 0), 5
|
||||||
|
|
@ -73,7 +74,7 @@ class TestFace(unittest.TestCase):
|
||||||
bottom_wire = Wire.make_circle(1)
|
bottom_wire = Wire.make_circle(1)
|
||||||
top_wire = Wire.make_circle(1, Plane((0, 0, 1)))
|
top_wire = Wire.make_circle(1, Plane((0, 0, 1)))
|
||||||
curved = Face.make_surface_from_curves(bottom_wire, top_wire)
|
curved = Face.make_surface_from_curves(bottom_wire, top_wire)
|
||||||
self.assertTrue(curved.is_valid())
|
self.assertTrue(curved.is_valid)
|
||||||
self.assertAlmostEqual(curved.area, 2 * math.pi, 5)
|
self.assertAlmostEqual(curved.area, 2 * math.pi, 5)
|
||||||
|
|
||||||
def test_center(self):
|
def test_center(self):
|
||||||
|
|
@ -168,6 +169,13 @@ class TestFace(unittest.TestCase):
|
||||||
flipped_square = -square
|
flipped_square = -square
|
||||||
self.assertAlmostEqual(flipped_square.normal_at(), (0, 0, -1), 5)
|
self.assertAlmostEqual(flipped_square.normal_at(), (0, 0, -1), 5)
|
||||||
|
|
||||||
|
# Ensure the topo_parent is cleared when a face is negated
|
||||||
|
# (otherwise the original Rectangle would be the topo_parent)
|
||||||
|
flipped = -Rectangle(34, 10).face()
|
||||||
|
left_edge = flipped.edges().sort_by(Axis.X)[0]
|
||||||
|
parent_face = left_edge.topo_parent
|
||||||
|
self.assertAlmostEqual(flipped.normal_at(), parent_face.normal_at(), 5)
|
||||||
|
|
||||||
def test_offset(self):
|
def test_offset(self):
|
||||||
bbox = Face.make_rect(2, 2, Plane.XY).offset(5).bounding_box()
|
bbox = Face.make_rect(2, 2, Plane.XY).offset(5).bounding_box()
|
||||||
self.assertAlmostEqual(bbox.min, (-1, -1, 5), 5)
|
self.assertAlmostEqual(bbox.min, (-1, -1, 5), 5)
|
||||||
|
|
@ -182,7 +190,7 @@ class TestFace(unittest.TestCase):
|
||||||
happy = Face(outer, inners)
|
happy = Face(outer, inners)
|
||||||
self.assertAlmostEqual(happy.area, math.pi * (10**2 - 2), 5)
|
self.assertAlmostEqual(happy.area, math.pi * (10**2 - 2), 5)
|
||||||
|
|
||||||
outer = Edge.make_circle(10, end_angle=180).to_wire()
|
outer = Wire(Edge.make_circle(10, end_angle=180))
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Face(outer, inners)
|
Face(outer, inners)
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
|
|
@ -191,7 +199,7 @@ class TestFace(unittest.TestCase):
|
||||||
outer = Wire.make_circle(10)
|
outer = Wire.make_circle(10)
|
||||||
inners = [
|
inners = [
|
||||||
Wire.make_circle(1).locate(Location((-2, 2, 0))),
|
Wire.make_circle(1).locate(Location((-2, 2, 0))),
|
||||||
Edge.make_circle(1, end_angle=180).to_wire().locate(Location((2, 2, 0))),
|
Wire(Edge.make_circle(1, end_angle=180)).locate(Location((2, 2, 0))),
|
||||||
]
|
]
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Face(outer, inners)
|
Face(outer, inners)
|
||||||
|
|
@ -302,7 +310,7 @@ class TestFace(unittest.TestCase):
|
||||||
for j in range(4 - i % 2)
|
for j in range(4 - i % 2)
|
||||||
]
|
]
|
||||||
cylinder_walls_with_holes = cylinder_wall.make_holes(projected_wires)
|
cylinder_walls_with_holes = cylinder_wall.make_holes(projected_wires)
|
||||||
self.assertTrue(cylinder_walls_with_holes.is_valid())
|
self.assertTrue(cylinder_walls_with_holes.is_valid)
|
||||||
self.assertLess(cylinder_walls_with_holes.area, cylinder_wall.area)
|
self.assertLess(cylinder_walls_with_holes.area, cylinder_wall.area)
|
||||||
|
|
||||||
def test_is_inside(self):
|
def test_is_inside(self):
|
||||||
|
|
@ -376,7 +384,7 @@ class TestFace(unittest.TestCase):
|
||||||
surface_points=[Vector(0, 0, -5)],
|
surface_points=[Vector(0, 0, -5)],
|
||||||
interior_wires=[hole],
|
interior_wires=[hole],
|
||||||
)
|
)
|
||||||
self.assertTrue(surface.is_valid())
|
self.assertTrue(surface.is_valid)
|
||||||
self.assertEqual(surface.geom_type, GeomType.BSPLINE)
|
self.assertEqual(surface.geom_type, GeomType.BSPLINE)
|
||||||
bbox = surface.bounding_box()
|
bbox = surface.bounding_box()
|
||||||
self.assertAlmostEqual(bbox.min, (-50.5, -24.5, -5.113393280136395), 5)
|
self.assertAlmostEqual(bbox.min, (-50.5, -24.5, -5.113393280136395), 5)
|
||||||
|
|
@ -422,15 +430,15 @@ class TestFace(unittest.TestCase):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Face.sweep(edge, Polyline((0, 0), (0.1, 0), (0.2, 0.1)))
|
Face.sweep(edge, Polyline((0, 0), (0.1, 0), (0.2, 0.1)))
|
||||||
|
|
||||||
def test_to_arcs(self):
|
# def test_to_arcs(self):
|
||||||
with BuildSketch() as bs:
|
# with BuildSketch() as bs:
|
||||||
with BuildLine() as bl:
|
# with BuildLine() as bl:
|
||||||
Polyline((0, 0), (1, 0), (1.5, 0.5), (2, 0), (2, 1), (0, 1), (0, 0))
|
# Polyline((0, 0), (1, 0), (1.5, 0.5), (2, 0), (2, 1), (0, 1), (0, 0))
|
||||||
fillet(bl.vertices(), radius=0.1)
|
# fillet(bl.vertices(), radius=0.1)
|
||||||
make_face()
|
# make_face()
|
||||||
smooth = bs.faces()[0]
|
# smooth = bs.faces()[0]
|
||||||
fragmented = smooth.to_arcs()
|
# fragmented = smooth.to_arcs()
|
||||||
self.assertLess(len(smooth.edges()), len(fragmented.edges()))
|
# self.assertLess(len(smooth.edges()), len(fragmented.edges()))
|
||||||
|
|
||||||
def test_outer_wire(self):
|
def test_outer_wire(self):
|
||||||
face = (Face.make_rect(1, 1) - Face.make_rect(0.5, 0.5)).face()
|
face = (Face.make_rect(1, 1) - Face.make_rect(0.5, 0.5)).face()
|
||||||
|
|
@ -457,6 +465,37 @@ class TestFace(unittest.TestCase):
|
||||||
face = Cylinder(1, 1).faces().filter_by(GeomType.CYLINDER)[0]
|
face = Cylinder(1, 1).faces().filter_by(GeomType.CYLINDER)[0]
|
||||||
self.assertAlmostEqual(face.normal_at(0, 1), (1, 0, 0), 5)
|
self.assertAlmostEqual(face.normal_at(0, 1), (1, 0, 0), 5)
|
||||||
|
|
||||||
|
def test_location_at(self):
|
||||||
|
face = Face.make_rect(1, 1)
|
||||||
|
|
||||||
|
# Default center (u=0, v=0)
|
||||||
|
loc = face.location_at(0, 0)
|
||||||
|
self.assertAlmostEqual(loc.position, (-0.5, -0.5, 0), 5)
|
||||||
|
self.assertAlmostEqual(loc.z_axis.direction, (0, 0, 1), 5)
|
||||||
|
|
||||||
|
# Using surface_point instead of u,v
|
||||||
|
point = face.position_at(0, 0)
|
||||||
|
loc2 = face.location_at(point)
|
||||||
|
self.assertAlmostEqual(loc2.position, (-0.5, -0.5, 0), 5)
|
||||||
|
self.assertAlmostEqual(loc2.z_axis.direction, (0, 0, 1), 5)
|
||||||
|
|
||||||
|
# Bad args
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
face.location_at(0)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
face.location_at(center=(0, 0))
|
||||||
|
|
||||||
|
# Curved surface: verify z-direction is outward normal
|
||||||
|
face = Cylinder(1, 1).faces().filter_by(GeomType.CYLINDER)[0]
|
||||||
|
loc3 = face.location_at(0, 1)
|
||||||
|
self.assertAlmostEqual(loc3.z_axis.direction, (1, 0, 0), 5)
|
||||||
|
|
||||||
|
# Curved surface: verify center
|
||||||
|
face = Cylinder(1, 1).faces().filter_by(GeomType.CYLINDER)[0]
|
||||||
|
loc4 = face.location_at()
|
||||||
|
self.assertAlmostEqual(loc4.position, (-1, 0, 0), 5)
|
||||||
|
self.assertAlmostEqual(loc4.z_axis.direction, (-1, 0, 0), 5)
|
||||||
|
|
||||||
def test_without_holes(self):
|
def test_without_holes(self):
|
||||||
# Planar test
|
# Planar test
|
||||||
frame = (Rectangle(1, 1) - Rectangle(0.5, 0.5)).face()
|
frame = (Rectangle(1, 1) - Rectangle(0.5, 0.5)).face()
|
||||||
|
|
@ -876,7 +915,7 @@ class TestFace(unittest.TestCase):
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
surface.wrap(star.outer_wire(), target)
|
surface.wrap(star.outer_wire(), target)
|
||||||
|
|
||||||
@patch.object(Wire, "is_valid", return_value=False)
|
@patch.object(Wire, "is_valid", new_callable=PropertyMock, return_value=False)
|
||||||
def test_wrap_invalid_wire(self, mock_is_valid):
|
def test_wrap_invalid_wire(self, mock_is_valid):
|
||||||
surface = Cone(5, 2, 10).faces().filter_by(GeomType.PLANE, reverse=True)[0]
|
surface = Cone(5, 2, 10).faces().filter_by(GeomType.PLANE, reverse=True)[0]
|
||||||
target = surface.location_at(0.5, 0.5, x_dir=(1, 0, 0))
|
target = surface.location_at(0.5, 0.5, x_dir=(1, 0, 0))
|
||||||
|
|
@ -888,6 +927,22 @@ class TestFace(unittest.TestCase):
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
surface.wrap(star, target)
|
surface.wrap(star, target)
|
||||||
|
|
||||||
|
def test_wrap_faces(self):
|
||||||
|
sphere = Solid.make_sphere(50, angle1=-90).face()
|
||||||
|
surface = sphere.face()
|
||||||
|
path: Edge = (
|
||||||
|
sphere.cut(
|
||||||
|
Solid.make_cylinder(80, 100, Plane.YZ).locate(Location((-50, 0, -70)))
|
||||||
|
)
|
||||||
|
.edges()
|
||||||
|
.sort_by(Axis.Z)[0]
|
||||||
|
.reversed()
|
||||||
|
)
|
||||||
|
text = Text(txt="ei", font_size=15, align=(Align.MIN, Align.CENTER))
|
||||||
|
wrapped_faces = surface.wrap_faces(text.faces(), path, 0.2)
|
||||||
|
self.assertEqual(len(wrapped_faces), 3)
|
||||||
|
self.assertTrue(all(not f.is_planar_face for f in wrapped_faces))
|
||||||
|
|
||||||
def test_revolve(self):
|
def test_revolve(self):
|
||||||
l1 = Edge.make_line((3, 0), (3, 2))
|
l1 = Edge.make_line((3, 0), (3, 2))
|
||||||
revolved = Face.revolve(l1, 360, Axis.Y)
|
revolved = Face.revolve(l1, 360, Axis.Y)
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,11 @@ class TestImportExport(unittest.TestCase):
|
||||||
original_box = Solid.make_box(1, 1, 1)
|
original_box = Solid.make_box(1, 1, 1)
|
||||||
export_step(original_box, "test_box.step")
|
export_step(original_box, "test_box.step")
|
||||||
step_box = import_step("test_box.step")
|
step_box = import_step("test_box.step")
|
||||||
self.assertTrue(step_box.is_valid())
|
self.assertTrue(step_box.is_valid)
|
||||||
self.assertAlmostEqual(step_box.volume, 1, 5)
|
self.assertAlmostEqual(step_box.volume, 1, 5)
|
||||||
export_brep(step_box, "test_box.brep")
|
export_brep(step_box, "test_box.brep")
|
||||||
brep_box = import_brep("test_box.brep")
|
brep_box = import_brep("test_box.brep")
|
||||||
self.assertTrue(brep_box.is_valid())
|
self.assertTrue(brep_box.is_valid)
|
||||||
self.assertAlmostEqual(brep_box.volume, 1, 5)
|
self.assertAlmostEqual(brep_box.volume, 1, 5)
|
||||||
os.remove("test_box.step")
|
os.remove("test_box.step")
|
||||||
os.remove("test_box.brep")
|
os.remove("test_box.brep")
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ license:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import unittest
|
import unittest
|
||||||
from build123d.geometry import (
|
from build123d.geometry import (
|
||||||
Axis,
|
Axis,
|
||||||
|
|
@ -52,7 +51,7 @@ class TestGeomEncode(unittest.TestCase):
|
||||||
|
|
||||||
c_json = json.dumps(Color("red"), cls=GeomEncoder)
|
c_json = json.dumps(Color("red"), cls=GeomEncoder)
|
||||||
color = json.loads(c_json, object_hook=GeomEncoder.geometry_hook)
|
color = json.loads(c_json, object_hook=GeomEncoder.geometry_hook)
|
||||||
self.assertEqual(Color("red").to_tuple(), color.to_tuple())
|
self.assertEqual(tuple(Color("red")), tuple(color))
|
||||||
|
|
||||||
loc = Location((0, 1, 2), (4, 8, 16))
|
loc = Location((0, 1, 2), (4, 8, 16))
|
||||||
l_json = json.dumps(loc, cls=GeomEncoder)
|
l_json = json.dumps(loc, cls=GeomEncoder)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ license:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Always equal to any other object, to test that __eq__ cooperation is working
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
|
|
@ -34,7 +33,6 @@ import os
|
||||||
import unittest
|
import unittest
|
||||||
from random import uniform
|
from random import uniform
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from OCP.gp import (
|
from OCP.gp import (
|
||||||
gp_Ax1,
|
gp_Ax1,
|
||||||
gp_Dir,
|
gp_Dir,
|
||||||
|
|
@ -51,6 +49,8 @@ from build123d.topology import Edge, Solid, Vertex
|
||||||
|
|
||||||
|
|
||||||
class AlwaysEqual:
|
class AlwaysEqual:
|
||||||
|
"""Always equal to any other object, to test that __eq__ cooperation is working"""
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ class TestLocation(unittest.TestCase):
|
||||||
def test_location(self):
|
def test_location(self):
|
||||||
loc0 = Location()
|
loc0 = Location()
|
||||||
T = loc0.wrapped.Transformation().TranslationPart()
|
T = loc0.wrapped.Transformation().TranslationPart()
|
||||||
np.testing.assert_allclose((T.X(), T.Y(), T.Z()), (0, 0, 0), 1e-6)
|
self.assertAlmostEqual((T.X(), T.Y(), T.Z()), (0, 0, 0), 5)
|
||||||
angle = math.degrees(
|
angle = math.degrees(
|
||||||
loc0.wrapped.Transformation().GetRotation().GetRotationAngle()
|
loc0.wrapped.Transformation().GetRotation().GetRotationAngle()
|
||||||
)
|
)
|
||||||
|
|
@ -69,19 +69,19 @@ class TestLocation(unittest.TestCase):
|
||||||
loc0 = Location((0, 0, 1))
|
loc0 = Location((0, 0, 1))
|
||||||
|
|
||||||
T = loc0.wrapped.Transformation().TranslationPart()
|
T = loc0.wrapped.Transformation().TranslationPart()
|
||||||
np.testing.assert_allclose((T.X(), T.Y(), T.Z()), (0, 0, 1), 1e-6)
|
self.assertAlmostEqual((T.X(), T.Y(), T.Z()), (0, 0, 1), 5)
|
||||||
|
|
||||||
# List
|
# List
|
||||||
loc0 = Location([0, 0, 1])
|
loc0 = Location([0, 0, 1])
|
||||||
|
|
||||||
T = loc0.wrapped.Transformation().TranslationPart()
|
T = loc0.wrapped.Transformation().TranslationPart()
|
||||||
np.testing.assert_allclose((T.X(), T.Y(), T.Z()), (0, 0, 1), 1e-6)
|
self.assertAlmostEqual((T.X(), T.Y(), T.Z()), (0, 0, 1), 5)
|
||||||
|
|
||||||
# Vector
|
# Vector
|
||||||
loc1 = Location(Vector(0, 0, 1))
|
loc1 = Location(Vector(0, 0, 1))
|
||||||
|
|
||||||
T = loc1.wrapped.Transformation().TranslationPart()
|
T = loc1.wrapped.Transformation().TranslationPart()
|
||||||
np.testing.assert_allclose((T.X(), T.Y(), T.Z()), (0, 0, 1), 1e-6)
|
self.assertAlmostEqual((T.X(), T.Y(), T.Z()), (0, 0, 1), 5)
|
||||||
|
|
||||||
# rotation + translation
|
# rotation + translation
|
||||||
loc2 = Location(Vector(0, 0, 1), Vector(0, 0, 1), 45)
|
loc2 = Location(Vector(0, 0, 1), Vector(0, 0, 1), 45)
|
||||||
|
|
@ -103,13 +103,8 @@ class TestLocation(unittest.TestCase):
|
||||||
|
|
||||||
# Test creation from the OCP.gp.gp_Trsf object
|
# Test creation from the OCP.gp.gp_Trsf object
|
||||||
loc4 = Location(gp_Trsf())
|
loc4 = Location(gp_Trsf())
|
||||||
np.testing.assert_allclose(loc4.to_tuple()[0], (0, 0, 0), 1e-7)
|
self.assertAlmostEqual(tuple(loc4)[0], (0, 0, 0), 5)
|
||||||
np.testing.assert_allclose(loc4.to_tuple()[1], (0, 0, 0), 1e-7)
|
self.assertAlmostEqual(tuple(loc4)[1], (0, 0, 0), 5)
|
||||||
|
|
||||||
# Test creation from Plane and Vector
|
|
||||||
loc4 = Location(Plane.XY, (0, 0, 1))
|
|
||||||
np.testing.assert_allclose(loc4.to_tuple()[0], (0, 0, 1), 1e-7)
|
|
||||||
np.testing.assert_allclose(loc4.to_tuple()[1], (0, 0, 0), 1e-7)
|
|
||||||
|
|
||||||
# Test composition
|
# Test composition
|
||||||
loc4 = Location((0, 0, 0), Vector(0, 0, 1), 15)
|
loc4 = Location((0, 0, 0), Vector(0, 0, 1), 15)
|
||||||
|
|
@ -119,7 +114,7 @@ class TestLocation(unittest.TestCase):
|
||||||
loc7 = loc4**2
|
loc7 = loc4**2
|
||||||
|
|
||||||
T = loc5.wrapped.Transformation().TranslationPart()
|
T = loc5.wrapped.Transformation().TranslationPart()
|
||||||
np.testing.assert_allclose((T.X(), T.Y(), T.Z()), (0, 0, 1), 1e-6)
|
self.assertAlmostEqual((T.X(), T.Y(), T.Z()), (0, 0, 1), 5)
|
||||||
|
|
||||||
angle5 = math.degrees(
|
angle5 = math.degrees(
|
||||||
loc5.wrapped.Transformation().GetRotation().GetRotationAngle()
|
loc5.wrapped.Transformation().GetRotation().GetRotationAngle()
|
||||||
|
|
@ -165,21 +160,46 @@ class TestLocation(unittest.TestCase):
|
||||||
t.SetRotationPart(q)
|
t.SetRotationPart(q)
|
||||||
loc2 = Location(t)
|
loc2 = Location(t)
|
||||||
|
|
||||||
np.testing.assert_allclose(loc1.to_tuple()[0], loc2.to_tuple()[0], 1e-6)
|
self.assertAlmostEqual(tuple(loc1)[0], tuple(loc2)[0], 5)
|
||||||
np.testing.assert_allclose(loc1.to_tuple()[1], loc2.to_tuple()[1], 1e-6)
|
self.assertAlmostEqual(tuple(loc1)[1], tuple(loc2)[1], 5)
|
||||||
|
|
||||||
loc1 = Location((1, 2), 34)
|
loc1 = Location((1, 2), 34)
|
||||||
np.testing.assert_allclose(loc1.to_tuple()[0], (1, 2, 0), 1e-6)
|
self.assertAlmostEqual(tuple(loc1)[0], (1, 2, 0), 5)
|
||||||
np.testing.assert_allclose(loc1.to_tuple()[1], (0, 0, 34), 1e-6)
|
self.assertAlmostEqual(tuple(loc1)[1], (0, 0, 34), 5)
|
||||||
|
|
||||||
rot_angles = (-115.00, 35.00, -135.00)
|
rot_angles = (-115.00, 35.00, -135.00)
|
||||||
loc2 = Location((1, 2, 3), rot_angles)
|
loc2 = Location((1, 2, 3), rot_angles)
|
||||||
np.testing.assert_allclose(loc2.to_tuple()[0], (1, 2, 3), 1e-6)
|
self.assertAlmostEqual(tuple(loc2)[0], (1, 2, 3), 5)
|
||||||
np.testing.assert_allclose(loc2.to_tuple()[1], rot_angles, 1e-6)
|
self.assertAlmostEqual(tuple(loc2)[1], rot_angles, 5)
|
||||||
|
|
||||||
loc3 = Location(loc2)
|
loc3 = Location(loc2)
|
||||||
np.testing.assert_allclose(loc3.to_tuple()[0], (1, 2, 3), 1e-6)
|
self.assertAlmostEqual(tuple(loc3)[0], (1, 2, 3), 5)
|
||||||
np.testing.assert_allclose(loc3.to_tuple()[1], rot_angles, 1e-6)
|
self.assertAlmostEqual(tuple(loc3)[1], rot_angles, 5)
|
||||||
|
|
||||||
|
def test_location_kwarg_parameters(self):
|
||||||
|
loc = Location(position=(10, 20, 30))
|
||||||
|
self.assertAlmostEqual(loc.position, (10, 20, 30), 5)
|
||||||
|
|
||||||
|
loc = Location(position=(10, 20, 30), orientation=(10, 20, 30))
|
||||||
|
self.assertAlmostEqual(loc.position, (10, 20, 30), 5)
|
||||||
|
self.assertAlmostEqual(loc.orientation, (10, 20, 30), 5)
|
||||||
|
|
||||||
|
loc = Location(
|
||||||
|
position=(10, 20, 30), orientation=(90, 0, 90), ordering=Extrinsic.XYZ
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(loc.position, (10, 20, 30), 5)
|
||||||
|
self.assertAlmostEqual(loc.orientation, (0, 90, 90), 5)
|
||||||
|
|
||||||
|
loc = Location((10, 20, 30), orientation=(10, 20, 30))
|
||||||
|
self.assertAlmostEqual(loc.position, (10, 20, 30), 5)
|
||||||
|
self.assertAlmostEqual(loc.orientation, (10, 20, 30), 5)
|
||||||
|
|
||||||
|
loc = Location(plane=Plane.isometric)
|
||||||
|
self.assertAlmostEqual(loc.position, (0, 0, 0), 5)
|
||||||
|
self.assertAlmostEqual(loc.orientation, (45.00, 35.26, 30.00), 2)
|
||||||
|
|
||||||
|
loc = Location(location=Location())
|
||||||
|
self.assertAlmostEqual(loc.position, (0, 0, 0), 5)
|
||||||
|
|
||||||
def test_location_parameters(self):
|
def test_location_parameters(self):
|
||||||
loc = Location((10, 20, 30))
|
loc = Location((10, 20, 30))
|
||||||
|
|
@ -240,15 +260,16 @@ class TestLocation(unittest.TestCase):
|
||||||
loc1 = Location((1, 2, 3), (90, 45, 22.5))
|
loc1 = Location((1, 2, 3), (90, 45, 22.5))
|
||||||
loc2 = copy.copy(loc1)
|
loc2 = copy.copy(loc1)
|
||||||
loc3 = copy.deepcopy(loc1)
|
loc3 = copy.deepcopy(loc1)
|
||||||
self.assertAlmostEqual(loc1.position, loc2.position.to_tuple(), 6)
|
self.assertAlmostEqual(loc1.position, loc2.position, 6)
|
||||||
self.assertAlmostEqual(loc1.orientation, loc2.orientation.to_tuple(), 6)
|
self.assertAlmostEqual(loc1.orientation, loc2.orientation, 6)
|
||||||
self.assertAlmostEqual(loc1.position, loc3.position.to_tuple(), 6)
|
self.assertAlmostEqual(loc1.position, loc3.position, 6)
|
||||||
self.assertAlmostEqual(loc1.orientation, loc3.orientation.to_tuple(), 6)
|
self.assertAlmostEqual(loc1.orientation, loc3.orientation, 6)
|
||||||
|
|
||||||
def test_to_axis(self):
|
# deprecated
|
||||||
axis = Location((1, 2, 3), (-90, 0, 0)).to_axis()
|
# def test_to_axis(self):
|
||||||
self.assertAlmostEqual(axis.position, (1, 2, 3), 6)
|
# axis = Location((1, 2, 3), (-90, 0, 0)).to_axis()
|
||||||
self.assertAlmostEqual(axis.direction, (0, 1, 0), 6)
|
# self.assertAlmostEqual(axis.position, (1, 2, 3), 6)
|
||||||
|
# self.assertAlmostEqual(axis.direction, (0, 1, 0), 6)
|
||||||
|
|
||||||
def test_equal(self):
|
def test_equal(self):
|
||||||
loc = Location((1, 2, 3), (4, 5, 6))
|
loc = Location((1, 2, 3), (4, 5, 6))
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ from build123d.build_enums import (
|
||||||
from build123d.geometry import Axis, Location, Plane, Vector
|
from build123d.geometry import Axis, Location, Plane, Vector
|
||||||
from build123d.objects_curve import Polyline
|
from build123d.objects_curve import Polyline
|
||||||
from build123d.objects_part import Box, Cylinder
|
from build123d.objects_part import Box, Cylinder
|
||||||
from build123d.topology import Compound, Edge, Face, Wire
|
from build123d.operations_part import extrude
|
||||||
|
from build123d.operations_generic import fillet
|
||||||
|
from build123d.topology import Compound, Edge, Face, Solid, Wire
|
||||||
|
|
||||||
|
|
||||||
class TestMixin1D(unittest.TestCase):
|
class TestMixin1D(unittest.TestCase):
|
||||||
|
|
@ -53,10 +55,8 @@ class TestMixin1D(unittest.TestCase):
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
# Not sure what PARAMETER mode returns - but it's in the ballpark
|
# Not sure what PARAMETER mode returns - but it's in the ballpark
|
||||||
point = (
|
point = Edge.make_line((0, 0, 0), (1, 1, 1)).position_at(
|
||||||
Edge.make_line((0, 0, 0), (1, 1, 1))
|
0.5, position_mode=PositionMode.PARAMETER
|
||||||
.position_at(0.5, position_mode=PositionMode.PARAMETER)
|
|
||||||
.to_tuple()
|
|
||||||
)
|
)
|
||||||
self.assertTrue(all([0.0 < v < 1.0 for v in point]))
|
self.assertTrue(all([0.0 < v < 1.0 for v in point]))
|
||||||
|
|
||||||
|
|
@ -119,10 +119,8 @@ class TestMixin1D(unittest.TestCase):
|
||||||
(-1, 0, 0),
|
(-1, 0, 0),
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
tangent = (
|
tangent = Edge.make_circle(1, start_angle=0, end_angle=90).tangent_at(
|
||||||
Edge.make_circle(1, start_angle=0, end_angle=90)
|
0.0, position_mode=PositionMode.PARAMETER
|
||||||
.tangent_at(0.0, position_mode=PositionMode.PARAMETER)
|
|
||||||
.to_tuple()
|
|
||||||
)
|
)
|
||||||
self.assertTrue(all([0.0 <= v <= 1.0 for v in tangent]))
|
self.assertTrue(all([0.0 <= v <= 1.0 for v in tangent]))
|
||||||
|
|
||||||
|
|
@ -364,6 +362,29 @@ class TestMixin1D(unittest.TestCase):
|
||||||
wire = Wire.make_rect(1, 1)
|
wire = Wire.make_rect(1, 1)
|
||||||
self.assertAlmostEqual(wire.volume, 0, 5)
|
self.assertAlmostEqual(wire.volume, 0, 5)
|
||||||
|
|
||||||
|
def test_edges(self):
|
||||||
|
box = Solid.make_box(1, 1, 1)
|
||||||
|
top_x = box.faces().sort_by(Axis.Z)[-1].edges().sort_by(Axis.X)[-1]
|
||||||
|
self.assertEqual(top_x.topo_parent, box)
|
||||||
|
self.assertTrue(isinstance(top_x, Edge))
|
||||||
|
self.assertAlmostEqual(top_x.center(), (1, 0.5, 1), 5)
|
||||||
|
|
||||||
|
def test_edges_topo_parent(self):
|
||||||
|
phone_case_plan = Face.make_rect(80, 150) - Face.make_rect(
|
||||||
|
25, 25, Plane((-20, 55))
|
||||||
|
)
|
||||||
|
phone_case = extrude(phone_case_plan, 2)
|
||||||
|
window_edges = phone_case.faces().sort_by(Axis.Z)[-1].inner_wires()[0].edges()
|
||||||
|
for e in window_edges:
|
||||||
|
self.assertEqual(e.topo_parent, phone_case)
|
||||||
|
phone_case_f = fillet(window_edges, 1)
|
||||||
|
self.assertLess(phone_case_f.volume, phone_case.volume)
|
||||||
|
perimeter = phone_case_f.faces().sort_by(Axis.Z)[-1].outer_wire().edges()
|
||||||
|
for e in perimeter:
|
||||||
|
self.assertEqual(e.topo_parent, phone_case_f)
|
||||||
|
phone_case_ff = fillet(perimeter, 1)
|
||||||
|
self.assertLess(phone_case_ff.volume, phone_case_f.volume)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ license:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch, PropertyMock
|
||||||
|
|
||||||
from build123d.build_enums import CenterOf, Kind
|
from build123d.build_enums import CenterOf, Kind
|
||||||
from build123d.geometry import Axis, Plane
|
from build123d.geometry import Axis, Plane
|
||||||
|
|
@ -67,7 +67,7 @@ class TestMixin3D(unittest.TestCase):
|
||||||
face = box.faces().sort_by(Axis.Z)[0]
|
face = box.faces().sort_by(Axis.Z)[0]
|
||||||
self.assertRaises(ValueError, box.chamfer, 0.1, None, edge, face=face)
|
self.assertRaises(ValueError, box.chamfer, 0.1, None, edge, face=face)
|
||||||
|
|
||||||
@patch.object(Shape, "is_valid", return_value=False)
|
@patch.object(Shape, "is_valid", new_callable=PropertyMock, return_value=False)
|
||||||
def test_chamfer_invalid_shape_raises_error(self, mock_is_valid):
|
def test_chamfer_invalid_shape_raises_error(self, mock_is_valid):
|
||||||
box = Solid.make_box(1, 1, 1)
|
box = Solid.make_box(1, 1, 1)
|
||||||
|
|
||||||
|
|
@ -111,7 +111,7 @@ class TestMixin3D(unittest.TestCase):
|
||||||
d = Solid.make_box(1, 1, 1, Plane((-0.5, -0.5, 0))).dprism(
|
d = Solid.make_box(1, 1, 1, Plane((-0.5, -0.5, 0))).dprism(
|
||||||
None, [f], additive=False
|
None, [f], additive=False
|
||||||
)
|
)
|
||||||
self.assertTrue(d.is_valid())
|
self.assertTrue(d.is_valid)
|
||||||
self.assertAlmostEqual(d.volume, 1 - 0.5**2, 5)
|
self.assertAlmostEqual(d.volume, 1 - 0.5**2, 5)
|
||||||
|
|
||||||
# face with depth
|
# face with depth
|
||||||
|
|
@ -119,7 +119,7 @@ class TestMixin3D(unittest.TestCase):
|
||||||
d = Solid.make_box(1, 1, 1, Plane((-0.5, -0.5, 0))).dprism(
|
d = Solid.make_box(1, 1, 1, Plane((-0.5, -0.5, 0))).dprism(
|
||||||
None, [f], depth=0.5, thru_all=False, additive=False
|
None, [f], depth=0.5, thru_all=False, additive=False
|
||||||
)
|
)
|
||||||
self.assertTrue(d.is_valid())
|
self.assertTrue(d.is_valid)
|
||||||
self.assertAlmostEqual(d.volume, 1 - 0.5**3, 5)
|
self.assertAlmostEqual(d.volume, 1 - 0.5**3, 5)
|
||||||
|
|
||||||
# face until
|
# face until
|
||||||
|
|
@ -128,7 +128,7 @@ class TestMixin3D(unittest.TestCase):
|
||||||
d = Solid.make_box(1, 1, 1, Plane((-0.5, -0.5, 0))).dprism(
|
d = Solid.make_box(1, 1, 1, Plane((-0.5, -0.5, 0))).dprism(
|
||||||
None, [f], up_to_face=limit, thru_all=False, additive=False
|
None, [f], up_to_face=limit, thru_all=False, additive=False
|
||||||
)
|
)
|
||||||
self.assertTrue(d.is_valid())
|
self.assertTrue(d.is_valid)
|
||||||
self.assertAlmostEqual(d.volume, 1 - 0.5**3, 5)
|
self.assertAlmostEqual(d.volume, 1 - 0.5**3, 5)
|
||||||
|
|
||||||
# wire
|
# wire
|
||||||
|
|
@ -136,7 +136,7 @@ class TestMixin3D(unittest.TestCase):
|
||||||
d = Solid.make_box(1, 1, 1, Plane((-0.5, -0.5, 0))).dprism(
|
d = Solid.make_box(1, 1, 1, Plane((-0.5, -0.5, 0))).dprism(
|
||||||
None, [w], additive=False
|
None, [w], additive=False
|
||||||
)
|
)
|
||||||
self.assertTrue(d.is_valid())
|
self.assertTrue(d.is_valid)
|
||||||
self.assertAlmostEqual(d.volume, 1 - 0.5**2, 5)
|
self.assertAlmostEqual(d.volume, 1 - 0.5**2, 5)
|
||||||
|
|
||||||
def test_center(self):
|
def test_center(self):
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,8 @@ class TestPlane(unittest.TestCase):
|
||||||
Plane()
|
Plane()
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
Plane(o, z_dir="up")
|
Plane(o, z_dir="up")
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Plane(o, forward="up")
|
||||||
|
|
||||||
# rotated location around z
|
# rotated location around z
|
||||||
loc = Location((0, 0, 0), (0, 0, 45))
|
loc = Location((0, 0, 0), (0, 0, 45))
|
||||||
|
|
@ -211,6 +213,54 @@ class TestPlane(unittest.TestCase):
|
||||||
self.assertAlmostEqual(p.y_dir, expected[i][1], 6)
|
self.assertAlmostEqual(p.y_dir, expected[i][1], 6)
|
||||||
self.assertAlmostEqual(p.z_dir, expected[i][2], 6)
|
self.assertAlmostEqual(p.z_dir, expected[i][2], 6)
|
||||||
|
|
||||||
|
def test_plane_from_axis(self):
|
||||||
|
origin = Vector(1, 2, 3)
|
||||||
|
direction = Vector(0, 0, 1)
|
||||||
|
axis = Axis(origin, direction)
|
||||||
|
plane = Plane(axis)
|
||||||
|
|
||||||
|
self.assertEqual(plane.origin, origin)
|
||||||
|
self.assertTrue(plane.z_dir, direction.normalized())
|
||||||
|
self.assertAlmostEqual(plane.x_dir.length, 1.0, places=12)
|
||||||
|
self.assertAlmostEqual(plane.y_dir.length, 1.0, places=12)
|
||||||
|
self.assertAlmostEqual(plane.z_dir.length, 1.0, places=12)
|
||||||
|
|
||||||
|
def test_plane_from_axis_with_x_dir(self):
|
||||||
|
origin = Vector(0, 0, 0)
|
||||||
|
z_dir = Vector(0, 0, 1)
|
||||||
|
x_dir = Vector(1, 0, 0)
|
||||||
|
axis = Axis(origin, z_dir)
|
||||||
|
plane = Plane(axis, x_dir)
|
||||||
|
|
||||||
|
self.assertEqual(plane.origin, origin)
|
||||||
|
self.assertEqual(plane.z_dir, z_dir.normalized())
|
||||||
|
self.assertEqual(plane.x_dir, x_dir.normalized())
|
||||||
|
self.assertEqual(plane.y_dir, z_dir.cross(x_dir).normalized())
|
||||||
|
|
||||||
|
def test_plane_from_axis_with_kwargs(self):
|
||||||
|
axis = Axis((0, 0, 0), (0, 1, 0))
|
||||||
|
x_dir = Vector(1, 0, 0)
|
||||||
|
plane = Plane(axis=axis, x_dir=x_dir)
|
||||||
|
|
||||||
|
self.assertEqual(plane.z_dir, Vector(0, 1, 0))
|
||||||
|
self.assertEqual(plane.x_dir, x_dir.normalized())
|
||||||
|
|
||||||
|
def test_plane_from_axis_without_x_dir(self):
|
||||||
|
axis = Axis((0, 0, 0), (1, 0, 0))
|
||||||
|
plane = Plane(axis)
|
||||||
|
|
||||||
|
self.assertEqual(plane.z_dir, Vector(1, 0, 0))
|
||||||
|
self.assertAlmostEqual(plane.x_dir.length, 1.0, places=12)
|
||||||
|
self.assertAlmostEqual(plane.y_dir.length, 1.0, places=12)
|
||||||
|
self.assertGreater(plane.z_dir.cross(plane.x_dir).dot(plane.y_dir), 0.99)
|
||||||
|
|
||||||
|
def test_plane_from_axis_invalid_x_dir(self):
|
||||||
|
axis = Axis((0, 0, 0), (0, 0, 1))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Plane(axis, x_dir=(0, 0, 0))
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Plane(axis, "front")
|
||||||
|
|
||||||
def test_plane_neg(self):
|
def test_plane_neg(self):
|
||||||
p = Plane(
|
p = Plane(
|
||||||
origin=(1, 2, 3),
|
origin=(1, 2, 3),
|
||||||
|
|
@ -273,11 +323,13 @@ class TestPlane(unittest.TestCase):
|
||||||
np.testing.assert_allclose(target_point, local_box_vertices[i], 1e-7)
|
np.testing.assert_allclose(target_point, local_box_vertices[i], 1e-7)
|
||||||
|
|
||||||
def test_localize_vertex(self):
|
def test_localize_vertex(self):
|
||||||
vertex = Vertex(random.random(), random.random(), random.random())
|
v_x, v_y, v_z = (random.random(), random.random(), random.random())
|
||||||
np.testing.assert_allclose(
|
vertex = Vertex(v_x, v_y, v_z)
|
||||||
Plane.YZ.to_local_coords(vertex).to_tuple(),
|
self.assertAlmostEqual(
|
||||||
Plane.YZ.to_local_coords(Vector(vertex)).to_tuple(),
|
Plane.YZ.to_local_coords(Vector(vertex)), (v_y, v_z, v_x), 5
|
||||||
5,
|
)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
Vector(Plane.YZ.to_local_coords(vertex)), (v_y, v_z, v_x), 5
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_repr(self):
|
def test_repr(self):
|
||||||
|
|
|
||||||
|
|
@ -94,10 +94,6 @@ class TestProjection(unittest.TestCase):
|
||||||
self.assertAlmostEqual(projection[0].position_at(0), (0, 1, 0), 5)
|
self.assertAlmostEqual(projection[0].position_at(0), (0, 1, 0), 5)
|
||||||
self.assertAlmostEqual(projection[0].arc_center, (0, 0, 0), 5)
|
self.assertAlmostEqual(projection[0].arc_center, (0, 0, 0), 5)
|
||||||
|
|
||||||
def test_to_axis(self):
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
Edge.make_circle(1, end_angle=30).to_axis()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,11 @@ license:
|
||||||
# Always equal to any other object, to test that __eq__ cooperation is working
|
# Always equal to any other object, to test that __eq__ cooperation is working
|
||||||
import unittest
|
import unittest
|
||||||
from random import uniform
|
from random import uniform
|
||||||
from unittest.mock import patch
|
from unittest.mock import PropertyMock, patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from build123d.build_enums import CenterOf, Keep
|
from anytree import PreOrderIter
|
||||||
|
from build123d.build_enums import CenterOf, GeomType, Keep
|
||||||
from build123d.geometry import (
|
from build123d.geometry import (
|
||||||
Axis,
|
Axis,
|
||||||
Color,
|
Color,
|
||||||
|
|
@ -43,7 +44,7 @@ from build123d.geometry import (
|
||||||
Rotation,
|
Rotation,
|
||||||
Vector,
|
Vector,
|
||||||
)
|
)
|
||||||
from build123d.objects_part import Box, Cylinder
|
from build123d.objects_part import Box, Cone, Cylinder, Sphere
|
||||||
from build123d.objects_sketch import Circle
|
from build123d.objects_sketch import Circle
|
||||||
from build123d.operations_part import extrude
|
from build123d.operations_part import extrude
|
||||||
from build123d.topology import (
|
from build123d.topology import (
|
||||||
|
|
@ -100,7 +101,7 @@ class TestShape(unittest.TestCase):
|
||||||
Shape.combined_center(objs, center_of=CenterOf.GEOMETRY)
|
Shape.combined_center(objs, center_of=CenterOf.GEOMETRY)
|
||||||
|
|
||||||
def test_shape_type(self):
|
def test_shape_type(self):
|
||||||
self.assertEqual(Vertex().shape_type(), "Vertex")
|
self.assertEqual(Vertex().shape_type, "Vertex")
|
||||||
|
|
||||||
def test_scale(self):
|
def test_scale(self):
|
||||||
self.assertAlmostEqual(Solid.make_box(1, 1, 1).scale(2).volume, 2**3, 5)
|
self.assertAlmostEqual(Solid.make_box(1, 1, 1).scale(2).volume, 2**3, 5)
|
||||||
|
|
@ -109,10 +110,10 @@ class TestShape(unittest.TestCase):
|
||||||
box1 = Solid.make_box(1, 1, 1)
|
box1 = Solid.make_box(1, 1, 1)
|
||||||
box2 = Solid.make_box(1, 1, 1, Plane((1, 0, 0)))
|
box2 = Solid.make_box(1, 1, 1, Plane((1, 0, 0)))
|
||||||
combined = box1.fuse(box2, glue=True)
|
combined = box1.fuse(box2, glue=True)
|
||||||
self.assertTrue(combined.is_valid())
|
self.assertTrue(combined.is_valid)
|
||||||
self.assertAlmostEqual(combined.volume, 2, 5)
|
self.assertAlmostEqual(combined.volume, 2, 5)
|
||||||
fuzzy = box1.fuse(box2, tol=1e-6)
|
fuzzy = box1.fuse(box2, tol=1e-6)
|
||||||
self.assertTrue(fuzzy.is_valid())
|
self.assertTrue(fuzzy.is_valid)
|
||||||
self.assertAlmostEqual(fuzzy.volume, 2, 5)
|
self.assertAlmostEqual(fuzzy.volume, 2, 5)
|
||||||
|
|
||||||
def test_faces_intersected_by_axis(self):
|
def test_faces_intersected_by_axis(self):
|
||||||
|
|
@ -245,7 +246,7 @@ class TestShape(unittest.TestCase):
|
||||||
# invalid_object = box.fillet(0.75, box.edges())
|
# invalid_object = box.fillet(0.75, box.edges())
|
||||||
# invalid_object.max_fillet(invalid_object.edges())
|
# invalid_object.max_fillet(invalid_object.edges())
|
||||||
|
|
||||||
@patch.object(Shape, "is_valid", return_value=False)
|
@patch.object(Shape, "is_valid", new_callable=PropertyMock, return_value=False)
|
||||||
def test_max_fillet_invalid_shape_raises_error(self, mock_is_valid):
|
def test_max_fillet_invalid_shape_raises_error(self, mock_is_valid):
|
||||||
box = Solid.make_box(1, 1, 1)
|
box = Solid.make_box(1, 1, 1)
|
||||||
|
|
||||||
|
|
@ -317,8 +318,8 @@ class TestShape(unittest.TestCase):
|
||||||
c0 = Edge.make_circle(1).locate(Location((0, 2.1, 0)))
|
c0 = Edge.make_circle(1).locate(Location((0, 2.1, 0)))
|
||||||
c1 = Edge.make_circle(1)
|
c1 = Edge.make_circle(1)
|
||||||
closest = c0.closest_points(c1)
|
closest = c0.closest_points(c1)
|
||||||
self.assertAlmostEqual(closest[0], c0.position_at(0.75).to_tuple(), 5)
|
self.assertAlmostEqual(closest[0], c0.position_at(0.75), 5)
|
||||||
self.assertAlmostEqual(closest[1], c1.position_at(0.25).to_tuple(), 5)
|
self.assertAlmostEqual(closest[1], c1.position_at(0.25), 5)
|
||||||
|
|
||||||
def test_distance_to(self):
|
def test_distance_to(self):
|
||||||
c0 = Edge.make_circle(1).locate(Location((0, 2.1, 0)))
|
c0 = Edge.make_circle(1).locate(Location((0, 2.1, 0)))
|
||||||
|
|
@ -347,19 +348,19 @@ class TestShape(unittest.TestCase):
|
||||||
obj = Solid()
|
obj = Solid()
|
||||||
self.assertIs(obj, obj.clean())
|
self.assertIs(obj, obj.clean())
|
||||||
|
|
||||||
def test_relocate(self):
|
# def test_relocate(self):
|
||||||
box = Solid.make_box(10, 10, 10).move(Location((20, -5, -5)))
|
# box = Solid.make_box(10, 10, 10).move(Location((20, -5, -5)))
|
||||||
cylinder = Solid.make_cylinder(2, 50).move(Location((0, 0, 0), (0, 90, 0)))
|
# cylinder = Solid.make_cylinder(2, 50).move(Location((0, 0, 0), (0, 90, 0)))
|
||||||
|
|
||||||
box_with_hole = box.cut(cylinder)
|
# box_with_hole = box.cut(cylinder)
|
||||||
box_with_hole.relocate(box.location)
|
# box_with_hole.relocate(box.location)
|
||||||
|
|
||||||
self.assertEqual(box.location, box_with_hole.location)
|
# self.assertEqual(box.location, box_with_hole.location)
|
||||||
|
|
||||||
bbox1 = box.bounding_box()
|
# bbox1 = box.bounding_box()
|
||||||
bbox2 = box_with_hole.bounding_box()
|
# bbox2 = box_with_hole.bounding_box()
|
||||||
self.assertAlmostEqual(bbox1.min, bbox2.min, 5)
|
# self.assertAlmostEqual(bbox1.min, bbox2.min, 5)
|
||||||
self.assertAlmostEqual(bbox1.max, bbox2.max, 5)
|
# self.assertAlmostEqual(bbox1.max, bbox2.max, 5)
|
||||||
|
|
||||||
def test_project_to_viewport(self):
|
def test_project_to_viewport(self):
|
||||||
# Basic test
|
# Basic test
|
||||||
|
|
@ -459,44 +460,56 @@ class TestShape(unittest.TestCase):
|
||||||
def test_ocp_section(self):
|
def test_ocp_section(self):
|
||||||
# Vertex
|
# Vertex
|
||||||
verts, edges = Vertex(1, 2, 0)._ocp_section(Vertex(1, 2, 0))
|
verts, edges = Vertex(1, 2, 0)._ocp_section(Vertex(1, 2, 0))
|
||||||
self.assertListEqual(verts, []) # ?
|
self.assertEqual(len(verts), 1)
|
||||||
self.assertListEqual(edges, [])
|
self.assertEqual(len(edges), 0)
|
||||||
|
self.assertAlmostEqual(Vector(verts[0]), (1, 2, 0), 5)
|
||||||
|
|
||||||
verts, edges = Vertex(1, 2, 0)._ocp_section(Edge.make_line((0, 0), (2, 4)))
|
verts, edges = Vertex(1, 2, 0)._ocp_section(Edge.make_line((0, 0), (2, 4)))
|
||||||
self.assertListEqual(verts, []) # ?
|
self.assertEqual(len(verts), 1)
|
||||||
self.assertListEqual(edges, [])
|
self.assertEqual(len(edges), 0)
|
||||||
|
self.assertAlmostEqual(Vector(verts[0]), (1, 2, 0), 5)
|
||||||
|
|
||||||
verts, edges = Vertex(1, 2, 0)._ocp_section(Face.make_rect(5, 5))
|
verts, edges = Vertex(1, 2, 0)._ocp_section(Face.make_rect(5, 5))
|
||||||
np.testing.assert_allclose(tuple(verts[0]), (1, 2, 0), 1e-5)
|
self.assertAlmostEqual(Vector(verts[0]), (1, 2, 0), 5)
|
||||||
self.assertListEqual(edges, [])
|
self.assertListEqual(edges, [])
|
||||||
|
|
||||||
verts, edges = Vertex(1, 2, 0)._ocp_section(Face.make_plane(Plane.XY))
|
verts, edges = Vertex(1, 2, 0)._ocp_section(Face.make_plane(Plane.XY))
|
||||||
np.testing.assert_allclose(tuple(verts[0]), (1, 2, 0), 1e-5)
|
self.assertAlmostEqual(Vector(verts[0]), (1, 2, 0), 5)
|
||||||
self.assertListEqual(edges, [])
|
self.assertListEqual(edges, [])
|
||||||
|
|
||||||
# spline = Spline((-10, 10, -10), (-10, -5, -5), (20, 0, 5))
|
cylinder = Face.extrude(Edge.make_circle(5, Plane.XY.offset(-10)), (0, 0, 20))
|
||||||
# cylinder = Pos(Z=-10) * extrude(Circle(5), 20)
|
cylinder2 = Face.extrude(Edge.make_circle(5, Plane.YZ.offset(-10)), (20, 0, 0))
|
||||||
# cylinder2 = (Rot((0, 90, 0)) * cylinder).face()
|
pln = Plane.XY
|
||||||
# pln = Plane.XY
|
|
||||||
# box1 = Box(10, 10, 10, align=(Align.CENTER, Align.CENTER, Align.MIN))
|
|
||||||
# box2 = Pos(Z=-10) * box1
|
|
||||||
|
|
||||||
# # vertices, edges = ocp_section(spline, Face.make_rect(1e6, 1e6, pln))
|
v_edge = Edge.make_line((-5, 0, -20), (-5, 0, 20))
|
||||||
# vertices1, edges1 = spline.ocp_section(Face.make_plane(pln))
|
vertices1, edges1 = cylinder._ocp_section(v_edge)
|
||||||
# print(vertices1, edges1)
|
vertices1 = ShapeList(vertices1).sort_by(Axis.Z)
|
||||||
|
self.assertEqual(len(vertices1), 2)
|
||||||
|
|
||||||
# vertices2, edges2 = cylinder.ocp_section(Face.make_plane(pln))
|
self.assertAlmostEqual(Vector(vertices1[0]), (-5, 0, -10), 5)
|
||||||
# print(vertices2, edges2)
|
self.assertAlmostEqual(Vector(vertices1[1]), (-5, 0, 10), 5)
|
||||||
|
self.assertEqual(len(edges1), 1)
|
||||||
|
self.assertAlmostEqual(edges1[0].length, 20, 5)
|
||||||
|
|
||||||
# vertices3, edges3 = cylinder2.ocp_section(Face.make_plane(pln))
|
vertices2, edges2 = cylinder._ocp_section(Face.make_plane(pln))
|
||||||
# print(vertices3, edges3)
|
self.assertEqual(len(vertices2), 1)
|
||||||
|
self.assertEqual(len(edges2), 1)
|
||||||
|
self.assertAlmostEqual(Vector(vertices2[0]), (5, 0, 0), 5)
|
||||||
|
self.assertEqual(edges2[0].geom_type, GeomType.CIRCLE)
|
||||||
|
self.assertAlmostEqual(edges2[0].radius, 5, 5)
|
||||||
|
|
||||||
# # vertices4, edges4 = cylinder2.ocp_section(cylinder)
|
vertices4, edges4 = cylinder2._ocp_section(cylinder)
|
||||||
|
self.assertGreaterEqual(len(vertices4), 0)
|
||||||
|
self.assertGreaterEqual(len(edges4), 2)
|
||||||
|
self.assertTrue(all(e.geom_type == GeomType.ELLIPSE for e in edges4))
|
||||||
|
|
||||||
# vertices5, edges5 = box1.ocp_section(Face.make_plane(pln))
|
cylinder3 = Cylinder(5, 20).solid()
|
||||||
# print(vertices5, edges5)
|
cylinder4 = Rotation(0, 90, 0) * cylinder3
|
||||||
|
|
||||||
# vertices6, edges6 = box1.ocp_section(box2.faces().sort_by(Axis.Z)[-1])
|
vertices5, edges5 = cylinder3._ocp_section(cylinder4)
|
||||||
|
self.assertGreaterEqual(len(vertices5), 0)
|
||||||
|
self.assertGreaterEqual(len(edges5), 2)
|
||||||
|
self.assertTrue(all(e.geom_type == GeomType.ELLIPSE for e in edges5))
|
||||||
|
|
||||||
def test_copy_attributes_to(self):
|
def test_copy_attributes_to(self):
|
||||||
box = Box(1, 1, 1)
|
box = Box(1, 1, 1)
|
||||||
|
|
@ -526,7 +539,7 @@ class TestShape(unittest.TestCase):
|
||||||
self.assertEqual(hash(empty), 0)
|
self.assertEqual(hash(empty), 0)
|
||||||
self.assertFalse(empty.is_same(Solid()))
|
self.assertFalse(empty.is_same(Solid()))
|
||||||
self.assertFalse(empty.is_equal(Solid()))
|
self.assertFalse(empty.is_equal(Solid()))
|
||||||
self.assertTrue(empty.is_valid())
|
self.assertTrue(empty.is_valid)
|
||||||
empty_bbox = empty.bounding_box()
|
empty_bbox = empty.bounding_box()
|
||||||
self.assertEqual(tuple(empty_bbox.size), (0, 0, 0))
|
self.assertEqual(tuple(empty_bbox.size), (0, 0, 0))
|
||||||
self.assertIs(empty, empty.mirror(Plane.XY))
|
self.assertIs(empty, empty.mirror(Plane.XY))
|
||||||
|
|
@ -560,10 +573,10 @@ class TestShape(unittest.TestCase):
|
||||||
empty.moved(Location())
|
empty.moved(Location())
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
box.moved(empty_loc)
|
box.moved(empty_loc)
|
||||||
with self.assertRaises(ValueError):
|
# with self.assertRaises(ValueError):
|
||||||
empty.relocate(Location())
|
# empty.relocate(Location())
|
||||||
with self.assertRaises(ValueError):
|
# with self.assertRaises(ValueError):
|
||||||
box.relocate(empty_loc)
|
# box.relocate(empty_loc)
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
empty.distance_to(Vector(1, 1, 1))
|
empty.distance_to(Vector(1, 1, 1))
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
|
|
@ -615,5 +628,57 @@ class TestShape(unittest.TestCase):
|
||||||
self.assertIsNone(Vertex(1, 1, 1).compound())
|
self.assertIsNone(Vertex(1, 1, 1).compound())
|
||||||
|
|
||||||
|
|
||||||
|
class TestGlobalLocation(unittest.TestCase):
|
||||||
|
def test_global_location_hierarchy(self):
|
||||||
|
# Create a hierarchy: root → child → grandchild
|
||||||
|
root = Box(1, 1, 1)
|
||||||
|
root.location = Location((10, 0, 0))
|
||||||
|
|
||||||
|
child = Box(1, 1, 1)
|
||||||
|
child.location = Location((0, 20, 0))
|
||||||
|
child.parent = root
|
||||||
|
|
||||||
|
grandchild = Box(1, 1, 1)
|
||||||
|
grandchild.location = Location((0, 0, 30))
|
||||||
|
grandchild.parent = child
|
||||||
|
|
||||||
|
# Compute expected global location manually
|
||||||
|
expected_location = root.location * child.location * grandchild.location
|
||||||
|
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
grandchild.global_location.position, expected_location.position
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
grandchild.global_location.orientation, expected_location.orientation
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_global_location_in_assembly(self):
|
||||||
|
cone = Cone(2, 1, 3)
|
||||||
|
cone.label = "Cone"
|
||||||
|
box = Box(1, 2, 3)
|
||||||
|
box.label = "Box"
|
||||||
|
sphere = Sphere(1)
|
||||||
|
sphere.label = "Sphere"
|
||||||
|
|
||||||
|
assembly1 = Compound(label="Assembly1", children=[cone])
|
||||||
|
assembly1.move(Location((3, 3, 3), (90, 0, 0)))
|
||||||
|
assembly2 = Compound(label="Assembly2", children=[assembly1, box])
|
||||||
|
assembly2.move(Location((2, 4, 6), (0, 0, 90)))
|
||||||
|
assembly3 = Compound(label="Assembly3", children=[assembly2, sphere])
|
||||||
|
assembly3.move(Location((3, 6, 9)))
|
||||||
|
deep_shape: Shape = next(
|
||||||
|
iter(PreOrderIter(assembly3, filter_=lambda n: n.label in ("Cone")))
|
||||||
|
)
|
||||||
|
# print(deep_shape.path)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
deep_shape.global_location.position, (2, 13, 18), places=6
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
deep_shape.global_location.orientation, (0, 90, 90), places=6
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
from ocp_vscode import show
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import math
|
||||||
import re
|
import re
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from IPython.lib import pretty
|
from IPython.lib import pretty
|
||||||
from build123d.build_common import GridLocations, PolarLocations
|
from build123d.build_common import GridLocations, PolarLocations
|
||||||
from build123d.build_enums import GeomType, SortBy
|
from build123d.build_enums import GeomType, SortBy
|
||||||
|
|
@ -64,7 +63,9 @@ class TestShapeList(unittest.TestCase):
|
||||||
actual_lines = actual.splitlines()
|
actual_lines = actual.splitlines()
|
||||||
self.assertEqual(len(actual_lines), len(expected_lines))
|
self.assertEqual(len(actual_lines), len(expected_lines))
|
||||||
for actual_line, expected_line in zip(actual_lines, expected_lines):
|
for actual_line, expected_line in zip(actual_lines, expected_lines):
|
||||||
start, end = re.split(r"at 0x[0-9a-f]+", expected_line, maxsplit=2, flags=re.I)
|
start, end = re.split(
|
||||||
|
r"at 0x[0-9a-f]+", expected_line, maxsplit=2, flags=re.I
|
||||||
|
)
|
||||||
self.assertTrue(actual_line.startswith(start))
|
self.assertTrue(actual_line.startswith(start))
|
||||||
self.assertTrue(actual_line.endswith(end))
|
self.assertTrue(actual_line.endswith(end))
|
||||||
|
|
||||||
|
|
@ -302,7 +303,7 @@ class TestShapeList(unittest.TestCase):
|
||||||
|
|
||||||
def test_vertex(self):
|
def test_vertex(self):
|
||||||
sl = ShapeList([Edge.make_circle(1)])
|
sl = ShapeList([Edge.make_circle(1)])
|
||||||
np.testing.assert_allclose(sl.vertex().to_tuple(), (1, 0, 0), 1e-5)
|
self.assertAlmostEqual(tuple(sl.vertex()), (1, 0, 0), 5)
|
||||||
sl = ShapeList([Face.make_rect(1, 1), Face.make_rect(1, 1, Plane((4, 4)))])
|
sl = ShapeList([Face.make_rect(1, 1), Face.make_rect(1, 1, Plane((4, 4)))])
|
||||||
with self.assertWarns(UserWarning):
|
with self.assertWarns(UserWarning):
|
||||||
sl.vertex()
|
sl.vertex()
|
||||||
|
|
@ -403,5 +404,59 @@ class TestShapeList(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShapeListAddition(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Create distinct faces to test with
|
||||||
|
self.face1 = Box(1, 1, 1).faces().sort_by(Axis.Z)[0] # bottom face
|
||||||
|
self.face2 = Box(1, 1, 1).faces().sort_by(Axis.Z)[-1] # top face
|
||||||
|
self.face3 = Box(1, 1, 1).faces().sort_by(Axis.X)[0] # side face
|
||||||
|
|
||||||
|
def test_add_single_shape(self):
|
||||||
|
sl = ShapeList([self.face1])
|
||||||
|
result = sl + self.face2
|
||||||
|
self.assertIsInstance(result, ShapeList)
|
||||||
|
self.assertEqual(len(result), 2)
|
||||||
|
self.assertIn(self.face1, result)
|
||||||
|
self.assertIn(self.face2, result)
|
||||||
|
|
||||||
|
def test_add_shape_list(self):
|
||||||
|
sl1 = ShapeList([self.face1])
|
||||||
|
sl2 = ShapeList([self.face2, self.face3])
|
||||||
|
result = sl1 + sl2
|
||||||
|
self.assertIsInstance(result, ShapeList)
|
||||||
|
self.assertEqual(len(result), 3)
|
||||||
|
self.assertListEqual(result, [self.face1, self.face2, self.face3])
|
||||||
|
|
||||||
|
def test_iadd_single_shape(self):
|
||||||
|
sl = ShapeList([self.face1])
|
||||||
|
sl_id_before = id(sl)
|
||||||
|
sl += self.face2
|
||||||
|
self.assertEqual(id(sl), sl_id_before) # in-place mutation
|
||||||
|
self.assertEqual(len(sl), 2)
|
||||||
|
self.assertListEqual(sl, [self.face1, self.face2])
|
||||||
|
|
||||||
|
def test_iadd_shape_list(self):
|
||||||
|
sl = ShapeList([self.face1])
|
||||||
|
sl += ShapeList([self.face2, self.face3])
|
||||||
|
self.assertEqual(len(sl), 3)
|
||||||
|
self.assertListEqual(sl, [self.face1, self.face2, self.face3])
|
||||||
|
|
||||||
|
def test_add_vector(self):
|
||||||
|
vector = Vector(1, 2, 3)
|
||||||
|
sl = ShapeList([vector])
|
||||||
|
sl += Vector(4, 5, 6)
|
||||||
|
self.assertEqual(len(sl), 2)
|
||||||
|
self.assertIsInstance(sl[0], Vector)
|
||||||
|
self.assertIsInstance(sl[1], Vector)
|
||||||
|
|
||||||
|
def test_add_invalid_type(self):
|
||||||
|
sl = ShapeList([self.face1])
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
_ = sl + 123 # type: ignore
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
sl += "not a shape" # type: ignore
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,12 @@ class TestShells(unittest.TestCase):
|
||||||
def test_shell_init(self):
|
def test_shell_init(self):
|
||||||
box_faces = Solid.make_box(1, 1, 1).faces()
|
box_faces = Solid.make_box(1, 1, 1).faces()
|
||||||
box_shell = Shell(box_faces)
|
box_shell = Shell(box_faces)
|
||||||
self.assertTrue(box_shell.is_valid())
|
self.assertTrue(box_shell.is_valid)
|
||||||
|
|
||||||
def test_shell_init_single_face(self):
|
def test_shell_init_single_face(self):
|
||||||
face = Solid.make_cone(1, 0, 2).faces().filter_by(GeomType.CONE).first
|
face = Solid.make_cone(1, 0, 2).faces().filter_by(GeomType.CONE).first
|
||||||
shell = Shell(face)
|
shell = Shell(face)
|
||||||
self.assertTrue(shell.is_valid())
|
self.assertTrue(shell.is_valid)
|
||||||
|
|
||||||
def test_center(self):
|
def test_center(self):
|
||||||
box_faces = Solid.make_box(1, 1, 1).faces()
|
box_faces = Solid.make_box(1, 1, 1).faces()
|
||||||
|
|
@ -71,9 +71,9 @@ class TestShells(unittest.TestCase):
|
||||||
x_section = Rot(90) * Spline((0, -5), (-3, -2), (-2, 0), (-3, 2), (0, 5))
|
x_section = Rot(90) * Spline((0, -5), (-3, -2), (-2, 0), (-3, 2), (0, 5))
|
||||||
surface = sweep(x_section, Circle(5).wire())
|
surface = sweep(x_section, Circle(5).wire())
|
||||||
single_face = Shell(surface.face())
|
single_face = Shell(surface.face())
|
||||||
self.assertTrue(single_face.is_valid())
|
self.assertTrue(single_face.is_valid)
|
||||||
single_face = Shell(surface.faces())
|
single_face = Shell(surface.faces())
|
||||||
self.assertTrue(single_face.is_valid())
|
self.assertTrue(single_face.is_valid)
|
||||||
|
|
||||||
def test_sweep(self):
|
def test_sweep(self):
|
||||||
path_c1 = JernArc((0, 0), (-1, 0), 1, 180)
|
path_c1 = JernArc((0, 0), (-1, 0), 1, 180)
|
||||||
|
|
@ -116,6 +116,13 @@ class TestShells(unittest.TestCase):
|
||||||
outer_vol = 3 * 12 * 7
|
outer_vol = 3 * 12 * 7
|
||||||
self.assertAlmostEqual(thick.volume, outer_vol - inner_vol)
|
self.assertAlmostEqual(thick.volume, outer_vol - inner_vol)
|
||||||
|
|
||||||
|
def test_location_at(self):
|
||||||
|
shell = Solid.make_cylinder(1, 2).shell()
|
||||||
|
top_center = shell.location_at((0, 0, 2))
|
||||||
|
self.assertAlmostEqual(top_center.position, (0, 0, 2), 5)
|
||||||
|
self.assertAlmostEqual(top_center.z_axis.direction, (0, 0, 1), 5)
|
||||||
|
self.assertAlmostEqual(top_center.x_axis.direction, (1, 0, 0), 5)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -29,19 +29,27 @@ license:
|
||||||
import math
|
import math
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
# Mocks for testing failure cases
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from build123d.build_enums import GeomType, Kind, Until
|
from build123d.build_enums import GeomType, Kind, Until
|
||||||
from build123d.geometry import (
|
from build123d.geometry import Axis, Location, Plane, Pos, Vector
|
||||||
Axis,
|
|
||||||
BoundBox,
|
|
||||||
Location,
|
|
||||||
OrientedBoundBox,
|
|
||||||
Plane,
|
|
||||||
Pos,
|
|
||||||
Vector,
|
|
||||||
)
|
|
||||||
from build123d.objects_curve import Spline
|
from build123d.objects_curve import Spline
|
||||||
|
from build123d.objects_part import Box, Torus
|
||||||
from build123d.objects_sketch import Circle, Rectangle
|
from build123d.objects_sketch import Circle, Rectangle
|
||||||
from build123d.topology import Compound, Edge, Face, Shell, Solid, Vertex, Wire
|
from build123d.topology import (
|
||||||
|
Compound,
|
||||||
|
DraftAngleError,
|
||||||
|
Edge,
|
||||||
|
Face,
|
||||||
|
Shell,
|
||||||
|
Solid,
|
||||||
|
Vertex,
|
||||||
|
Wire,
|
||||||
|
)
|
||||||
|
import build123d
|
||||||
|
from OCP.BRepOffsetAPI import BRepOffsetAPI_DraftAngle
|
||||||
|
from OCP.StdFail import StdFail_NotDone
|
||||||
|
|
||||||
|
|
||||||
class TestSolid(unittest.TestCase):
|
class TestSolid(unittest.TestCase):
|
||||||
|
|
@ -51,7 +59,7 @@ class TestSolid(unittest.TestCase):
|
||||||
box = Solid(box_shell)
|
box = Solid(box_shell)
|
||||||
self.assertAlmostEqual(box.area, 6, 5)
|
self.assertAlmostEqual(box.area, 6, 5)
|
||||||
self.assertAlmostEqual(box.volume, 1, 5)
|
self.assertAlmostEqual(box.volume, 1, 5)
|
||||||
self.assertTrue(box.is_valid())
|
self.assertTrue(box.is_valid)
|
||||||
|
|
||||||
def test_extrude(self):
|
def test_extrude(self):
|
||||||
v = Edge.extrude(Vertex(1, 1, 1), (0, 0, 1))
|
v = Edge.extrude(Vertex(1, 1, 1), (0, 0, 1))
|
||||||
|
|
@ -254,5 +262,66 @@ class TestSolid(unittest.TestCase):
|
||||||
self.assertAlmostEqual(obb2.volume, 40, 4)
|
self.assertAlmostEqual(obb2.volume, 40, 4)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSolidDraft(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Create a simple box to test draft
|
||||||
|
self.box: Solid = Box(10, 10, 10).solid()
|
||||||
|
self.sides = self.box.faces().filter_by(Axis.Z, reverse=True)
|
||||||
|
self.bottom_face: Face = self.box.faces().sort_by(Axis.Z)[0]
|
||||||
|
self.neutral_plane = Plane(self.bottom_face)
|
||||||
|
|
||||||
|
def test_successful_draft(self):
|
||||||
|
"""Test that a draft operation completes successfully on a planar face"""
|
||||||
|
drafted = self.box.draft(self.sides, self.neutral_plane, 5)
|
||||||
|
self.assertIsInstance(drafted, Solid)
|
||||||
|
self.assertNotEqual(drafted.volume, self.box.volume)
|
||||||
|
|
||||||
|
def test_unsupported_geometry(self):
|
||||||
|
"""Test that a ValueError is raised on unsupported face geometry"""
|
||||||
|
# Create toroidal face to simulate unsupported geometry
|
||||||
|
torus = Torus(5, 1).solid()
|
||||||
|
with self.assertRaises(ValueError) as cm:
|
||||||
|
torus.draft([torus.faces()[0]], self.neutral_plane, 5)
|
||||||
|
self.assertIn("unsupported geometry type", str(cm.exception))
|
||||||
|
|
||||||
|
@patch("build123d.topology.three_d.BRepOffsetAPI_DraftAngle")
|
||||||
|
def test_adddone_failure_raises_draftangleerror(self, mock_draft_api):
|
||||||
|
"""Test that failure of AddDone() raises DraftAngleError"""
|
||||||
|
mock_builder = MagicMock()
|
||||||
|
mock_builder.AddDone.return_value = False
|
||||||
|
mock_builder.ProblematicShape.return_value = "BadShape"
|
||||||
|
mock_draft_api.return_value = mock_builder
|
||||||
|
|
||||||
|
with self.assertRaises(DraftAngleError) as cm:
|
||||||
|
self.box.draft(self.sides, self.neutral_plane, 5)
|
||||||
|
self.assertEqual(cm.exception.face, self.sides[0])
|
||||||
|
self.assertEqual(cm.exception.problematic_shape, "BadShape")
|
||||||
|
self.assertIn("Draft could not be added", str(cm.exception))
|
||||||
|
|
||||||
|
@patch.object(
|
||||||
|
build123d.topology.three_d.BRepOffsetAPI_DraftAngle,
|
||||||
|
"Build",
|
||||||
|
side_effect=StdFail_NotDone,
|
||||||
|
)
|
||||||
|
def test_build_failure_raises_draftangleerror(self, mock_draft_api):
|
||||||
|
"""Test that Build() failure raises DraftAngleError"""
|
||||||
|
|
||||||
|
with self.assertRaises(DraftAngleError) as cm:
|
||||||
|
self.box.draft(self.sides, self.neutral_plane, 5)
|
||||||
|
self.assertIsNone(cm.exception.face)
|
||||||
|
self.assertEqual(
|
||||||
|
cm.exception.problematic_shape, cm.exception.problematic_shape
|
||||||
|
) # Not None
|
||||||
|
self.assertIn("Draft build failed", str(cm.exception))
|
||||||
|
|
||||||
|
def test_draftangleerror_contents(self):
|
||||||
|
"""Test that DraftAngleError stores face and problematic shape"""
|
||||||
|
err = DraftAngleError("msg", face="face123", problematic_shape="shape456")
|
||||||
|
self.assertEqual(str(err), "msg")
|
||||||
|
self.assertEqual(err.face, "face123")
|
||||||
|
self.assertEqual(err.problematic_shape, "shape456")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -191,26 +191,26 @@ class TestWire(unittest.TestCase):
|
||||||
e1 = Edge.make_line((1, 0), (1, 1))
|
e1 = Edge.make_line((1, 0), (1, 1))
|
||||||
w0 = Wire.make_circle(1)
|
w0 = Wire.make_circle(1)
|
||||||
w1 = Wire(e0)
|
w1 = Wire(e0)
|
||||||
self.assertTrue(w1.is_valid())
|
self.assertTrue(w1.is_valid)
|
||||||
w2 = Wire([e0])
|
w2 = Wire([e0])
|
||||||
self.assertAlmostEqual(w2.length, 1, 5)
|
self.assertAlmostEqual(w2.length, 1, 5)
|
||||||
self.assertTrue(w2.is_valid())
|
self.assertTrue(w2.is_valid)
|
||||||
w3 = Wire([e0, e1])
|
w3 = Wire([e0, e1])
|
||||||
self.assertTrue(w3.is_valid())
|
self.assertTrue(w3.is_valid)
|
||||||
self.assertAlmostEqual(w3.length, 2, 5)
|
self.assertAlmostEqual(w3.length, 2, 5)
|
||||||
w4 = Wire(w0.wrapped)
|
w4 = Wire(w0.wrapped)
|
||||||
self.assertTrue(w4.is_valid())
|
self.assertTrue(w4.is_valid)
|
||||||
w5 = Wire(obj=w0.wrapped)
|
w5 = Wire(obj=w0.wrapped)
|
||||||
self.assertTrue(w5.is_valid())
|
self.assertTrue(w5.is_valid)
|
||||||
w6 = Wire(obj=w0.wrapped, label="w6", color=Color("red"))
|
w6 = Wire(obj=w0.wrapped, label="w6", color=Color("red"))
|
||||||
self.assertTrue(w6.is_valid())
|
self.assertTrue(w6.is_valid)
|
||||||
self.assertEqual(w6.label, "w6")
|
self.assertEqual(w6.label, "w6")
|
||||||
np.testing.assert_allclose(tuple(w6.color), (1.0, 0.0, 0.0, 1.0), 1e-5)
|
np.testing.assert_allclose(tuple(w6.color), (1.0, 0.0, 0.0, 1.0), 1e-5)
|
||||||
w7 = Wire(w6)
|
w7 = Wire(w6)
|
||||||
self.assertTrue(w7.is_valid())
|
self.assertTrue(w7.is_valid)
|
||||||
c0 = Polyline((0, 0), (1, 0), (1, 1))
|
c0 = Polyline((0, 0), (1, 0), (1, 1))
|
||||||
w8 = Wire(c0)
|
w8 = Wire(c0)
|
||||||
self.assertTrue(w8.is_valid())
|
self.assertTrue(w8.is_valid)
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Wire(bob="fred")
|
Wire(bob="fred")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,11 @@ class DimensionLineTestCase(unittest.TestCase):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
DimensionLine([(0, 0, 0), (5, 0, 0)], draft=metric, arrows=(False, False))
|
DimensionLine([(0, 0, 0), (5, 0, 0)], draft=metric, arrows=(False, False))
|
||||||
|
|
||||||
|
def test_vertical(self):
|
||||||
|
d_line = DimensionLine([(0, 0), (0, 100)], Draft())
|
||||||
|
bbox = d_line.bounding_box()
|
||||||
|
self.assertAlmostEqual(bbox.size.Y, 100, 5) # numbers within
|
||||||
|
|
||||||
|
|
||||||
class ExtensionLineTestCase(unittest.TestCase):
|
class ExtensionLineTestCase(unittest.TestCase):
|
||||||
def test_min_x(self):
|
def test_min_x(self):
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,10 @@ import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import unittest
|
import unittest
|
||||||
from typing import Optional
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -39,7 +41,7 @@ from build123d.build_common import GridLocations
|
||||||
from build123d.build_enums import Unit
|
from build123d.build_enums import Unit
|
||||||
from build123d.build_line import BuildLine
|
from build123d.build_line import BuildLine
|
||||||
from build123d.build_sketch import BuildSketch
|
from build123d.build_sketch import BuildSketch
|
||||||
from build123d.exporters3d import export_gltf, export_step, export_brep, export_stl
|
from build123d.exporters3d import export_brep, export_gltf, export_step, export_stl
|
||||||
from build123d.geometry import Color, Pos, Vector, VectorLike
|
from build123d.geometry import Color, Pos, Vector, VectorLike
|
||||||
from build123d.objects_curve import Line
|
from build123d.objects_curve import Line
|
||||||
from build123d.objects_part import Box, Sphere
|
from build123d.objects_part import Box, Sphere
|
||||||
|
|
@ -144,6 +146,29 @@ class TestExportStep(DirectApiTestCase):
|
||||||
os.chmod("box_read_only.step", 0o777) # Make the file read/write
|
os.chmod("box_read_only.step", 0o777) # Make the file read/write
|
||||||
os.remove("box_read_only.step")
|
os.remove("box_read_only.step")
|
||||||
|
|
||||||
|
def test_export_step_timestamp_datetime(self):
|
||||||
|
b = Box(1, 1, 1)
|
||||||
|
t = datetime(2025, 5, 6, 21, 30, 25)
|
||||||
|
self.assertTrue(export_step(b, "box.step", timestamp=t))
|
||||||
|
with open("box.step", "r") as file:
|
||||||
|
step_data = file.read()
|
||||||
|
os.remove("box.step")
|
||||||
|
self.assertEqual(
|
||||||
|
re.findall("FILE_NAME\\('[^']*','([^']*)'", step_data),
|
||||||
|
["2025-05-06T21:30:25"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_export_step_timestamp_str(self):
|
||||||
|
b = Box(1, 1, 1)
|
||||||
|
self.assertTrue(export_step(b, "box.step", timestamp="0000-00-00T00:00:00"))
|
||||||
|
with open("box.step", "r") as file:
|
||||||
|
step_data = file.read()
|
||||||
|
os.remove("box.step")
|
||||||
|
self.assertEqual(
|
||||||
|
re.findall("FILE_NAME\\('[^']*','([^']*)'", step_data),
|
||||||
|
["0000-00-00T00:00:00"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestExportGltf(DirectApiTestCase):
|
class TestExportGltf(DirectApiTestCase):
|
||||||
def test_export_gltf(self):
|
def test_export_gltf(self):
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ class TestHollowImport(unittest.TestCase):
|
||||||
export_stl(test_shape, "test.stl")
|
export_stl(test_shape, "test.stl")
|
||||||
importer = Mesher()
|
importer = Mesher()
|
||||||
stl = importer.read("test.stl")
|
stl = importer.read("test.stl")
|
||||||
self.assertTrue(stl[0].is_valid())
|
self.assertTrue(stl[0].is_valid)
|
||||||
|
|
||||||
|
|
||||||
class TestImportDegenerateTriangles(unittest.TestCase):
|
class TestImportDegenerateTriangles(unittest.TestCase):
|
||||||
|
|
@ -221,7 +221,7 @@ class TestImportDegenerateTriangles(unittest.TestCase):
|
||||||
stl = importer.read("cyl_w_rect_hole.stl")[0]
|
stl = importer.read("cyl_w_rect_hole.stl")[0]
|
||||||
self.assertEqual(type(stl), Solid)
|
self.assertEqual(type(stl), Solid)
|
||||||
self.assertTrue(stl.is_manifold)
|
self.assertTrue(stl.is_manifold)
|
||||||
self.assertTrue(stl.is_valid())
|
self.assertTrue(stl.is_valid)
|
||||||
self.assertEqual(sum(f.area == 0 for f in stl.faces()), 0)
|
self.assertEqual(sum(f.area == 0 for f in stl.faces()), 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue