Compare commits
26 Commits
main
...
touhou/yas
Author | SHA1 | Date |
---|---|---|
|
bd7e8677c7 | |
|
c5f9e570a6 | |
|
63c2c74e02 | |
|
0fb88a97d3 | |
|
4edad88299 | |
|
83d4232ad7 | |
|
4d4e4c7eab | |
|
b83bf5a57d | |
|
670d4a8c21 | |
|
22a4f4ceec | |
|
a684996475 | |
|
5b5ccee94e | |
|
b88d52f4be | |
|
ca606c6bc1 | |
|
916ccee260 | |
|
44cd6ee960 | |
|
4dcd97613b | |
|
97675a2fc8 | |
|
74145f88d2 | |
|
878d532890 | |
|
7511efa9ee | |
|
3e0eab0cec | |
|
b15db172a0 | |
|
a74f919a5b | |
|
f4704b9ad6 | |
|
590033e492 |
|
@ -3,6 +3,9 @@
|
|||
This is the design repository for NorCal Hakkero Factory No. 1, where we use
|
||||
parametric CAD to make cosplay props.
|
||||
|
||||
> NorCal Hakkero Factory № 1
|
||||
> 北加国営八卦炉第一工場
|
||||
|
||||
## Development
|
||||
|
||||
Most cosplay schematics are created with Blender, CadQuery, and Inkscape. To
|
||||
|
|
|
@ -25,6 +25,9 @@ class Role(Flag):
|
|||
PARENT = auto()
|
||||
CHILD = auto()
|
||||
CASING = auto()
|
||||
STATOR = auto()
|
||||
ROTOR = auto()
|
||||
BEARING = auto()
|
||||
# Springs, cushions
|
||||
DAMPING = auto()
|
||||
# Main structural support
|
||||
|
@ -59,6 +62,9 @@ ROLE_COLOR_MAP = {
|
|||
Role.PARENT: _color('blue4', 0.6),
|
||||
Role.CASING: _color('dodgerblue3', 0.6),
|
||||
Role.CHILD: _color('darkorange2', 0.6),
|
||||
Role.STATOR: _color('gray', 0.5),
|
||||
Role.ROTOR: _color('blue3', 0.5),
|
||||
Role.BEARING: _color('green3', 0.8),
|
||||
Role.DAMPING: _color('springgreen', 1.0),
|
||||
Role.STRUCTURE: _color('gray', 0.4),
|
||||
Role.DECORATION: _color('lightseagreen', 0.4),
|
||||
|
@ -84,6 +90,7 @@ class Material(Enum):
|
|||
ACRYLIC_TRANSLUSCENT = 1.18, _color('ivory2', 0.8)
|
||||
ACRYLIC_TRANSPARENT = 1.18, _color('ghostwhite', 0.5)
|
||||
STEEL_SPRING = 7.8, _color('gray', 0.8)
|
||||
METAL_AL = 2.7, _color('gray', 0.6)
|
||||
METAL_BRASS = 8.5, _color('gold1', 0.8)
|
||||
|
||||
def __init__(self, density: float, color: Cq.Color):
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
import nhf.touhou.yasaka_kanako.onbashira as MO
|
||||
import nhf.touhou.yasaka_kanako.mirror as MM
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class Parameters(Model):
|
||||
|
||||
onbashira: MO.Onbashira = field(default_factory=lambda: MO.Onbashira())
|
||||
mirror: MM.Mirror = field(default_factory=lambda: MM.Mirror())
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="yasaka-kanako")
|
||||
|
||||
@submodel(name="onbashira")
|
||||
def submodel_onbashira(self) -> Model:
|
||||
return self.onbashira
|
||||
@submodel(name="mirror")
|
||||
def submodel_mirror(self) -> Model:
|
||||
return self.mirror
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
p = Parameters()
|
||||
if len(sys.argv) == 1:
|
||||
p.build_all()
|
||||
sys.exit(0)
|
|
@ -0,0 +1,186 @@
|
|||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
from nhf.materials import Role, Material
|
||||
import nhf.touhou.yasaka_kanako.onbashira as MO
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class Mirror(Model):
|
||||
"""
|
||||
Kanako's mirror, made of three levels.
|
||||
|
||||
The mirror suface is sandwiched between two layers of wood. As such, its
|
||||
dimensions have to sit in between that of the aperature on the surface, and
|
||||
the outer walls. The width/height here refers to the outer edge's width and height
|
||||
"""
|
||||
width: float = 100.0
|
||||
height: float = 120.0
|
||||
|
||||
inner_gap: float = 3.0
|
||||
outer_gap: float = 3.0
|
||||
|
||||
core_thickness: float = 25.4 / 8
|
||||
casing_thickness: float = 25.4 / 8
|
||||
|
||||
flange_r0: float = 8.0
|
||||
flange_r1: float = 20.0
|
||||
flange_y1: float = 12.0
|
||||
flange_y2: float = 25.0
|
||||
flange_hole_r: float = 8.0
|
||||
wing_x1: float = 15.0
|
||||
wing_x2: float = 24.0
|
||||
wing_r1: float = 10.0
|
||||
wing_r2: float = 16.0
|
||||
tail_r0: float = 8.0
|
||||
tail_r1: float = 13.0
|
||||
tail_y1: float = 16.0
|
||||
tail_y2: float = 29.0
|
||||
|
||||
# Necklace hole
|
||||
hole_diam: float = 5.0
|
||||
|
||||
material_mirror: Material = Material.ACRYLIC_TRANSPARENT
|
||||
material_casing: Material = Material.WOOD_BIRCH
|
||||
|
||||
@target(name="core", kind=TargetKind.DXF)
|
||||
def profile_core(self) -> Cq.Sketch:
|
||||
rx = self.width/2 - self.outer_gap
|
||||
ry = self.height/2 - self.outer_gap
|
||||
return Cq.Sketch().ellipse(rx, ry)
|
||||
|
||||
def core(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_core())
|
||||
.extrude(self.core_thickness)
|
||||
)
|
||||
|
||||
@target(name="casing-bot", kind=TargetKind.DXF)
|
||||
def profile_casing_bot(self) -> Cq.Sketch:
|
||||
"""
|
||||
Base of the casing with no holes carved out
|
||||
"""
|
||||
yt = self.height / 2 - self.outer_gap
|
||||
yh = (self.flange_y1 + self.flange_y2) / 2
|
||||
flange = (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(self.flange_r0, yt),
|
||||
(self.flange_r0, yt + self.flange_y1),
|
||||
(self.flange_r1, yt + self.flange_y1),
|
||||
(self.flange_r1, yt + self.flange_y2),
|
||||
(-self.flange_r1, yt + self.flange_y2),
|
||||
(-self.flange_r1, yt + self.flange_y1),
|
||||
(-self.flange_r0, yt + self.flange_y1),
|
||||
(-self.flange_r0, yt),
|
||||
])
|
||||
.push([
|
||||
(self.flange_hole_r, yt+yh),
|
||||
(-self.flange_hole_r, yt+yh),
|
||||
])
|
||||
.circle(self.hole_diam/2, mode="s")
|
||||
)
|
||||
tail = (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(+self.tail_r0, -yt),
|
||||
(+self.tail_r0, -yt - self.tail_y1),
|
||||
(+self.tail_r1, -yt - self.tail_y1),
|
||||
(+self.tail_r1, -yt - self.tail_y2),
|
||||
(-self.tail_r1, -yt - self.tail_y2),
|
||||
(-self.tail_r1, -yt - self.tail_y1),
|
||||
(-self.tail_r0, -yt - self.tail_y1),
|
||||
(-self.tail_r0, -yt),
|
||||
])
|
||||
)
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.ellipse(self.width/2, self.height/2)
|
||||
.boolean(flange, mode="a")
|
||||
.boolean(tail, mode="a")
|
||||
.boolean(self.profile_wing(-1), mode="a")
|
||||
.boolean(self.profile_wing(1), mode="a")
|
||||
)
|
||||
def casing_bot(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_casing_bot())
|
||||
.extrude(self.casing_thickness)
|
||||
)
|
||||
def profile_wing(self, sign: float=1) -> Cq.Sketch:
|
||||
xt = self.width / 2 - self.outer_gap
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(sign*xt, self.wing_r1),
|
||||
(sign*(xt+self.wing_x1), self.wing_r1),
|
||||
(sign*(xt+self.wing_x1), self.wing_r2),
|
||||
(sign*(xt+self.wing_x2), self.wing_r2),
|
||||
(sign*(xt+self.wing_x2), -self.wing_r2),
|
||||
(sign*(xt+self.wing_x1), -self.wing_r2),
|
||||
(sign*(xt+self.wing_x1), -self.wing_r1),
|
||||
(sign*xt, -self.wing_r1),
|
||||
])
|
||||
)
|
||||
@target(name="casing-mid", kind=TargetKind.DXF)
|
||||
def profile_casing_mid(self) -> Cq.Sketch:
|
||||
rx = self.width/2 - self.outer_gap
|
||||
ry = self.height/2 - self.outer_gap
|
||||
return (
|
||||
self.profile_casing_bot()
|
||||
.ellipse(rx, ry, mode="s")
|
||||
)
|
||||
def casing_mid(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_casing_mid())
|
||||
.extrude(self.core_thickness)
|
||||
)
|
||||
@target(name="casing-top", kind=TargetKind.DXF)
|
||||
def profile_casing_top(self) -> Cq.Sketch:
|
||||
rx = self.width/2 - self.outer_gap - self.inner_gap
|
||||
ry = self.height/2 - self.outer_gap - self.inner_gap
|
||||
return (
|
||||
self.profile_casing_bot()
|
||||
.ellipse(rx, ry, mode="s")
|
||||
)
|
||||
def casing_top(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_casing_top())
|
||||
.extrude(self.casing_thickness)
|
||||
)
|
||||
|
||||
@assembly()
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
return (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
self.core(),
|
||||
name="core",
|
||||
material=self.material_mirror,
|
||||
role=Role.DECORATION,
|
||||
loc=Cq.Location(0, 0, self.casing_thickness)
|
||||
)
|
||||
.addS(
|
||||
self.casing_bot(),
|
||||
name="casing_bot",
|
||||
material=self.material_casing,
|
||||
role=Role.CASING,
|
||||
)
|
||||
.addS(
|
||||
self.casing_mid(),
|
||||
name="casing_mid",
|
||||
material=self.material_casing,
|
||||
role=Role.CASING,
|
||||
loc=Cq.Location(0, 0, self.casing_thickness)
|
||||
)
|
||||
.addS(
|
||||
self.casing_top(),
|
||||
name="casing_top",
|
||||
material=self.material_casing,
|
||||
role=Role.CASING,
|
||||
loc=Cq.Location(0, 0, self.core_thickness + self.casing_thickness)
|
||||
)
|
||||
)
|
File diff suppressed because it is too large
Load Diff
|
@ -162,6 +162,15 @@ def tagPlane(self, tag: str,
|
|||
|
||||
Cq.Workplane.tagPlane = tagPlane
|
||||
|
||||
def tag_absolute(
|
||||
self,
|
||||
tag: str,
|
||||
loc: Union[Cq.Location, Tuple[float, float, float]],
|
||||
direction: Union[str, Cq.Vector, Tuple[float, float, float]] = '+Z'):
|
||||
return self.pushPoints([loc]).tagPlane(tag, direction=direction)
|
||||
|
||||
Cq.Workplane.tagAbsolute = tag_absolute
|
||||
|
||||
def make_sphere(r: float = 2) -> Cq.Solid:
|
||||
"""
|
||||
Makes a full sphere. The default function makes a hemisphere
|
||||
|
|
Loading…
Reference in New Issue