Created
August 6, 2024 18:15
-
-
Save garyvdm/c5daa72e2418ede4ca008ac121848b2c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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