cosplay: Touhou/Houjuu Nue #4
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Operations on planar geometry (usually used for laser cutting parts)
|
||||
"""
|
||||
import math
|
||||
from typing import Tuple
|
||||
import cadquery as Cq
|
||||
|
||||
def extrude_with_markers(
|
||||
sketch: Cq.Sketch,
|
||||
thickness: float,
|
||||
tags: list[Tuple[str, Cq.Location]],
|
||||
reverse: bool = False):
|
||||
"""
|
||||
Extrudes a sketch and place tags on the sketch for mating.
|
||||
|
||||
Each tag is of the format `(name, loc)`, where the (must be 2d) location's
|
||||
angle is specifies in degrees counterclockwise from +X. Two marks are
|
||||
generated for each `name`, "{name}" for the location (with normal) and
|
||||
"{name}_dir" for the directrix specified by the angle.
|
||||
|
||||
This simulates a process of laser cutting and bonding (for wood and acrylic)
|
||||
"""
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(sketch)
|
||||
.extrude(thickness)
|
||||
)
|
||||
plane = result.faces("<Z" if reverse else ">Z").workplane()
|
||||
sign = -1 if reverse else 1
|
||||
for tag, p in tags:
|
||||
(x, y), angle = p.to2d()
|
||||
theta = sign * math.radians(angle)
|
||||
direction = (math.cos(theta), math.sin(theta), 0)
|
||||
plane.moveTo(x, sign * y).tagPlane(tag)
|
||||
plane.moveTo(x, sign * y).tagPlane(f"{tag}_dir", direction)
|
||||
return result
|
|
@ -12,6 +12,7 @@ from nhf.build import Model, TargetKind, target, assembly, submodel
|
|||
from nhf.parts.box import box_with_centre_holes, MountingBox, Hole
|
||||
from nhf.parts.joints import HirthJoint
|
||||
from nhf.touhou.houjuu_nue.joints import ShoulderJoint, ElbowJoint, DiskJoint
|
||||
from nhf.parts.planar import extrude_with_markers
|
||||
import nhf.utils
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
|
@ -300,13 +301,13 @@ class WingProfile(Model):
|
|||
c, s = math.cos(-theta), math.sin(-theta)
|
||||
tags = [
|
||||
# transforms [axle_dist, -sw/2] about the centre (tip_x, tip_y - sw/2)
|
||||
("shoulder", (
|
||||
("shoulder", Cq.Location.from2d(
|
||||
self.shoulder_tip_x + axle_dist * c + (-sw/2) * s,
|
||||
self.shoulder_tip_y - sw / 2 - axle_dist * s + (-sw/2) * c),
|
||||
self.shoulder_joint.angle_neutral),
|
||||
("base", (base_dx, base_dy), 90),
|
||||
self.shoulder_tip_y - sw / 2 - axle_dist * s + (-sw/2) * c,
|
||||
self.shoulder_joint.angle_neutral)),
|
||||
("base", Cq.Location.from2d(base_dx, base_dy, 90)),
|
||||
]
|
||||
result = nhf.utils.extrude_with_markers(
|
||||
result = extrude_with_markers(
|
||||
self.profile_s0(),
|
||||
self.panel_thickness,
|
||||
tags,
|
||||
|
@ -453,21 +454,21 @@ class WingProfile(Model):
|
|||
shoulder_h = self.shoulder_joint.child_height
|
||||
h = (self.shoulder_joint.height - shoulder_h) / 2
|
||||
tags_shoulder = [
|
||||
("shoulder_bot", (0, h), 90),
|
||||
("shoulder_top", (0, h + shoulder_h), 270),
|
||||
("shoulder_bot", Cq.Location.from2d(0, h, 90)),
|
||||
("shoulder_top", Cq.Location.from2d(0, h + shoulder_h, 270)),
|
||||
]
|
||||
h = self.elbow_height / 2
|
||||
tags_elbow = [
|
||||
("elbow_bot",
|
||||
self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h - self.elbow_h2),
|
||||
self.elbow_angle + 0),
|
||||
("elbow_top",
|
||||
self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h + self.elbow_h2),
|
||||
self.elbow_angle + 0),
|
||||
("elbow_bot", Cq.Location.from2d(
|
||||
*self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h - self.elbow_h2),
|
||||
self.elbow_angle + 0)),
|
||||
("elbow_top", Cq.Location.from2d(
|
||||
*self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h + self.elbow_h2),
|
||||
self.elbow_angle + 0)),
|
||||
]
|
||||
profile = self.profile_s1()
|
||||
tags = tags_shoulder + tags_elbow
|
||||
return nhf.utils.extrude_with_markers(
|
||||
return extrude_with_markers(
|
||||
profile, self.panel_thickness, tags, reverse=front)
|
||||
@submodel(name="spacer-s1-shoulder")
|
||||
def spacer_s1_shoulder(self) -> MountingBox:
|
||||
|
@ -526,25 +527,25 @@ class WingProfile(Model):
|
|||
def surface_s2(self, front: bool = True) -> Cq.Workplane:
|
||||
h = self.elbow_height / 2
|
||||
tags_elbow = [
|
||||
("elbow_bot",
|
||||
self.elbow_to_abs(self.elbow_joint.child_arm_radius, h - self.elbow_h2),
|
||||
self.elbow_angle + 180),
|
||||
("elbow_top",
|
||||
self.elbow_to_abs(self.elbow_joint.child_arm_radius, h + self.elbow_h2),
|
||||
self.elbow_angle + 180),
|
||||
("elbow_bot", Cq.Location.from2d(
|
||||
*self.elbow_to_abs(self.elbow_joint.child_arm_radius, h - self.elbow_h2),
|
||||
self.elbow_angle + 180)),
|
||||
("elbow_top", Cq.Location.from2d(
|
||||
*self.elbow_to_abs(self.elbow_joint.child_arm_radius, h + self.elbow_h2),
|
||||
self.elbow_angle + 180)),
|
||||
]
|
||||
h = self.wrist_height / 2
|
||||
tags_wrist = [
|
||||
("wrist_bot",
|
||||
self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h - self.wrist_h2),
|
||||
self.wrist_angle),
|
||||
("wrist_top",
|
||||
self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h + self.wrist_h2),
|
||||
self.wrist_angle),
|
||||
("wrist_bot", Cq.Location.from2d(
|
||||
*self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h - self.wrist_h2),
|
||||
self.wrist_angle)),
|
||||
("wrist_top", Cq.Location.from2d(
|
||||
*self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h + self.wrist_h2),
|
||||
self.wrist_angle)),
|
||||
]
|
||||
profile = self.profile_s2()
|
||||
tags = tags_elbow + tags_wrist
|
||||
return nhf.utils.extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||
return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||
@submodel(name="spacer-s2-elbow")
|
||||
def spacer_s2_elbow(self) -> MountingBox:
|
||||
return self.spacer_of_joint(
|
||||
|
@ -596,15 +597,15 @@ class WingProfile(Model):
|
|||
front: bool = True) -> Cq.Workplane:
|
||||
h = self.wrist_height / 2
|
||||
tags = [
|
||||
("wrist_bot",
|
||||
self.wrist_to_abs(self.wrist_joint.child_arm_radius, h - self.wrist_h2),
|
||||
self.wrist_angle + 180),
|
||||
("wrist_top",
|
||||
self.wrist_to_abs(self.wrist_joint.child_arm_radius, h + self.wrist_h2),
|
||||
self.wrist_angle + 180),
|
||||
("wrist_bot", Cq.Location.from2d(
|
||||
*self.wrist_to_abs(self.wrist_joint.child_arm_radius, h - self.wrist_h2),
|
||||
self.wrist_angle + 180)),
|
||||
("wrist_top", Cq.Location.from2d(
|
||||
*self.wrist_to_abs(self.wrist_joint.child_arm_radius, h + self.wrist_h2),
|
||||
self.wrist_angle + 180)),
|
||||
]
|
||||
profile = self.profile_s3()
|
||||
return nhf.utils.extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||
return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||
@submodel(name="spacer-s3-wrist")
|
||||
def spacer_s3_wrist(self) -> MountingBox:
|
||||
return self.spacer_of_joint(
|
||||
|
|
35
nhf/utils.py
35
nhf/utils.py
|
@ -1,9 +1,5 @@
|
|||
"""
|
||||
Marking utilities for `Cq.Workplane`
|
||||
|
||||
Adds the functions to `Cq.Workplane`:
|
||||
1. `tagPoint`
|
||||
2. `tagPlane`
|
||||
Utility functions for cadquery objects
|
||||
"""
|
||||
import math
|
||||
import functools
|
||||
|
@ -170,32 +166,3 @@ def get_abs_location(self: Cq.Assembly,
|
|||
return loc
|
||||
|
||||
Cq.Assembly.get_abs_location = get_abs_location
|
||||
|
||||
|
||||
def extrude_with_markers(sketch: Cq.Sketch,
|
||||
thickness: float,
|
||||
tags: list[Tuple[str, Tuple[float, float], float]],
|
||||
reverse: bool = False):
|
||||
"""
|
||||
Extrudes a sketch and place tags on the sketch for mating.
|
||||
|
||||
Each tag is of the format `(name, (x, y), angle)`, where the angle is
|
||||
specifies in degrees counterclockwise from +X. Two marks are generated for
|
||||
each `name`, "{name}" for the location (with normal) and "{name}_dir" for
|
||||
the directrix specified by the angle.
|
||||
|
||||
This simulates a process of laser cutting and bonding (for wood and acrylic)
|
||||
"""
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(sketch)
|
||||
.extrude(thickness)
|
||||
)
|
||||
plane = result.faces("<Z" if reverse else ">Z").workplane()
|
||||
sign = -1 if reverse else 1
|
||||
for tag, (px, py), angle in tags:
|
||||
theta = sign * math.radians(angle)
|
||||
direction = (math.cos(theta), math.sin(theta), 0)
|
||||
plane.moveTo(px, sign * py).tagPlane(tag)
|
||||
plane.moveTo(px, sign * py).tagPlane(f"{tag}_dir", direction)
|
||||
return result
|
||||
|
|
Loading…
Reference in New Issue