Updating surface modeling docs
BIN
docs/_static/spitfire_wing.glb
vendored
Normal file
BIN
docs/assets/surface_modeling/heart_token.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/surface_modeling/spitfire_wing.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<svg width="100.09mm" height="66.151142mm" viewBox="-541.648921 -836.924186 4767.847817 3151.14975" 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="4.28720455097892">
|
||||
<path d="M 1445.821235,834.74528 C 1353.526765,803.718243 1260.88331,770.545273 1167.994034,735.580745 C 1083.806246,703.883592 999.414296,670.712071 914.854789,636.113423 C 835.638754,603.69438 756.274425,570.022422 676.77418,534.952257 C 592.583511,497.803918 508.238612,459.095508 423.75228,418.390166 C 381.475605,398.017947 339.163641,377.140102 296.820744,355.66775 C 255.380094,334.651781 213.909574,313.067151 172.417787,290.800527 C 132.498839,269.376021 92.559713,247.323024 52.616058,224.499576 C 33.433555,213.541072 14.251486,202.395739 -4.930148,191.063577 C -23.484296,180.09936 -42.03687,168.953007 -60.583168,157.587759 C -78.431472,146.650245 -96.273964,135.510001 -114.110039,124.195809 C -131.629708,113.082326 -149.143185,101.801012 -166.62766,90.088045 C -183.749938,78.617716 -200.844402,66.733433 -217.887184,54.406354 C -249.053676,31.859239 -280.057077,7.844124 -310.705712,-17.830688 C -324.493158,-29.38285 -338.206616,-41.273839 -351.816854,-53.534273 C -364.358757,-64.832326 -376.813009,-76.4441 -389.144607,-88.405566 C -400.391943,-99.315314 -411.537246,-110.515963 -422.53694,-122.049338 C -432.452058,-132.445516 -442.248865,-143.112041 -451.870806,-154.096773 C -460.428098,-163.866068 -468.84708,-173.887048 -477.05134,-184.212339 C -484.238191,-193.257193 -491.260272,-202.535563 -498.010658,-212.099865 C -503.829085,-220.343718 -509.445657,-228.800001 -514.706406,-237.507752 C -519.174839,-244.90404 -523.386558,-252.481753 -527.120208,-260.233274 C -530.2732,-266.779278 -533.085259,-273.449232 -535.256161,-280.125931 C -537.126266,-285.877518 -538.520579,-291.634109 -539.139475,-297.089851 C -539.710024,-302.119395 -539.621572,-306.893256 -538.815367,-311.085197 C -538.009295,-315.276448 -536.485707,-318.88597 -534.41361,-321.787038 C -532.173969,-324.922679 -529.293531,-327.230668 -526.080734,-328.912927 C -522.384603,-330.848265 -518.248591,-331.955443 -513.912155,-332.549837 C -508.837989,-333.245352 -503.489405,-333.238769 -498.016517,-332.811088 C -491.653993,-332.313885 -485.123469,-331.247553 -478.514247,-329.834557 C -470.940437,-328.21534 -463.263282,-326.140884 -455.535716,-323.780678 C -446.817103,-321.117779 -438.03432,-318.091135 -429.219119,-314.830251 C -419.4158,-311.203845 -409.572388,-307.287744 -399.708184,-303.182158 C -388.876522,-298.673905 -378.01979,-293.937177 -367.149854,-289.051048 C -355.343719,-283.74409 -343.522009,-278.26089 -331.692086,-272.665067 C -318.963322,-266.644073 -306.225051,-260.492693 -293.48194,-254.263696 C -279.88052,-247.615147 -266.273587,-240.878173 -252.664091,-234.095751 C -238.238065,-226.906402 -223.809159,-219.665987 -209.379903,-212.417551 C -194.175885,-204.777738 -178.971635,-197.135641 -163.767154,-189.491259 C -147.290706,-181.209732 -130.81417,-172.933298 -114.327757,-164.445833 C -96.638051,-155.338894 -78.936973,-145.988995 -61.23183,-136.489711 C -42.83628,-126.618999 -24.437139,-116.590472 -6.034405,-106.404129 C 12.988826,-95.875361 32.015044,-85.181632 51.041971,-74.350743 C 90.663309,-51.796303 130.287184,-28.649245 169.904219,-5.058022 C 211.090446,19.468633 252.269483,44.474456 293.437331,69.839459 C 335.517614,95.767754 377.586396,122.072076 419.642854,148.660515 C 503.76059,201.847286 587.828904,256.147719 671.854879,311.120502 C 751.312767,363.113312 830.733706,415.715977 910.121501,468.777679 C 995.01224,525.524664 1079.865697,582.797171 1164.674248,640.617824 C 1192.283028,659.441198 1219.886541,678.326945 1247.484787,697.275067 C 1270.73957,713.242184 1293.996044,729.258338 1317.241888,745.321373 C 1327.107887,752.139255 1336.976715,758.968255 1346.848371,765.808374 C 1355.493823,771.798596 1364.146132,777.799924 1372.767724,783.793106 C 1380.127277,788.908999 1387.464446,794.018955 1394.889693,799.180553 C 1401.009487,803.434676 1407.189112,807.723879 1413.119965,811.887911 C 1417.7599,815.145595 1422.247573,818.326668 1427.380659,821.845665 C 1431.063255,824.370277 1435.078042,827.068818 1437.610752,828.998585 C 1439.134823,830.159833 1440.12222,831.0427 1443.76641,833.306567 C 1444.982916,834.062292 1446.495484,834.97191 1445.821235,834.74528" />
|
||||
<path d="M 4088.698422,-2201.97176 C 4071.863286,-2209.539657 4054.994642,-2217.480665 4038.106206,-2225.738798 C 4022.573261,-2233.338843 4007.022586,-2241.206509 3991.458597,-2249.421198 C 3983.516432,-2253.614748 3975.570735,-2257.900919 3967.622793,-2262.30905 C 3960.261943,-2266.392612 3952.898981,-2270.579106 3945.536956,-2274.915202 C 3943.845675,-2275.912386 3942.154454,-2276.917073 3940.463292,-2277.929262 C 3938.829503,-2278.906677 3937.195728,-2279.889856 3935.56293,-2280.905309 C 3931.124503,-2283.667466 3926.692829,-2286.658395 3922.292205,-2289.898531 C 3918.777241,-2292.48891 3915.280436,-2295.235265 3911.829585,-2298.184808 C 3910.073846,-2299.687297 3908.329767,-2301.242202 3906.611091,-2302.876449 C 3905.350277,-2304.077409 3904.101247,-2305.31876 3902.891914,-2306.64651 C 3902.453922,-2307.124054 3902.026791,-2307.61984 3901.610521,-2308.133868 C 3901.299294,-2308.518314 3901.002293,-2308.919081 3900.719517,-2309.336169 C 3900.527397,-2309.621912 3900.344384,-2309.916518 3900.220419,-2310.243794 C 3900.138415,-2310.46029 3900.08225,-2310.691082 3900.113043,-2310.851579 C 3900.143825,-2311.012017 3900.2615,-2311.10221 3900.393136,-2311.146406 C 3900.591347,-2311.212954 3900.82121,-2311.175213 3901.054853,-2311.119196 C 3901.395934,-2311.034587 3901.742062,-2310.920466 3902.093238,-2310.776833 C 3902.560145,-2310.589421 3903.03076,-2310.365917 3903.501882,-2310.129577 C 3904.091767,-2309.832167 3904.682134,-2309.519273 3905.272983,-2309.190897 C 3906.803549,-2308.34235 3908.334632,-2307.429826 3909.864882,-2306.507962 C 3911.837632,-2305.316998 3913.809818,-2304.101833 3915.782074,-2302.891418 C 3918.161608,-2301.426976 3920.541478,-2299.976332 3922.921685,-2298.539486 C 3927.169463,-2295.979761 3931.419103,-2293.492922 3935.667474,-2291.12314 C 3937.263565,-2290.232961 3938.85938,-2289.359703 3940.455335,-2288.478295 C 3942.141204,-2287.546398 3943.827175,-2286.607933 3945.51325,-2285.6629 C 3952.851904,-2281.554558 3960.1915,-2277.358362 3967.530929,-2273.121841 C 3975.456854,-2268.545879 3983.382716,-2263.92158 3991.308409,-2259.279052 C 4006.85028,-2250.172017 4022.391679,-2241.00337 4037.933217,-2231.854444 C 4045.982183,-2227.115049 4054.031164,-2222.377487 4062.08016,-2217.64176 C 4066.897545,-2214.806906 4071.71493,-2211.972052 4076.532315,-2209.137199 C 4078.284341,-2208.105943 4080.036161,-2207.074566 4081.787775,-2206.043066 C 4082.482313,-2205.633843 4083.180026,-2205.222922 4083.880913,-2204.810304 C 4084.459844,-2204.469581 4085.044497,-2204.12585 4085.605526,-2203.794355 C 4086.04433,-2203.535079 4086.468682,-2203.283289 4086.954417,-2202.999576 C 4087.302828,-2202.796074 4087.682821,-2202.576147 4087.921957,-2202.429398 C 4088.065839,-2202.341102 4088.15873,-2202.279298 4088.504102,-2202.086294 C 4088.619387,-2202.02187 4088.762804,-2201.942827 4088.698422,-2201.97176" />
|
||||
<path d="M -538.815367,-311.085197 L -91.009814,-565.713777 L 357.94024,-813.228178 L 802.388988,-1050.515762 L 1236.747229,-1274.592501 L 1655.552655,-1482.6405 L 2053.538547,-1672.043435 L 2425.7,-1840.419453 L 2767.356868,-1985.651129 L 3074.212618,-2105.912089 L 3342.40836,-2199.689984 L 3568.571378,-2265.8055 L 3749.85754,-2303.427198 L 3883.987068,-2312.081962 L 3969.273206,-2291.660954" />
|
||||
<path d="M 3969.273206,-2291.660954 L 4106.195866,-2183.789653 L 4191.480731,-2048.45593 L 4224.055294,-1887.361685 L 4203.50991,-1702.532772 L 4130.102951,-1496.293521 L 4004.75755,-1271.237512 L 3829.05,-1030.194953 L 3605.189924,-776.197095 L 3335.992494,-512.438108 L 3024.843022,-242.234914 L 2675.654394,31.014524 L 2292.817859,303.873937 L 1881.14781,572.911958 L 1445.821235,834.74528" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
68
docs/heart_token.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# [Code]
|
||||
from build123d import *
|
||||
from ocp_vscode import show
|
||||
|
||||
# Create the edges of one half the heart surface
|
||||
l1 = JernArc((0, 0), (1, 1.4), 40, -17)
|
||||
l2 = JernArc(l1 @ 1, l1 % 1, 4.5, 175)
|
||||
l3 = IntersectingLine(l2 @ 1, l2 % 1, other=Edge.make_line((0, 0), (0, 20)))
|
||||
l4 = ThreePointArc(l3 @ 1, (0, 0, 1.5) + (l3 @ 1 + l1 @ 0) / 2, l1 @ 0)
|
||||
heart_half = Wire([l1, l2, l3, l4])
|
||||
# [SurfaceEdges]
|
||||
|
||||
# Create a point elevated off the center
|
||||
surface_pnt = l2.arc_center + (0, 0, 1.5)
|
||||
# [SurfacePoint]
|
||||
|
||||
# Create the surface from the edges and point
|
||||
top_right_surface = Pos(Z=0.5) * -Face.make_surface(heart_half, [surface_pnt])
|
||||
# [Surface]
|
||||
|
||||
# Use the mirror method to create the other top and bottom surfaces
|
||||
top_left_surface = top_right_surface.mirror(Plane.YZ)
|
||||
bottom_right_surface = top_right_surface.mirror(Plane.XY)
|
||||
bottom_left_surface = -top_left_surface.mirror(Plane.XY)
|
||||
# [Surfaces]
|
||||
|
||||
# Create the left and right sides
|
||||
left_wire = Wire([l3, l2, l1])
|
||||
left_side = Pos(Z=-0.5) * Shell.extrude(left_wire, (0, 0, 1))
|
||||
right_side = left_side.mirror(Plane.YZ)
|
||||
# [Sides]
|
||||
|
||||
# Put all of the faces together into a Shell/Solid
|
||||
heart = Solid(
|
||||
Shell(
|
||||
[
|
||||
top_right_surface,
|
||||
top_left_surface,
|
||||
bottom_right_surface,
|
||||
bottom_left_surface,
|
||||
left_side,
|
||||
right_side,
|
||||
]
|
||||
)
|
||||
)
|
||||
# [Solid]
|
||||
|
||||
# Build a frame around the heart
|
||||
with BuildPart() as heart_token:
|
||||
with BuildSketch() as outline:
|
||||
with BuildLine():
|
||||
add(l1)
|
||||
add(l2)
|
||||
add(l3)
|
||||
Line(l3 @ 1, l1 @ 0)
|
||||
make_face()
|
||||
mirror(about=Plane.YZ)
|
||||
center = outline.sketch
|
||||
offset(amount=2, kind=Kind.INTERSECTION)
|
||||
add(center, mode=Mode.SUBTRACT)
|
||||
extrude(amount=2, both=True)
|
||||
add(heart)
|
||||
|
||||
heart_token.part.color = "Red"
|
||||
|
||||
show(heart_token)
|
||||
# [End]
|
||||
# export_gltf(heart_token.part, "heart_token.glb", binary=True)
|
||||
77
docs/spitfire_wing_gordon.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"""
|
||||
Supermarine Spitfire Wing
|
||||
"""
|
||||
|
||||
# [Code]
|
||||
|
||||
from build123d import *
|
||||
from ocp_vscode import show
|
||||
|
||||
wing_span = 36 * FT + 10 * IN
|
||||
wing_leading = 2.5 * FT
|
||||
wing_trailing = wing_span / 4 - wing_leading
|
||||
wing_leading_fraction = wing_leading / (wing_leading + wing_trailing)
|
||||
wing_tip_section = wing_span / 2 - 1 * IN # distance from root to last section
|
||||
|
||||
# Create leading and trailing edges
|
||||
leading_edge = EllipticalCenterArc(
|
||||
(0, 0), wing_span / 2, wing_leading, start_angle=270, end_angle=360
|
||||
)
|
||||
trailing_edge = EllipticalCenterArc(
|
||||
(0, 0), wing_span / 2, wing_trailing, start_angle=0, end_angle=90
|
||||
)
|
||||
|
||||
# [AirfoilSizes]
|
||||
# Calculate the airfoil sizes from the leading/trailing edges
|
||||
airfoil_sizes = []
|
||||
for i in [0, 1]:
|
||||
tip_axis = Axis(i * (wing_tip_section, 0, 0), (0, 1, 0))
|
||||
leading_pnt = leading_edge.intersect(tip_axis)[0]
|
||||
trailing_pnt = trailing_edge.intersect(tip_axis)[0]
|
||||
airfoil_sizes.append(trailing_pnt.Y - leading_pnt.Y)
|
||||
|
||||
# [Airfoils]
|
||||
# Create the root and tip airfoils - note that they are different NACA profiles
|
||||
airfoil_root = Plane.YZ * scale(
|
||||
Airfoil("2213").translate((-wing_leading_fraction, 0, 0)), airfoil_sizes[0]
|
||||
)
|
||||
airfoil_tip = (
|
||||
Plane.YZ
|
||||
* Pos(Z=wing_tip_section)
|
||||
* scale(Airfoil("2205").translate((-wing_leading_fraction, 0, 0)), airfoil_sizes[1])
|
||||
)
|
||||
|
||||
# [Profiles]
|
||||
# Create the Gordon surface profiles and guides
|
||||
profiles = airfoil_root.edges() + airfoil_tip.edges()
|
||||
profiles.append(leading_edge @ 1) # wing tip
|
||||
guides = [leading_edge, trailing_edge]
|
||||
# Create the wing surface as a Gordon Surface
|
||||
wing_surface = -Face.make_gordon_surface(profiles, guides)
|
||||
# Create the root of the wing
|
||||
wing_root = -Face(Wire(wing_surface.edges().filter_by(Edge.is_closed)))
|
||||
|
||||
# [Solid]
|
||||
# Create the wing Solid
|
||||
wing = Solid(Shell([wing_surface, wing_root]))
|
||||
wing.color = 0x99A3B9 # Azure Blue
|
||||
|
||||
show(wing)
|
||||
# [End]
|
||||
# Documentation artifact generation
|
||||
# wing_control_edges = Curve(
|
||||
# [airfoil_root, airfoil_tip, Vertex(leading_edge @ 1), leading_edge, trailing_edge]
|
||||
# )
|
||||
# visible, _ = wing_control_edges.project_to_viewport((50 * FT, -50 * FT, 50 * FT))
|
||||
# max_dimension = max(*Compound(children=visible).bounding_box().size)
|
||||
# svg = ExportSVG(scale=100 / max_dimension)
|
||||
# svg.add_shape(visible)
|
||||
# svg.write("assets/surface_modeling/spitfire_wing_profiles_guides.svg")
|
||||
|
||||
# export_gltf(
|
||||
# wing,
|
||||
# "assets/surface_modeling/spitfire_wing.glb",
|
||||
# binary=True,
|
||||
# linear_deflection=0.1,
|
||||
# angular_deflection=1,
|
||||
# )
|
||||
125
docs/tutorial_surface_heart_token.rst
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
##################################
|
||||
Tutorial: Heart Token (Basics)
|
||||
##################################
|
||||
|
||||
This hands‑on tutorial introduces the fundamentals of surface modeling by building
|
||||
a heart‑shaped token from a small set of non‑planar faces. We’ll create
|
||||
non‑planar surfaces, mirror them, add side faces, and assemble a closed shell
|
||||
into a solid.
|
||||
|
||||
As described in the `topology_` section, a BREP model consists of vertices, edges, faces,
|
||||
and other elements that define the boundary of an object. When creating objects with
|
||||
non-planar faces, it is often more convenient to explicitly create the boundary faces of
|
||||
the object. To illustrate this process, we will create the following game token:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
|
||||
<model-viewer poster="_images/heart_token.png" src="_static/heart_token.glb" alt="Game Token" auto-rotate camera-controls style="width: 100%; height: 50vh;"></model-viewer>
|
||||
|
||||
Useful :class:`~topology.Face` creation methods include
|
||||
:meth:`~topology.Face.make_surface`, :meth:`~topology.Face.make_bezier_surface`,
|
||||
and :meth:`~topology.Face.make_surface_from_array_of_points`. See the
|
||||
:doc:`surface_modeling` overview for the full list.
|
||||
|
||||
In this case, we'll use the ``make_surface`` method, providing it with the edges that define
|
||||
the perimeter of the surface and a central point on that surface.
|
||||
|
||||
To create the perimeter, we'll define the perimeter edges. Since the heart is
|
||||
symmetric, we'll only create half of its surface here:
|
||||
|
||||
.. literalinclude:: heart_token.py
|
||||
:start-after: [Code]
|
||||
:end-before: [SurfaceEdges]
|
||||
|
||||
Note that ``l4`` is not in the same plane as the other lines; it defines the center line
|
||||
of the heart and archs up off ``Plane.XY``.
|
||||
|
||||
.. image:: ./assets/surface_modeling/token_heart_perimeter.png
|
||||
:align: center
|
||||
:alt: token perimeter
|
||||
|
||||
In preparation for creating the surface, we'll define a point on the surface:
|
||||
|
||||
.. literalinclude:: heart_token.py
|
||||
:start-after: [SurfaceEdges]
|
||||
:end-before: [SurfacePoint]
|
||||
|
||||
We will then use this point to create a non-planar ``Face``:
|
||||
|
||||
.. literalinclude:: heart_token.py
|
||||
:start-after: [SurfacePoint]
|
||||
:end-before: [Surface]
|
||||
|
||||
.. image:: ./assets/surface_modeling/token_half_surface.png
|
||||
:align: center
|
||||
:alt: token perimeter
|
||||
|
||||
Note that the surface was raised up by 0.5 using an Algebra expression with Pos. Also,
|
||||
note that the ``-`` in front of ``Face`` simply flips the face normal so that the colored
|
||||
side is up, which isn't necessary but helps with viewing.
|
||||
|
||||
Now that one half of the top of the heart has been created, the remainder of the top
|
||||
and bottom can be created by mirroring:
|
||||
|
||||
.. literalinclude:: heart_token.py
|
||||
:start-after: [Surface]
|
||||
:end-before: [Surfaces]
|
||||
|
||||
The sides of the heart are going to be created by extruding the outside of the perimeter
|
||||
as follows:
|
||||
|
||||
.. literalinclude:: heart_token.py
|
||||
:start-after: [Surfaces]
|
||||
:end-before: [Sides]
|
||||
|
||||
.. image:: ./assets/surface_modeling/token_sides.png
|
||||
:align: center
|
||||
:alt: token sides
|
||||
|
||||
With the top, bottom, and sides, the complete boundary of the object is defined. We can
|
||||
now put them together, first into a :class:`~topology.Shell` and then into a
|
||||
:class:`~topology.Solid`:
|
||||
|
||||
.. literalinclude:: heart_token.py
|
||||
:start-after: [Sides]
|
||||
:end-before: [Solid]
|
||||
|
||||
.. image:: ./assets/surface_modeling/token_heart_solid.png
|
||||
:align: center
|
||||
:alt: token heart solid
|
||||
|
||||
.. note::
|
||||
When creating a Solid from a Shell, the Shell must be "water-tight," meaning it
|
||||
should have no holes. For objects with complex Edges, it's best practice to reuse
|
||||
Edges in adjoining Faces whenever possible to avoid slight mismatches that can
|
||||
create openings.
|
||||
|
||||
Finally, we'll create the frame around the heart as a simple extrusion of a planar
|
||||
shape defined by the perimeter of the heart and merge all of the components together:
|
||||
|
||||
.. literalinclude:: heart_token.py
|
||||
:start-after: [Solid]
|
||||
:end-before: [End]
|
||||
|
||||
Note that an additional planar line is used to close ``l1`` and ``l3`` so a ``Face``
|
||||
can be created. The :func:`~operations_generic.offset` function defines the outside of
|
||||
the frame as a constant distance from the heart itself.
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
In this tutorial, we've explored surface modeling techniques to create a non-planar
|
||||
heart-shaped object using build123d. By utilizing methods from the :class:`~topology.Face`
|
||||
class, such as :meth:`~topology.Face.make_surface`, we constructed the perimeter and
|
||||
central point of the surface. We then assembled the complete boundary of the object
|
||||
by creating the top, bottom, and sides, and combined them into a :class:`~topology.Shell`
|
||||
and eventually a :class:`~topology.Solid`. Finally, we added a frame around the heart
|
||||
using the :func:`~operations_generic.offset` function to maintain a constant distance
|
||||
from the heart.
|
||||
|
||||
Next steps
|
||||
----------
|
||||
|
||||
Continue to :doc:`tutorial_heart_token` for an advanced example using
|
||||
:meth:`~topology.Face.make_gordon_surface` to create a Supermarine Spitfire wing.
|
||||
|
|
@ -1,156 +1,55 @@
|
|||
################
|
||||
#################
|
||||
Surface Modeling
|
||||
################
|
||||
#################
|
||||
|
||||
Surface modeling is employed to create objects with non-planar surfaces that can't be
|
||||
generated using functions like :func:`~operations_part.extrude`,
|
||||
:func:`~operations_generic.sweep`, or :func:`~operations_part.revolve`. Since there are no
|
||||
specific builders designed to assist with the creation of non-planar surfaces or objects,
|
||||
the following should be considered a more advanced technique.
|
||||
|
||||
As described in the `topology_` section, a BREP model consists of vertices, edges, faces,
|
||||
and other elements that define the boundary of an object. When creating objects with
|
||||
non-planar faces, it is often more convenient to explicitly create the boundary faces of
|
||||
the object. To illustrate this process, we will create the following game token:
|
||||
Surface modeling refers to the direct creation and manipulation of the skin of a 3D
|
||||
object—its bounding faces—rather than starting from volumetric primitives or solid
|
||||
operations.
|
||||
|
||||
.. raw:: html
|
||||
Instead of defining a shape by extruding or revolving a 2D profile to fill a volume,
|
||||
surface modeling focuses on building the individual curved or planar faces that together
|
||||
define the outer boundary of a part. This approach allows for precise control of complex
|
||||
freeform geometry such as aerodynamic surfaces, boat hulls, or organic transitions that
|
||||
cannot easily be expressed with simple parametric solids.
|
||||
|
||||
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
|
||||
<model-viewer poster="_images/heart_token.png" src="_static/heart_token.glb" alt="Game Token" auto-rotate camera-controls style="width: 100%; height: 50vh;"></model-viewer>
|
||||
In build123d, as in other CAD kernels based on BREP (Boundary Representation) modeling,
|
||||
all solids are ultimately defined by their boundaries: a hierarchy of faces, edges, and
|
||||
vertices. Each face represents a finite patch of a geometric surface (plane, cylinder,
|
||||
Bézier patch, etc.) bounded by one or more edge loops or wires. When adjacent faces share
|
||||
edges consistently and close into a continuous boundary, they form a manifold
|
||||
:class:`~topology.Shell`—the watertight surface of a volume. If this shell is properly
|
||||
oriented and encloses a finite region of space, the model becomes a solid.
|
||||
|
||||
There are several methods of the :class:`~topology.Face` class that can be used to create
|
||||
non-planar surfaces:
|
||||
Surface modeling therefore operates at the most fundamental level of BREP construction.
|
||||
Rather than relying on higher-level modeling operations to implicitly generate faces,
|
||||
it allows you to construct and connect those faces explicitly. This provides a path to
|
||||
build geometry that blends analytical and freeform shapes seamlessly, with full control
|
||||
over continuity, tangency, and curvature across boundaries.
|
||||
|
||||
* :meth:`~topology.Face.make_bezier_surface`,
|
||||
* :meth:`~topology.Face.make_surface`, and
|
||||
* :meth:`~topology.Face.make_surface_from_array_of_points`.
|
||||
This section provides:
|
||||
- A concise overview of surface‑building tools in build123d
|
||||
- Hands‑on tutorials, from fundamentals to advanced techniques like Gordon surfaces
|
||||
|
||||
In this case, we'll use the ``make_surface`` method, providing it with the edges that define
|
||||
the perimeter of the surface and a central point on that surface.
|
||||
.. rubric:: Available surface methods
|
||||
|
||||
To create the perimeter, we'll use a ``BuildLine`` instance as follows. Since the heart is
|
||||
symmetric, we'll only create half of its surface here:
|
||||
Methods on :class:`~topology.Face` for creating non‑planar surfaces:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with BuildLine() as heart_half:
|
||||
l1 = JernArc((0, 0), (1, 1.4), 40, -17)
|
||||
l2 = JernArc(l1 @ 1, l1 % 1, 4.5, 175)
|
||||
l3 = IntersectingLine(l2 @ 1, l2 % 1, other=Edge.make_line((0, 0), (0, 20)))
|
||||
l4 = ThreePointArc(l3 @ 1, Vector(0, 0, 1.5) + (l3 @ 1 + l1 @ 0) / 2, l1 @ 0)
|
||||
|
||||
Note that ``l4`` is not in the same plane as the other lines; it defines the center line
|
||||
of the heart and archs up off ``Plane.XY``.
|
||||
|
||||
.. image:: ./assets/token_heart_perimeter.png
|
||||
:align: center
|
||||
:alt: token perimeter
|
||||
|
||||
In preparation for creating the surface, we'll define a point on the surface:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
surface_pnt = l2.edge().arc_center + Vector(0, 0, 1.5)
|
||||
|
||||
We will then use this point to create a non-planar ``Face``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
top_right_surface = -Face.make_surface(heart_half.wire(), [surface_pnt]).locate(
|
||||
Pos(Z=0.5)
|
||||
)
|
||||
|
||||
.. image:: ./assets/token_half_surface.png
|
||||
:align: center
|
||||
:alt: token perimeter
|
||||
|
||||
Note that the surface was raised up by 0.5 using the locate method. Also, note that
|
||||
the ``-`` in front of ``Face`` simply flips the face normal so that the colored side
|
||||
is up, which isn't necessary but helps with viewing.
|
||||
|
||||
Now that one half of the top of the heart has been created, the remainder of the top
|
||||
and bottom can be created by mirroring:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
top_left_surface = top_right_surface.mirror(Plane.YZ)
|
||||
bottom_right_surface = top_right_surface.mirror(Plane.XY)
|
||||
bottom_left_surface = -top_left_surface.mirror(Plane.XY)
|
||||
|
||||
The sides of the heart are going to be created by extruding the outside of the perimeter
|
||||
as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
left_wire = Wire([l3.edge(), l2.edge(), l1.edge()])
|
||||
left_side = Face.extrude(left_wire, (0, 0, 1)).locate(Pos(Z=-0.5))
|
||||
right_side = left_side.mirror(Plane.YZ)
|
||||
|
||||
.. image:: ./assets/token_sides.png
|
||||
:align: center
|
||||
:alt: token sides
|
||||
|
||||
With the top, bottom, and sides, the complete boundary of the object is defined. We can
|
||||
now put them together, first into a :class:`~topology.Shell` and then into a
|
||||
:class:`~topology.Solid`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
heart = Solid(
|
||||
Shell(
|
||||
[
|
||||
top_right_surface,
|
||||
top_left_surface,
|
||||
bottom_right_surface,
|
||||
bottom_left_surface,
|
||||
left_side,
|
||||
right_side,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
.. image:: ./assets/token_heart_solid.png
|
||||
:align: center
|
||||
:alt: token heart solid
|
||||
* :meth:`~topology.Face.make_bezier_surface`
|
||||
* :meth:`~topology.Face.make_gordon_surface`
|
||||
* :meth:`~topology.Face.make_surface`
|
||||
* :meth:`~topology.Face.make_surface_from_array_of_points`
|
||||
* :meth:`~topology.Face.make_surface_from_curves`
|
||||
* :meth:`~topology.Face.make_surface_patch`
|
||||
|
||||
.. note::
|
||||
When creating a Solid from a Shell, the Shell must be "water-tight," meaning it
|
||||
should have no holes. For objects with complex Edges, it's best practice to reuse
|
||||
Edges in adjoining Faces whenever possible to avoid slight mismatches that can
|
||||
create openings.
|
||||
Surface modeling is an advanced technique. Robust results usually come from
|
||||
reusing the same :class:`~topology.Edge` objects across adjacent faces and
|
||||
ensuring the final :class:`~topology.Shell` is *water‑tight* or *manifold* (no gaps).
|
||||
|
||||
Finally, we'll create the frame around the heart as a simple extrusion of a planar
|
||||
shape defined by the perimeter of the heart and merge all of the components together:
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
.. code-block:: python
|
||||
tutorial_surface_heart_token.rst
|
||||
tutorial_spitfire_wing_gordon.rst
|
||||
|
||||
with BuildPart() as heart_token:
|
||||
with BuildSketch() as outline:
|
||||
with BuildLine():
|
||||
add(l1)
|
||||
add(l2)
|
||||
add(l3)
|
||||
Line(l3 @ 1, l1 @ 0)
|
||||
make_face()
|
||||
mirror(about=Plane.YZ)
|
||||
center = outline.sketch
|
||||
offset(amount=2, kind=Kind.INTERSECTION)
|
||||
add(center, mode=Mode.SUBTRACT)
|
||||
extrude(amount=2, both=True)
|
||||
add(heart)
|
||||
|
||||
Note that an additional planar line is used to close ``l1`` and ``l3`` so a ``Face``
|
||||
can be created. The :func:`~operations_generic.offset` function defines the outside of
|
||||
the frame as a constant distance from the heart itself.
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
In this tutorial, we've explored surface modeling techniques to create a non-planar
|
||||
heart-shaped object using build123d. By utilizing methods from the :class:`~topology.Face`
|
||||
class, such as :meth:`~topology.Face.make_surface`, we constructed the perimeter and
|
||||
central point of the surface. We then assembled the complete boundary of the object
|
||||
by creating the top, bottom, and sides, and combined them into a :class:`~topology.Shell`
|
||||
and eventually a :class:`~topology.Solid`. Finally, we added a frame around the heart
|
||||
using the :func:`~operations_generic.offset` function to maintain a constant distance
|
||||
from the heart.
|
||||