Skip to content

Instantly share code, notes, and snippets.

@garyvdm
Created August 6, 2024 18:15
Show Gist options
  • Save garyvdm/c5daa72e2418ede4ca008ac121848b2c to your computer and use it in GitHub Desktop.
Save garyvdm/c5daa72e2418ede4ca008ac121848b2c to your computer and use it in GitHub Desktop.
from enum import Enum, auto
from math import atan2, cos, degrees, pi, tau, sin, sqrt, tan # NOQA
from pprint import pprint
from typing import Optional, Sequence # NOQA
from skspatial.objects import Circle, Line
from skspatial.objects import Point as SKSPoint
from skspatial.objects import Vector
from fullcontrol import ( # NOQA F401
Extruder,
GcodeControls,
GcodeComment,
ManualGcode,
PlotControls,
Point,
arcXY,
flatten,
transform,
first_point,
last_point,
)
# Units:
# * Lengths: mm
# * Angles: rad
od = 120.0
id = 81.0
pcd = 102
hole_d = 15
num_holes = 6
layer_height = 0.2
wall_space = 0.4
wall_width = 0.42
or_ = od / 2
ir = id / 2
hole_r = hole_d / 2
def vector_from_angle(angle, length=1):
return Vector([cos(angle) * length, sin(angle) * length])
def fc_Point(point: SKSPoint, z=None):
return Point(x=point[0], y=point[1], z=z)
def sks_Point(point: Point):
return SKSPoint([point.x, point.y])
center = SKSPoint([110, 110])
hole_angles = [tau * i / num_holes + 0.5 * tau for i in range(num_holes + 1)]
hole_centers = [
center + vector_from_angle(hole_angle, pcd / 2) for hole_angle in hole_angles
]
hole_circles = [
Circle(hole_center, hole_d / 2 + wall_space / 2) for hole_center in hole_centers
]
outer_circle = Circle(center, od / 2 - wall_space / 2)
inner_circle = Circle(center, id / 2 + wall_space / 2)
class Move(Enum):
Inwards = auto()
Outwards = auto()
def get_move_away_steps(point: Point, move: Move, z=None):
point = sks_Point(point)
v = Vector.from_points(center, point).unit() * 10
match move:
case Move.Inwards:
p = point - v
case Move.Outwards:
p = point + v
result = [
GcodeComment(text=f"Move away {move.name}"),
fc_Point(p, z),
]
return result
def extrude_after_first(
source,
pre_move: Optional[Move] = None,
post_move: Optional[Move] = None,
z=None,
):
source = list(source)
fp = first_point(source, False).model_copy(update=dict(z=z))
result = [
fp,
Extruder(on=True),
*source,
Extruder(on=False),
]
if pre_move is not None:
result[:0] = get_move_away_steps(fp, pre_move)
if post_move is not None:
result.extend(
get_move_away_steps(
last_point(source, False),
post_move,
z=layer_height if z is not None else None,
)
)
return result
def wall_circle(
center: SKSPoint,
or_=None,
ir=None,
z=None,
start_angle=0.5 * tau,
end_angle=tau,
comment=None,
pre_move=None,
post_move=None,
):
if or_:
wall_center_r = or_ - wall_width / 2
if ir:
wall_center_r = ir + wall_width / 2
steps = extrude_after_first(
arcXY(
fc_Point(center, z=z),
wall_center_r,
start_angle,
end_angle,
),
pre_move=pre_move,
post_move=post_move,
z=z,
)
if comment:
steps.insert(0, GcodeComment(text=comment))
return steps
def top_bottom_layer(z):
steps = [
*flatten(
wall_circle(
center,
or_ - i * wall_space,
z=z,
pre_move=Move.Outwards if i == 0 else None,
comment=f"Outer wall {i=}",
)
for i in range(2)
),
# 5/6 holes and fills
*flatten(hole_and_fill(hole_i, z) for hole_i in range(num_holes - 1)),
# inner walls
*flatten(
wall_circle(
center,
ir=ir + i * wall_space,
z=z,
start_angle=hole_angles[5],
comment=f"Inner wall {i=}",
)
for i in range(6)
),
# last hole and fill
flatten(hole_and_fill(5, z, Move.Outwards)),
]
return flatten(steps)
def hole_and_fill(hole_i, z, post_move=None):
return [
# 3 walls for holes
*[
wall_circle(
center=hole_centers[hole_i],
ir=hole_r + i * wall_space,
start_angle=(
hole_angles[hole_i]
if hole_i % 2 == 0
else hole_angles[hole_i] + 0.5 * tau
),
end_angle=tau if hole_i % 2 == 0 else 0 - tau,
z=z,
comment=f"Hole wall {hole_i=} {i=}",
)
for i in range(2)
],
# fill between holes
*extrude_after_first(
inter_hole_fill(
hole_i,
z,
),
post_move=post_move,
),
]
hole_outer_r = hole_r + wall_space * 2 + wall_width / 3
hole_outer_circles = [Circle(hole_center, hole_outer_r) for hole_center in hole_centers]
inter_hole_fill_or = or_ - 2 * wall_space
inter_hole_fill_ir = ir + 6 * wall_space
inter_hole_fill_num_arcs = round((inter_hole_fill_or - inter_hole_fill_ir) / wall_space)
inter_hole_fill_wall_space = (
inter_hole_fill_or - inter_hole_fill_ir
) / inter_hole_fill_num_arcs
def inter_hole_fill(
hole_i,
z,
):
yield GcodeComment(text=f"Inter hole fill {hole_i=}")
if hole_i % 2 == 0:
arc_steps = range(inter_hole_fill_num_arcs)
else:
arc_steps = range(inter_hole_fill_num_arcs - 1, -1, -1)
for i in arc_steps:
r = (
inter_hole_fill_or
- i * inter_hole_fill_wall_space
- inter_hole_fill_wall_space / 2
)
yield GcodeComment(text=f"Inter hole fill {hole_i=} {i=} {r=:0.3f}")
if i % 2 == 0:
yield from inter_hole_arc(
r,
hole_i,
(hole_angles[hole_i], hole_angles[hole_i + 1]),
z,
)
else:
yield from inter_hole_arc(
r,
hole_i,
(hole_angles[hole_i + 1], hole_angles[hole_i]),
z,
)
def inter_hole_arc(
r,
hole_i,
hole_angles: tuple[float, float],
z,
):
hole_circle: Circle = hole_outer_circles[hole_i]
intersection = Circle(center, r).intersect_circle(hole_circle)[0]
remove_angle = (
atan2(center[1] - intersection[1], center[0] - intersection[0])
- atan2(center[1] - hole_circle.point[1], center[0] - hole_circle.point[0])
) % tau
# print(
# f"({hole_center.x:0.3f}, {hole_center.y:0.3f}) "
# f"({intersection[0]:0.3f}, {intersection[1]:0.3f}) "
# f"{degrees(remove_angle)}"
# )
if hole_angles[0] > hole_angles[1]:
remove_angle = 0 - remove_angle
start_angle = hole_angles[0] + remove_angle
end_angle = hole_angles[1] - hole_angles[0] - remove_angle * 2
# print(
# f"{degrees(hole_angles[1]):0.1f} {degrees(hole_angles[0]):0.1f} "
# f"{degrees(remove_angle):0.1f} {degrees(end_angle):0.1f}"
# )
return arcXY(fc_Point(center, z=z), r, start_angle, end_angle)
middle_line_z = layer_height * 2
middle_fill_line_angle = tau / 24
middle_fill_line_radius_line_spacing = wall_space / sin(middle_fill_line_angle)
center_line_angle = tau / 12
center_lines = [
Line(center, vector_from_angle(hole_angle + center_line_angle, od / 2))
for hole_angle in hole_angles
]
class MiddleLineType(Enum):
Outer2HoleLine = auto()
Outer2Hole = auto()
Outer2Inner = auto()
Outer2CenterLine = auto()
Hole2Inner = auto()
Hole2HoleLine = auto()
def closest(points: Sequence[SKSPoint], other_point):
return min(
(point for point in points),
key=lambda point: point.distance_point(other_point),
)
def middle_fill_line(hole_i, i, side, reverse, type_: MiddleLineType):
comment = f"{type_.name} {hole_i=} {i=} {side=} {reverse=}"
# print(comment)
def return_process(points):
if reverse:
points = reversed(points)
return [
GcodeComment(text=comment),
*[Point(x=point[0], y=point[1], z=middle_line_z) for point in points],
]
hole_angle = hole_angles[hole_i]
hole_center = hole_centers[hole_i]
line = Line(
center
+ vector_from_angle(
hole_angle,
or_ - (i + 0.5) * middle_fill_line_radius_line_spacing,
),
vector_from_angle(hole_angle + middle_fill_line_angle * side),
)
outer_point = center + vector_from_angle(hole_angle, or_)
inner_point = center + vector_from_angle(hole_angle, ir)
match type_:
case MiddleLineType.Outer2HoleLine:
# Outer circle to hole center line
return return_process(
[
line.point,
closest(outer_circle.intersect_line(line), outer_point),
]
)
case MiddleLineType.Outer2Hole:
return return_process(
[
closest(hole_circles[hole_i].intersect_line(line), outer_point),
closest(outer_circle.intersect_line(line), outer_point),
]
)
case MiddleLineType.Outer2Inner:
return return_process(
[
closest(inner_circle.intersect_line(line), hole_center),
closest(outer_circle.intersect_line(line), hole_center),
]
)
case MiddleLineType.Outer2CenterLine:
if side == 1:
center_line = center_lines[hole_i]
else:
center_line = center_lines[hole_i - 1]
return return_process(
[
center_line.intersect_line(line),
closest(outer_circle.intersect_line(line), hole_center),
]
)
case MiddleLineType.Hole2Inner:
return return_process(
[
closest(inner_circle.intersect_line(line), inner_point),
closest(hole_circles[hole_i].intersect_line(line), inner_point),
]
)
case MiddleLineType.Hole2HoleLine:
return return_process(
[
line.point,
closest(hole_circles[hole_i].intersect_line(line), inner_point),
]
)
def middle_layer_hole(hole_i):
block1 = [
*flatten(
middle_fill_line(hole_i, i, -1, i % 2 == 0, MiddleLineType.Outer2Inner)
for i in range(64, 24, -1)
),
*flatten(
middle_fill_line(hole_i, i, -1, i % 2 == 0, MiddleLineType.Outer2Hole)
for i in range(24, 0, -1)
),
*middle_fill_line(hole_i, 0, -1, True, MiddleLineType.Outer2HoleLine),
*middle_fill_line(hole_i, 0, 1, False, MiddleLineType.Outer2HoleLine),
*flatten(
middle_fill_line(hole_i, i, 1, i % 2 != 0, MiddleLineType.Outer2Hole)
for i in range(1, 25)
),
*middle_fill_line(hole_i, 25, 1, True, MiddleLineType.Outer2Inner),
*flatten(
middle_fill_line(hole_i, i, 1, i % 2 != 0, MiddleLineType.Hole2Inner)
for i in range(24, 12, -1)
),
*middle_fill_line(hole_i, 12, 1, False, MiddleLineType.Hole2HoleLine),
*middle_fill_line(hole_i, 11, 1, True, MiddleLineType.Hole2HoleLine),
*middle_fill_line(hole_i, 11, -1, False, MiddleLineType.Hole2HoleLine),
*middle_fill_line(hole_i, 12, -1, True, MiddleLineType.Hole2HoleLine),
*flatten(
middle_fill_line(hole_i, i, -1, i % 2 == 0, MiddleLineType.Hole2Inner)
for i in range(13, 25)
),
# *middle_fill_line(
# hole_i,
# i,
# -1 if i % 2 == 0 else 1,
# True,
# MiddleLineType.Hole2HoleLine,
# ),
]
block2 = [
*flatten(
middle_fill_line(hole_i, i, 1, i % 2 != 0, MiddleLineType.Outer2Inner)
for i in range(26, 65)
),
*flatten(
[
*middle_fill_line(
hole_i + 1 if i % 2 == 0 else hole_i,
i,
-1 if i % 2 == 0 else 1,
True,
MiddleLineType.Outer2CenterLine,
),
*middle_fill_line(
hole_i if i % 2 == 0 else hole_i + 1,
i,
1 if i % 2 == 0 else -1,
False,
MiddleLineType.Outer2CenterLine,
),
]
for i in range(65, 78)
),
]
# pprint(steps)
return (
[GcodeComment(text=f"Middle {hole_i=}")]
+ extrude_after_first(
block1, pre_move=Move.Outwards, post_move=Move.Inwards, z=middle_line_z
)
+ extrude_after_first(
block2, pre_move=Move.Inwards, post_move=Move.Outwards, z=middle_line_z
)
)
def middle_layer():
return flatten(middle_layer_hole(i) for i in range(num_holes))
steps = [
Point(x=10, y=110, z=layer_height),
*top_bottom_layer(layer_height * 1),
*middle_layer(),
*top_bottom_layer(layer_height * 3),
Point(x=20, y=120, z=layer_height),
]
# transform(
# steps,
# "plot",
# PlotControls(
# style="line",
# initialization_data={
# "extrusion_width": wall_space,
# "extrusion_height": layer_height,
# },
# ),
# )
transform(
steps,
"gcode",
GcodeControls(
printer_name="ender_3",
# printer_name="creality_ender3v3se",
save_as="MGTC Rear Wheel Hub Gasket",
include_date=False,
initialization_data={
"primer": "side_lines",
"extrusion_width": wall_width,
"extrusion_height": layer_height,
"nozzle_temp": 220,
"bed_temp": 40,
"fan_percent": 100,
},
),
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment