Skip to content

Commit

Permalink
Exploring raycasting and sweeps
Browse files Browse the repository at this point in the history
  • Loading branch information
Yeicor committed Jun 27, 2024
1 parent 9854fef commit 5e922ef
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 229 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/wip.blend*

# IDE
/*.iml
Expand All @@ -7,8 +8,8 @@
/*.stl
/*.step
/*.glb
!/reference.glb
!/reference.stl

# Python
/venv/
/__pycache__/
/__pycache__/
179 changes: 64 additions & 115 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import os
from math import *

from build123d import *
from build123d import export_stl

# %%

reference: ShapeList[Shell] = Mesher().read('reference.stl')[0].shells()
print('Loaded reference mesh with shells:', [shell.area for shell in reference.shells()])
reference: Shell = reference.shells().sort_by(SortBy.AREA)[-1].shell()
print('Loaded reference shell areas:', reference.area)

# %%

# General parameters
tol = 0.2 * MM # Tolerance for operations
eps = 0.0001 * MM # Epsilon for operations
Expand All @@ -19,140 +25,83 @@
cap_head_height = 15 * MM
cap_to_support_taper = -5 # degrees
cap_support_top_offset = -4 * MM
cap_support_profile = import_svg('profile.svg', is_inkscape_label=True).wire()
# cap_support_profile = import_svg('profile.svg', is_inkscape_label=True).wire()
cap_support_length = 2 * CM
cap_support_angle = 30 # degrees
cap_cut_hole_radius = 0 # 4 * MM
cap_cut_hole_offset_xz = (-13 * MM, -(cap_cut_hole_radius + wall) * MM)

# Sample the bottom of the reference to generate a curve
NUM_SAMPLES = 10
reference_bb = reference.bounding_box()
all_points = []
for i in range(NUM_SAMPLES):
y_pct = 0.54 + 0.185 * i / NUM_SAMPLES
y_abs = reference_bb.min.Y + y_pct * reference_bb.size.Y
bottom_point = reference.find_intersection(Axis((0, y_abs, reference_bb.max.Z), (0, 0, -1)))[-1][0]
print(f'Sample {i}: {bottom_point}')
all_points.append(bottom_point)

with BuildLine() as bottom_curve:
Spline(*all_points)

# Now create planes from each bottom curve point to figure out contours
NUM_SAMPLES = 10
all_contours = []
for i in range(NUM_SAMPLES):
pt_param = (i / (NUM_SAMPLES - 1))
sample_plane = Plane(bottom_curve.line ^ pt_param, x_dir=Vector(1, 0, 0))
sample_plane.origin = sample_plane.origin + sample_plane.y_dir * 3
with BuildSketch(sample_plane) as sample_circle:
Circle(radius=reference_bb.size.X / 2)
sample_circle = sample_circle.sketch.edge()
print(f'Sample {i}: {sample_plane.origin}')
# Collapse the perimeter of the sample circle to get the contour
NUM_SUB_SAMPLES = 50
points = []
for j in range(NUM_SUB_SAMPLES):
pt_param = (j / (NUM_SUB_SAMPLES - 1))
sample_from = sample_circle @ pt_param
sample_from_test = Location(sample_from)
sample_dir = (sample_plane.origin - sample_from).normalized()
print(f'Subsample {j}: from {sample_from} to {sample_plane.origin}')
intersections = reference.find_intersection(Axis(sample_from, sample_dir))
print(f'Subsample {j}: with {len(intersections)} intersections')
if len(intersections) == 0:
continue
expected = intersections[0][0]
points.append(expected)
with BuildLine() as contour:
try:
Spline(*points) # TODO: Match tangents
except Exception as e:
print(f'Failed to create contour {i}: {e}')
continue
all_contours.append(contour.line)
del sample_circle, contour

def core_filled(offset: float = 0):
with BuildPart() as _core_filled: # Useful to offset a wrapper and to cut the support!
with BuildSketch():
Circle(cap_radius + offset)
shank_height = cap_height - cap_head_height
shank_height_flat = shank_height_tapered = shank_height / 2
extrude(amount=shank_height_flat)
shank_taper_angle = degrees(-atan2(cap_head_radius - cap_radius, shank_height_tapered))
extrude(faces().group_by(Axis.Z)[-1], amount=shank_height_tapered, taper=shank_taper_angle)
extrude(faces().group_by(Axis.Z)[-1], amount=cap_head_height + offset)
split(bisect_by=Plane.YZ, keep=Keep.BOTTOM) # Rectangular support half
extrude(faces().group_by(Axis.X)[-1], amount=cap_head_radius + offset, taper=cap_to_support_taper)
cap_hole_sketch_to_cut = None
if cap_cut_hole_radius > 0:
bb = _core_filled.part.bounding_box()
with BuildSketch(Plane.XZ.shift_origin(
(bb.max.X + cap_cut_hole_offset_xz[0], 0, bb.max.Z + cap_cut_hole_offset_xz[1])).offset(
-(bb.min.Y - wall))) as cap_hole_sketch_to_cut:
Circle(cap_cut_hole_radius)
Rectangle(2 * cap_cut_hole_radius, bb.size.Z, align=(Align.CENTER, Align.MAX))
return _core_filled, cap_hole_sketch_to_cut


with BuildLine() as support_line:
bb = cap_support_profile.bounding_box()
add(cap_support_profile.translate((-bb.min.X, -(bb.max.Y + wall))))
mirror(about=Plane.YZ)
del cap_support_profile

core_filled_base, cap_hole_sketch_to_cut = core_filled()
core_filled_extra = core_filled(wall)[0]
m = core_filled_base.part.bounding_box().max
support_loc = Pos(m.X, 0, m.Z + cap_support_top_offset) * Plane.YZ.location
extrude_dir = Vector(cos(radians(cap_support_angle)), 0, sin(radians(cap_support_angle))).normalized()
extrude_in_length = wall / cos(radians(cap_support_angle))
with BuildPart() as support_cut:
with BuildSketch(support_loc) as sk:
with BuildLine():
add(support_line)
min_x_point = support_line.line.vertices().group_by(Axis.X)[0].group_by(Axis.Z)[0].vertex().center()
max_x_point = support_line.line.vertices().group_by(Axis.X)[-1].group_by(Axis.Z)[0].vertex().center()
min_conn = support_line.line @ 0
max_conn = support_line.line @ 1
Polyline(min_conn,
Vector(min_x_point.X, min_conn.Y, min_x_point.Z),
min_x_point - Vector(0, cap_height, 0),
max_x_point - Vector(0, cap_height, 0),
Vector(max_x_point.X, max_conn.Y, max_x_point.Z),
max_conn)
make_face()
extrude(sk.sketch, amount=extrude_in_length, dir=-extrude_dir, both=True)
del sk

with BuildPart() as support:
with BuildSketch(support_loc) as sk:
with BuildLine():
add(support_line)
inside_wire = Wire(edges())
offset(amount=wall, side=Side.RIGHT) # Make sure this side is the outside...
outside_wire = Wire([e for e in edges() if min([e.distance(e2) for e2 in inside_wire.edges()]) > eps])
make_face()
with BuildLine(): # Round the corners
p1 = outside_wire @ 0
p2 = inside_wire @ 1
t1 = -(outside_wire % 0)
a = JernArc(p1, t1, (p2 - p1).length / 2, 180)
if (a @ 1 - p2).length > eps:
Line(a @ 1, p2)
Line(p2, p1)
make_face()
with BuildLine(): # Round the corners
p1 = inside_wire @ 0
p2 = outside_wire @ 1
t1 = -(inside_wire % 0)
a = JernArc(p1, t1, (p2 - p1).length / 2, 180)
if (a @ 1 - p2).length > eps:
Line(a @ 1, p2)
Line(p2, p1)
make_face()
del inside_wire, outside_wire
extrude(sk.sketch, amount=cap_support_length, dir=extrude_dir)
del sk
loft_base = faces().group_by(Axis.X)[-1].face()
bb = loft_base.bounding_box()
rotate_from = Vector(bb.center().X, bb.center().Y, bb.max.Z)
assert fabs(rotate_from.Y) <= eps, rotate_from
loft_top = loft_base.rotate(Axis(rotate_from, Vector(0, 1, 0)), -cap_support_angle)
loft_top = loft_top.translate( # HACK!
(0, 0, (bb.size.Z - loft_top.bounding_box().size.Z) / 2))
loft([loft_base, loft_top])
del loft_base, loft_top
add(core_filled_base, mode=Mode.SUBTRACT)
del support_line, support_loc

with (BuildPart() as bike_fork_cap):
add(core_filled_extra)
add(core_filled_base, mode=Mode.SUBTRACT) # HACK: alternative to offset that does not crash
del core_filled_extra, core_filled_base
add(support_cut, mode=Mode.SUBTRACT)
del support_cut
bb = bike_fork_cap.part.bounding_box()
cut_from_support = Box(bb.size.X, bb.size.Y, bb.size.Z, align=Align.MAX, mode=Mode.PRIVATE
).translate((bb.max.X, bb.max.Y, bb.min.Z))
add(support.part - cut_from_support)
del support, cut_from_support
if cap_hole_sketch_to_cut is not None:
add(extrude(to_extrude=cap_hole_sketch_to_cut.sketch, amount=-cap_head_radius), mode=Mode.SUBTRACT)
del cap_hole_sketch_to_cut
# %%

# TODO: crazy sweep with ALL the contours and the bottom curve
bike_fork_cap = sweep(sections=[Wire(c) for c in all_contours], path=bottom_curve.line)
del bottom_curve, all_contours

# %%
# -- export/show boilerplate --

if os.getenv('export_stl'):
print('Exporting STL file...')
export_stl(bike_fork_cap.part, 'bike_fork_cap.stl')
export_stl(bike_fork_cap, 'bike_fork_cap.stl')

if os.getenv('export_step'):
print('Exporting STEP file...')
export_step(bike_fork_cap.part, 'bike_fork_cap.step')
export_step(bike_fork_cap, 'bike_fork_cap.step')

try:
from yacv_server import *

show_all()

with open('reference.glb', 'rb') as f:
show(f.read(), names=['reference'], auto_clear=False)

if os.getenv('export_yacv'):
print('Exporting YACV file...')
export_all('.', lambda name, obj: name == 'bike_fork_cap')
Expand Down
112 changes: 0 additions & 112 deletions profile.svg

This file was deleted.

Binary file removed reference.glb
Binary file not shown.
Binary file added reference.stl
Binary file not shown.

0 comments on commit 5e922ef

Please sign in to comment.