migrate to cadquery

This commit is contained in:
2024-11-10 01:21:10 +01:00
parent 07ee3c1fbf
commit dc68d65aed
71 changed files with 1165155 additions and 529 deletions

38
src/export.py Normal file
View File

@@ -0,0 +1,38 @@
import cadquery as cq
from parts.rail import rail
import presets
import os
for name, params in presets.presets:
part = rail(params)
c = part.val().BoundingBox().center
part = part.translate(c * -1)
part = part.rotate((0, 0, 0), (1, 0, 0), -90)
dir = os.path.dirname(f"{name}.x")
os.makedirs(f"STEPs/{dir}", exist_ok=True)
os.makedirs(f"STLs/{dir}", exist_ok=True)
os.makedirs(f"assets/{dir}", exist_ok=True)
cq.exporters.export(part, f"STEPs/{name}.step")
cq.exporters.export(part, f"STLs/{name}.stl")
cq.exporters.export(
part,
f"assets/{name}.svg",
opt={
"width": 480,
"height": 480,
"marginLeft": 48,
"marginRight": 0,
"marginTop": 112,
"marginBottom": 0,
"showAxes": False,
"showHidden": True,
"projectionDir": (
1,
1,
1,
),
"strokeColor": (200, 20, 20),
"hiddenColor": (100, 40, 40),
"perspective": params.radius,
},
)

8
src/main.py Normal file
View File

@@ -0,0 +1,8 @@
from presets import presets
from parts.rail import rail
name = "classic/C7"
params = [params for name, params in presets if name == name][0]
show_object(rail(params))

39
src/params.py Normal file
View File

@@ -0,0 +1,39 @@
from dataclasses import dataclass
import units as u
@dataclass
class Params:
radius: float
to: tuple
shell: bool
hollow_studs: bool
width: float = u.studs(4)
height = u.studs(1)
start_joint = True
end_joint = True
joint_studs = 2
nail_slot = True
nail_slot_size = (u.studs(1), u.ldu(1), u.ldu(1))
connector = True
connector_position = u.ldu(4)
connector_width = u.ldu(8)
connector_depth = u.ldu(1)
shell_mid_thickness = u.stud(1)
shell_mid_cut_thickness = u.ldu(4)
shell_support = True
standoff_height = u.brick(1)
standoff_padding = u.ldu(6)
standoff_studs = (1, 2)
teeth_height = u.ldu(7)
teeth_width = u.ldu(2)
teeth_spacing = u.ldu(1)
teeth_inner_width = u.ldu(10)
teeth_outer_width = u.ldu(14)

0
src/parts/__init__.py Normal file
View File

94
src/parts/body.py Normal file
View File

@@ -0,0 +1,94 @@
import cadquery as cq
from params import Params
import units as u
from parts.straight_joint import straight_joint_cut, straight_joint
from parts.shell_support import rail_shell_support
def rail_body(params: Params, path: cq.Wire):
half_height = params.height / 2
t0 = path.tangentAt(0)
plane = cq.Plane(path.positionAt(0), -t0.cross(cq.Vector(0, 0, 1)), t0)
workplane = (
cq.Workplane(plane)
.center(0, half_height)
.rect(params.width, params.height)
.sweep(path)
)
if params.start_joint:
workplane = workplane - straight_joint_cut(
params, cq.Plane(path.positionAt(0), path.tangentAt(0))
)
if params.end_joint:
workplane = workplane - straight_joint_cut(
params, cq.Plane(path.positionAt(1), path.tangentAt(1) * -1)
)
workplane = workplane.combine()
shell_thickness = (u.studs(1) - u.stud(1)) / 2
if params.shell:
workplane = workplane.faces("<Z").shell(-shell_thickness)
standoff_cut_depth = (
u.studs(params.joint_studs - params.standoff_studs[0]) + shell_thickness
)
standoff_offset = params.standoff_height - params.height
standoff_cut_height = (
(standoff_offset + params.height - shell_thickness)
if params.shell
else standoff_offset
)
standoff_cut_width = params.width - u.studs(params.standoff_studs[1])
if params.start_joint:
workplane = workplane + straight_joint(
params,
cq.Plane(
path.positionAt(0),
path.tangentAt(0),
),
)
if params.start_joint and (params.height < params.standoff_height or params.shell):
cut = cq.Workplane(
cq.Plane(
path.positionAt(0)
+ path.tangentAt(0) * u.studs(params.standoff_studs[0])
+ cq.Vector(0, 0, -standoff_offset),
path.tangentAt(0),
)
).box(
standoff_cut_depth,
standoff_cut_width,
standoff_cut_height,
centered=(False, True, False),
)
workplane = workplane - cut
if params.end_joint:
workplane = workplane + straight_joint(
params, cq.Plane(path.positionAt(1), path.tangentAt(1) * -1)
)
if params.end_joint and (params.height < params.standoff_height or params.shell):
cut = cq.Workplane(
cq.Plane(
(
path.positionAt(1)
- path.tangentAt(1) * u.studs(params.standoff_studs[0])
)
+ cq.Vector(0, 0, -standoff_offset),
-path.tangentAt(1),
)
).box(
standoff_cut_depth,
standoff_cut_width,
standoff_cut_height,
centered=(False, True, False),
)
workplane = workplane - cut
if params.shell and params.shell_support:
workplane = workplane + rail_shell_support(params, path)
return workplane

40
src/parts/rail.py Normal file
View File

@@ -0,0 +1,40 @@
import cadquery as cq
from params import Params
import units as u
from parts.body import rail_body
from parts.teeth import rail_teeth
def rail(params: Params):
path = cq.Wire.assembleEdges(
(
cq.Workplane("XY").polyline([(0, 0), params.to])
if params.to[0] == 0
else cq.Workplane("XY").radiusArc(params.to, params.radius)
).ctx.pendingEdges
)
dumb_path = cq.Wire.assembleEdges(
cq.Workplane("XY")
.spline(
[
(u.studs(0), u.studs(0)),
(u.studs(10), u.studs(10)),
(u.studs(20), u.studs(0)),
],
tangents=[(0, 1), (0, -1)],
)
.ctx.pendingEdges
)
dumb_path = cq.Wire.assembleEdges(
cq.Workplane("XY")
.polyline(
[
(u.studs(0), u.studs(0)),
(u.studs(5), u.studs(0)),
(u.studs(5), u.studs(5)),
],
)
.ctx.pendingEdges
)
return (rail_body(params, path) + rail_teeth(params, path)).combine()

View File

@@ -0,0 +1,88 @@
import cadquery as cq
from params import Params
import math
import units as u
def rail_shell_support(params: Params, path: cq.Wire):
t0 = path.tangentAt(0)
plane = cq.Plane(path.positionAt(0), -t0.cross(cq.Vector(0, 0, 1)), t0)
support = (
cq.Workplane(plane)
.center(0, params.height / 2)
.rect(params.shell_mid_thickness, params.height)
.sweep(path)
)
path_wire_length = path.Length()
shell_thickness = (u.studs(1) - u.stud(1)) / 2
target_width = params.width - shell_thickness * 4
square_width = target_width / math.sqrt(2)
support_count = math.floor(path_wire_length / target_width - 0.5)
support_count -= 1 - support_count % 2 # only odd number of supports
for i in range(1, support_count + 1):
d = i / (support_count + 1)
local_plane = cq.Plane(path.positionAt(d), path.tangentAt(d))
support_square = (
cq.Workplane(local_plane)
.box(
square_width, square_width, params.height, centered=(True, True, False)
)
.rotateAboutCenter((0, 0, 1), 45)
)
support = support + (
support_square.faces("|Z")
.shell(-shell_thickness)
.union(
cq.Workplane(local_plane).box(
shell_thickness,
params.width - shell_thickness,
params.height,
centered=(True, True, False),
)
- support_square
)
)
support = support - (
cq.Workplane(plane)
.center(0, params.height / 2)
.rect(params.shell_mid_cut_thickness, params.height)
.sweep(path)
)
for i in range(0, support_count + 1):
d = (i + 0.5) / (support_count + 1)
local_plane = cq.Plane(path.positionAt(d), path.tangentAt(d))
support = support + (
cq.Workplane(local_plane).box(
shell_thickness,
params.shell_mid_thickness - params.shell_mid_cut_thickness,
params.height,
centered=(True, True, False),
)
)
if params.start_joint:
support = support - (
cq.Workplane(cq.Plane(path.positionAt(0), path.tangentAt(0))).box(
u.studs(params.standoff_studs[0]),
params.width * 2,
params.height,
centered=(False, True, False),
)
)
if params.end_joint:
support = support - (
cq.Workplane(cq.Plane(path.positionAt(1), -path.tangentAt(1))).box(
u.studs(params.standoff_studs[0]),
params.width * 2,
params.height,
centered=(False, True, False),
)
)
return support

170
src/parts/straight_joint.py Normal file
View File

@@ -0,0 +1,170 @@
import cadquery as cq
from params import Params
import units as u
def straight_joint_cut(params: Params, plane: cq.Plane):
return cq.Workplane(plane).box(
u.studs(params.joint_studs),
params.width * 2,
params.height,
centered=(False, True, False),
)
def joint_studs(params: Params, plane: cq.Plane, face: cq.Face):
normal = face.normalAt()
workplane = (
cq.Workplane(cq.Plane(face.Center(), normal.cross(plane.zDir), normal))
.rarray(u.studs(1), u.studs(1), params.joint_studs, 1)
.cylinder(u.stud_height(1), u.stud(0.5), centered=(True, True, False))
)
if params.hollow_studs:
workplane = workplane.faces(cq.selectors.DirectionNthSelector(normal, 1)).hole(
u.stud(1) - u.ldu(4)
)
return workplane.vals()[0]
def straight_joint(params: Params, plane: cq.Plane):
half_width = (params.width - u.plate(2)) / 2
workplane = (
cq.Workplane(plane)
.box(
u.studs(params.joint_studs),
params.width - u.plate(2),
params.height,
centered=(False, True, False),
)
.faces(
cq.selectors.SumSelector(
cq.selectors.DirectionNthSelector(plane.yDir, 1),
cq.selectors.DirectionNthSelector(plane.yDir, 0),
)
)
.each(
lambda f: joint_studs(params, plane, f),
useLocalCoordinates=True,
)
)
if params.nail_slot:
workplane = (
workplane.faces(cq.selectors.DirectionNthSelector(plane.zDir, 1))
.sketch()
.push(
[
(0, half_width),
(0, -half_width),
]
)
.rect(
params.nail_slot_size[0],
params.nail_slot_size[1] * 2,
)
.finalize()
.cutBlind(-params.nail_slot_size[2])
)
if params.height < params.standoff_height:
workplane = (
workplane.faces("<Z")
.rect(
u.studs(params.joint_studs),
params.width - u.plate(2),
)
.extrude(params.height - params.standoff_height)
)
else:
workplane = (
workplane.faces("<Z")
.rect(
-u.studs(params.standoff_studs[0]),
u.studs(params.standoff_studs[1]),
centered=(False, True),
)
.cutBlind(params.standoff_height - params.height)
)
if params.connector:
face: cq.Face = workplane.faces(
cq.selectors.DirectionNthSelector(plane.xDir, 0)
).val()
normal = face.normalAt()
face_plane = cq.Plane(face.Center(), normal.cross(plane.zDir), normal)
x_pos = (
params.width - u.plate(2) - params.connector_width
) / 2 - params.connector_position
positive = (
cq.Workplane(face_plane)
.center(-x_pos, 0)
.box(
params.connector_width,
face.BoundingBox().zlen,
params.connector_depth * 2,
)
)
negative = (
cq.Workplane(face_plane)
.center(x_pos, 0)
.box(
params.connector_width,
face.BoundingBox().zlen,
params.connector_depth * 2,
)
)
workplane = workplane + positive - negative
if params.height < params.standoff_height:
h = params.standoff_height - params.height
d = (
params.width - u.studs(params.standoff_studs[1]) - params.standoff_padding
) / 2
x = (params.width - d) / 2
workplane = workplane - (
cq.Workplane(plane)
.pushPoints(
[(-params.connector_depth, x, -h), (-params.connector_depth, -x, -h)]
)
.box(
u.studs(params.joint_studs) + params.connector_depth,
d,
h,
centered=(False, True, False),
)
)
shell_thickness = (u.studs(1) - u.stud(1)) / 2
stud_slot_plane = cq.Plane(
plane.origin + cq.Vector(0, 0, params.height - shell_thickness),
plane.xDir,
-plane.zDir,
)
stud_slot = (
cq.Workplane(stud_slot_plane)
.center(u.studs(params.standoff_studs[0] / 2), 0)
.box(
u.studs(params.standoff_studs[0]) - params.standoff_padding,
u.studs(params.standoff_studs[1]),
params.height,
centered=(True, True, False),
)
)
workplane = workplane - stud_slot
for i in range(1, params.standoff_studs[0] + 1):
x = u.studs(i / 2)
y = u.studs(params.standoff_studs[1]) / 2 - (u.studs(1) - u.stud(1)) / 4
workplane = workplane + (
cq.Workplane(stud_slot_plane)
.pushPoints([(x, y), (x, -y)])
.box(
params.standoff_padding / 2,
(u.studs(1) - u.stud(1)) / 2,
params.height,
centered=(True, True, False),
)
)
return workplane

45
src/parts/teeth.py Normal file
View File

@@ -0,0 +1,45 @@
import cadquery as cq
from params import Params
import math
def tooth(params: Params, plane: cq.Plane):
return (
cq.Workplane(plane)
.box(params.teeth_width, params.teeth_outer_width, params.teeth_height)
.edges("|Z")
.chamfer(
(params.teeth_outer_width - params.teeth_inner_width) / 2,
params.teeth_width / 3,
)
)
def rail_teeth(params: Params, path: cq.Wire):
path_wire_length = path.Length()
teeth_count = math.floor(
path_wire_length / (params.teeth_width + params.teeth_spacing)
)
t0 = path.tangentAt(0)
workplane = (
cq.Workplane(cq.Plane(path.positionAt(0), -t0.cross(cq.Vector(0, 0, 1)), t0))
.center(0, params.height + params.teeth_height / 2)
.rect(params.teeth_inner_width, params.teeth_height)
.sweep(path)
)
for i in range(1, teeth_count + 1):
d = (i - 0.5) / (teeth_count)
workplane = workplane.add(
tooth(
params,
cq.Plane(
path.positionAt(d)
+ cq.Vector(0, 0, params.height + params.teeth_height / 2),
path.tangentAt(d),
),
)
)
return workplane.combine()

185
src/presets.py Normal file
View File

@@ -0,0 +1,185 @@
from params import Params
import units as u
presets = [
(
"classic/C7",
Params(
shell=True,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(1), u.studs(7)),
),
),
(
"classic/C15",
Params(
shell=True,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(5), u.studs(15)),
),
),
# (
# "classic/S4",
# Params(
# shell=True,
# hollow_studs=True,
# radius=u.studs(25),
# to=(u.studs(0), u.studs(4)),
# ),
# ),
(
"classic/S5",
Params(
shell=True,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(0), u.studs(5)),
),
),
(
"classic/S10",
Params(
shell=True,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(0), u.studs(10)),
),
),
(
"solid/C7",
Params(
shell=False,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(1), u.studs(7)),
),
),
(
"solid/C15",
Params(
shell=False,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(5), u.studs(15)),
),
),
(
"solid/S4",
Params(
shell=False,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(0), u.studs(4)),
),
),
(
"solid/S5",
Params(
shell=False,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(0), u.studs(5)),
),
),
(
"solid/S10",
Params(
shell=False,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(0), u.studs(10)),
),
),
(
"classic_solid_studs/C7",
Params(
shell=True,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(1), u.studs(7)),
),
),
(
"classic_solid_studs/C15",
Params(
shell=True,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(5), u.studs(15)),
),
),
# (
# "classic_solid_studs/S4",
# Params(
# shell=True,
# hollow_studs=False,
# radius=u.studs(25),
# to=(u.studs(0), u.studs(4)),
# ),
# ),
(
"classic_solid_studs/S5",
Params(
shell=True,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(0), u.studs(5)),
),
),
(
"classic_solid_studs/S10",
Params(
shell=True,
hollow_studs=False,
radius=u.studs(25),
to=(u.studs(0), u.studs(10)),
),
),
(
"solid_hollow_studs/C7",
Params(
shell=False,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(1), u.studs(7)),
),
),
(
"solid_hollow_studs/C15",
Params(
shell=False,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(5), u.studs(15)),
),
),
(
"solid_hollow_studs/S4",
Params(
shell=False,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(0), u.studs(4)),
),
),
(
"solid_hollow_studs/S5",
Params(
shell=False,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(0), u.studs(5)),
),
),
(
"solid_hollow_studs/S10",
Params(
shell=False,
hollow_studs=True,
radius=u.studs(25),
to=(u.studs(0), u.studs(10)),
),
),
]

22
src/units.py Normal file
View File

@@ -0,0 +1,22 @@
def ldu(value):
return value * 0.4
def stud(value):
return value * ldu(12)
def stud_height(value):
return value * ldu(4)
def studs(value):
return value * ldu(20)
def plate(value):
return value * ldu(8)
def brick(value):
return value * ldu(24)