cosplay: Touhou/Houjuu Nue #4
|
@ -238,9 +238,13 @@ class WingRoot:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
panel_thickness: float = 25.4 / 16
|
panel_thickness: float = 25.4 / 16
|
||||||
|
spacer_thickness: float = 25.4 / 8
|
||||||
height: float = 100.0
|
height: float = 100.0
|
||||||
shoulder_width: float = 20.0
|
shoulder_width: float = 30.0
|
||||||
root_width: float = 60.0
|
root_width: float = 80.0
|
||||||
|
|
||||||
|
tip_x: float = -200.0
|
||||||
|
tip_y: float = 160.0
|
||||||
|
|
||||||
def outer_spline(self) -> list[Tuple[float, float]]:
|
def outer_spline(self) -> list[Tuple[float, float]]:
|
||||||
"""
|
"""
|
||||||
|
@ -248,34 +252,81 @@ class WingRoot:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def profile(self) -> Cq.Sketch:
|
def profile(self) -> Cq.Sketch:
|
||||||
tip_x, tip_y = -100.0, 70.0
|
|
||||||
sketch = (
|
sketch = (
|
||||||
Cq.Sketch()
|
Cq.Sketch()
|
||||||
.segment((-self.root_width, 0), (0, 0))
|
.segment((-self.root_width, 0), (0, 0))
|
||||||
.spline([
|
.spline([
|
||||||
(0, 0),
|
(0, 0),
|
||||||
(-30.0, 50.0),
|
(-30.0, 80.0),
|
||||||
(tip_x, tip_y)
|
(self.tip_x, self.tip_y)
|
||||||
])
|
])
|
||||||
.segment(
|
.segment(
|
||||||
(tip_x, tip_y),
|
(self.tip_x, self.tip_y),
|
||||||
(tip_x, tip_y - self.shoulder_width)
|
(self.tip_x, self.tip_y - self.shoulder_width)
|
||||||
)
|
)
|
||||||
.segment(
|
.segment(
|
||||||
(tip_x, tip_y - self.shoulder_width),
|
(self.tip_x, self.tip_y - self.shoulder_width),
|
||||||
(-self.root_width, 0)
|
(-self.root_width, 0)
|
||||||
)
|
)
|
||||||
.assemble()
|
.assemble()
|
||||||
)
|
)
|
||||||
return sketch
|
return sketch
|
||||||
|
|
||||||
def xy_surface(self) -> Cq.Workplane:
|
def spacer(self) -> Cq.Workplane:
|
||||||
|
"""
|
||||||
|
Creates a rectangular spacer. This could be cut from acrylic.
|
||||||
|
|
||||||
return (
|
There are two holes on the top of the spacer. With the holes
|
||||||
Cq.Workplane()
|
"""
|
||||||
.placeSketch(self.profile())
|
length = self.height
|
||||||
.extrude(self.panel_thickness)
|
width = 10.0
|
||||||
|
h = self.spacer_thickness
|
||||||
|
result = (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.sketch()
|
||||||
|
.rect(length, width)
|
||||||
|
.finalize()
|
||||||
|
.extrude(h)
|
||||||
)
|
)
|
||||||
|
# Tag the mating surfaces to be glued
|
||||||
|
result.faces("<X").workplane().tagPlane("left")
|
||||||
|
result.faces(">X").workplane().tagPlane("right")
|
||||||
|
|
||||||
|
# Tag the directrix
|
||||||
|
result.faces(">Z").tag("dir")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def surface(self, top: bool = False) -> Cq.Workplane:
|
||||||
|
tags = [
|
||||||
|
("shoulder", (self.tip_x, self.tip_y + 30), 0),
|
||||||
|
("base", (-self.root_width, 0), 90),
|
||||||
|
]
|
||||||
|
return nhf.utils.extrude_with_markers(
|
||||||
|
self.profile(),
|
||||||
|
self.panel_thickness,
|
||||||
|
tags,
|
||||||
|
reverse=not top,
|
||||||
|
)
|
||||||
|
|
||||||
|
def assembly(self) -> Cq.Assembly:
|
||||||
|
result = (
|
||||||
|
Cq.Assembly()
|
||||||
|
.add(self.surface(top=True), name="bot")
|
||||||
|
.add(self.surface(top=False), name="top")
|
||||||
|
.constrain("bot@faces@>Z", "top@faces@<Z", "Point",
|
||||||
|
param=self.height)
|
||||||
|
)
|
||||||
|
for t in ["shoulder", "base"]:
|
||||||
|
name = f"{t}_spacer"
|
||||||
|
(
|
||||||
|
result
|
||||||
|
.add(self.spacer(), name=name)
|
||||||
|
.constrain(f"{name}?left", f"bot?{t}", "Plane")
|
||||||
|
.constrain(f"{name}?right", f"top?{t}", "Plane")
|
||||||
|
.constrain(f"{name}?dir", f"top?{t}_dir", "Axis")
|
||||||
|
)
|
||||||
|
return result.solve()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WingProfile:
|
class WingProfile:
|
||||||
|
|
27
nhf/utils.py
27
nhf/utils.py
|
@ -6,9 +6,34 @@ Adds the functions to `Cq.Workplane`:
|
||||||
2. `tagPlane`
|
2. `tagPlane`
|
||||||
"""
|
"""
|
||||||
import math
|
import math
|
||||||
|
import functools
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf import Role
|
from nhf import Role
|
||||||
from typing import Union, Tuple
|
from typing import Union, Tuple, cast
|
||||||
|
|
||||||
|
# Bug fixes
|
||||||
|
def _subloc(self, name: str) -> Tuple[Cq.Location, str]:
|
||||||
|
"""
|
||||||
|
Calculate relative location of an object in a subassembly.
|
||||||
|
|
||||||
|
Returns the relative positions as well as the name of the top assembly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
rv = Cq.Location()
|
||||||
|
obj = self.objects[name]
|
||||||
|
name_out = name
|
||||||
|
|
||||||
|
if obj not in self.children and obj is not self:
|
||||||
|
locs = []
|
||||||
|
while not obj.parent is self:
|
||||||
|
locs.append(obj.loc)
|
||||||
|
obj = cast(Cq.Assembly, obj.parent)
|
||||||
|
name_out = obj.name
|
||||||
|
|
||||||
|
rv = functools.reduce(lambda l1, l2: l2 * l1, locs)
|
||||||
|
|
||||||
|
return (rv, name_out)
|
||||||
|
Cq.Assembly._subloc = _subloc
|
||||||
|
|
||||||
|
|
||||||
def tagPoint(self, tag: str):
|
def tagPoint(self, tag: str):
|
||||||
|
|
Loading…
Reference in New Issue