Skip to content

Instantly share code, notes, and snippets.

@gongfan99
Last active October 25, 2025 02:37
Show Gist options
  • Select an option

  • Save gongfan99/6e8ea5575332411f96f6e91b93a88774 to your computer and use it in GitHub Desktop.

Select an option

Save gongfan99/6e8ea5575332411f96f6e91b93a88774 to your computer and use it in GitHub Desktop.
create the earbud model with build123d
# %%
from typing import Iterable
from build123d import *
from OCP.BRepBuilderAPI import BRepBuilderAPI_Sewing
from OCP.TopAbs import TopAbs_SHELL
from OCP.TopoDS import TopoDS
from ocp_vscode import Camera, set_defaults, show
set_defaults(reset_camera=Camera.CENTER, helper_scale=5, axes=True, axes0=True)
# %%
def get_tangent(u: float, edge: Edge, face: Face):
"""
Perpendicular to both face norm and edge
"""
norm = face.normal_at(edge @ u)
return norm.cross(edge % u)
def sew_faces(faces: Iterable[Face]):
"""
similar to Shell() but with higher sewing tolerance
"""
shell_builder = BRepBuilderAPI_Sewing(1e-4)
for face in faces:
if face.wrapped is not None:
shell_builder.Add(face.wrapped)
shell_builder.Perform()
shape = shell_builder.SewedShape()
if shape.ShapeType() != TopAbs_SHELL:
raise ValueError(f"ShapeType must be TopAbs_SHELL. It is {shape.ShapeType()}")
return Shell(TopoDS.Shell_s(shape))
"""
ellipse body
"""
body_length = 18
body_width = 8
body_height = 12
open_pos = 8
guide1 = Edge.make_ellipse(
body_width / 2, body_length / 2, start_angle=-90, end_angle=90, plane=Plane.XY
)
guide2 = Edge.make_ellipse(
body_length / 2, body_height / 2, end_angle=180, plane=Plane.YZ
)
guide1 = guide1.split(Plane.XZ.offset(open_pos), keep=Keep.BOTTOM)
assert isinstance(guide1, Edge)
guide2 = guide2.split(Plane.XZ.offset(open_pos), keep=Keep.BOTTOM)
assert isinstance(guide2, Edge)
vec10 = guide1 @ 0
vec20 = guide2 @ 1
profile1 = Edge.make_ellipse(abs(vec10.X), abs(vec20.Z), plane=Plane.XZ).translate(
(0, -open_pos, 0)
)
profile2 = Vector(0, body_length / 2, 0)
profiles = [profile1, profile2]
guides: list[Edge] = [
guide1,
guide2,
guide1.mirror(Plane.YZ),
guide2.mirror(Plane.XY),
]
face10 = Face.make_gordon_surface(profiles, guides) # ellipse body
"""
trim ellipse body
"""
loc30 = face10.location_at(0.75, 0.5)
edge30 = Edge.make_ellipse(5, 6).locate(loc30)
wire40 = edge30.project(face10, direction=loc30.z_axis.direction)
assert isinstance(wire40, Wire)
face50 = face10.split(Face.make_surface(wire40)) # body face after trim
assert isinstance(face50, Face)
"""
create base column
"""
edge20 = Edge.make_circle(2).rotate(Axis.Z, -90).translate((8, 5, -4))
face20 = Face.extrude(edge20, (0, 0, -15)) # column face
"""
create transition face
"""
profiles = [edge20, wire40._to_bspline()]
guides = []
for i in range(4):
u = i / 4
guides.append(
Spline(
profiles[0] @ u,
profiles[1] @ u,
tangents=(
get_tangent(u, profiles[0], face20),
get_tangent(u, profiles[1], face50),
),
)
)
face60 = Face.make_gordon_surface(profiles, guides) # transition face
"""
create final body
"""
faces = [
face20,
face50,
face60,
Face(Wire(face10.edges().sort_by(Axis.Y)[0])),
Face(Wire(face20.edges().sort_by(Axis.Z)[0])),
]
shell10 = sew_faces(faces)
solid10 = Solid(shell10)
assert solid10.is_valid
show(solid10)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment