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.box import box_with_centre_holes, MountingBox, Hole
|
||||||
from nhf.parts.joints import HirthJoint
|
from nhf.parts.joints import HirthJoint
|
||||||
from nhf.touhou.houjuu_nue.joints import ShoulderJoint, ElbowJoint, DiskJoint
|
from nhf.touhou.houjuu_nue.joints import ShoulderJoint, ElbowJoint, DiskJoint
|
||||||
|
from nhf.parts.planar import extrude_with_markers
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
|
@ -300,13 +301,13 @@ class WingProfile(Model):
|
||||||
c, s = math.cos(-theta), math.sin(-theta)
|
c, s = math.cos(-theta), math.sin(-theta)
|
||||||
tags = [
|
tags = [
|
||||||
# transforms [axle_dist, -sw/2] about the centre (tip_x, tip_y - sw/2)
|
# 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_x + axle_dist * c + (-sw/2) * s,
|
||||||
self.shoulder_tip_y - sw / 2 - axle_dist * s + (-sw/2) * c),
|
self.shoulder_tip_y - sw / 2 - axle_dist * s + (-sw/2) * c,
|
||||||
self.shoulder_joint.angle_neutral),
|
self.shoulder_joint.angle_neutral)),
|
||||||
("base", (base_dx, base_dy), 90),
|
("base", Cq.Location.from2d(base_dx, base_dy, 90)),
|
||||||
]
|
]
|
||||||
result = nhf.utils.extrude_with_markers(
|
result = extrude_with_markers(
|
||||||
self.profile_s0(),
|
self.profile_s0(),
|
||||||
self.panel_thickness,
|
self.panel_thickness,
|
||||||
tags,
|
tags,
|
||||||
|
@ -453,21 +454,21 @@ class WingProfile(Model):
|
||||||
shoulder_h = self.shoulder_joint.child_height
|
shoulder_h = self.shoulder_joint.child_height
|
||||||
h = (self.shoulder_joint.height - shoulder_h) / 2
|
h = (self.shoulder_joint.height - shoulder_h) / 2
|
||||||
tags_shoulder = [
|
tags_shoulder = [
|
||||||
("shoulder_bot", (0, h), 90),
|
("shoulder_bot", Cq.Location.from2d(0, h, 90)),
|
||||||
("shoulder_top", (0, h + shoulder_h), 270),
|
("shoulder_top", Cq.Location.from2d(0, h + shoulder_h, 270)),
|
||||||
]
|
]
|
||||||
h = self.elbow_height / 2
|
h = self.elbow_height / 2
|
||||||
tags_elbow = [
|
tags_elbow = [
|
||||||
("elbow_bot",
|
("elbow_bot", Cq.Location.from2d(
|
||||||
self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h - self.elbow_h2),
|
*self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h - self.elbow_h2),
|
||||||
self.elbow_angle + 0),
|
self.elbow_angle + 0)),
|
||||||
("elbow_top",
|
("elbow_top", Cq.Location.from2d(
|
||||||
self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h + self.elbow_h2),
|
*self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h + self.elbow_h2),
|
||||||
self.elbow_angle + 0),
|
self.elbow_angle + 0)),
|
||||||
]
|
]
|
||||||
profile = self.profile_s1()
|
profile = self.profile_s1()
|
||||||
tags = tags_shoulder + tags_elbow
|
tags = tags_shoulder + tags_elbow
|
||||||
return nhf.utils.extrude_with_markers(
|
return extrude_with_markers(
|
||||||
profile, self.panel_thickness, tags, reverse=front)
|
profile, self.panel_thickness, tags, reverse=front)
|
||||||
@submodel(name="spacer-s1-shoulder")
|
@submodel(name="spacer-s1-shoulder")
|
||||||
def spacer_s1_shoulder(self) -> MountingBox:
|
def spacer_s1_shoulder(self) -> MountingBox:
|
||||||
|
@ -526,25 +527,25 @@ class WingProfile(Model):
|
||||||
def surface_s2(self, front: bool = True) -> Cq.Workplane:
|
def surface_s2(self, front: bool = True) -> Cq.Workplane:
|
||||||
h = self.elbow_height / 2
|
h = self.elbow_height / 2
|
||||||
tags_elbow = [
|
tags_elbow = [
|
||||||
("elbow_bot",
|
("elbow_bot", Cq.Location.from2d(
|
||||||
self.elbow_to_abs(self.elbow_joint.child_arm_radius, h - self.elbow_h2),
|
*self.elbow_to_abs(self.elbow_joint.child_arm_radius, h - self.elbow_h2),
|
||||||
self.elbow_angle + 180),
|
self.elbow_angle + 180)),
|
||||||
("elbow_top",
|
("elbow_top", Cq.Location.from2d(
|
||||||
self.elbow_to_abs(self.elbow_joint.child_arm_radius, h + self.elbow_h2),
|
*self.elbow_to_abs(self.elbow_joint.child_arm_radius, h + self.elbow_h2),
|
||||||
self.elbow_angle + 180),
|
self.elbow_angle + 180)),
|
||||||
]
|
]
|
||||||
h = self.wrist_height / 2
|
h = self.wrist_height / 2
|
||||||
tags_wrist = [
|
tags_wrist = [
|
||||||
("wrist_bot",
|
("wrist_bot", Cq.Location.from2d(
|
||||||
self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h - self.wrist_h2),
|
*self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h - self.wrist_h2),
|
||||||
self.wrist_angle),
|
self.wrist_angle)),
|
||||||
("wrist_top",
|
("wrist_top", Cq.Location.from2d(
|
||||||
self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h + self.wrist_h2),
|
*self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h + self.wrist_h2),
|
||||||
self.wrist_angle),
|
self.wrist_angle)),
|
||||||
]
|
]
|
||||||
profile = self.profile_s2()
|
profile = self.profile_s2()
|
||||||
tags = tags_elbow + tags_wrist
|
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")
|
@submodel(name="spacer-s2-elbow")
|
||||||
def spacer_s2_elbow(self) -> MountingBox:
|
def spacer_s2_elbow(self) -> MountingBox:
|
||||||
return self.spacer_of_joint(
|
return self.spacer_of_joint(
|
||||||
|
@ -596,15 +597,15 @@ class WingProfile(Model):
|
||||||
front: bool = True) -> Cq.Workplane:
|
front: bool = True) -> Cq.Workplane:
|
||||||
h = self.wrist_height / 2
|
h = self.wrist_height / 2
|
||||||
tags = [
|
tags = [
|
||||||
("wrist_bot",
|
("wrist_bot", Cq.Location.from2d(
|
||||||
self.wrist_to_abs(self.wrist_joint.child_arm_radius, h - self.wrist_h2),
|
*self.wrist_to_abs(self.wrist_joint.child_arm_radius, h - self.wrist_h2),
|
||||||
self.wrist_angle + 180),
|
self.wrist_angle + 180)),
|
||||||
("wrist_top",
|
("wrist_top", Cq.Location.from2d(
|
||||||
self.wrist_to_abs(self.wrist_joint.child_arm_radius, h + self.wrist_h2),
|
*self.wrist_to_abs(self.wrist_joint.child_arm_radius, h + self.wrist_h2),
|
||||||
self.wrist_angle + 180),
|
self.wrist_angle + 180)),
|
||||||
]
|
]
|
||||||
profile = self.profile_s3()
|
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")
|
@submodel(name="spacer-s3-wrist")
|
||||||
def spacer_s3_wrist(self) -> MountingBox:
|
def spacer_s3_wrist(self) -> MountingBox:
|
||||||
return self.spacer_of_joint(
|
return self.spacer_of_joint(
|
||||||
|
|
35
nhf/utils.py
35
nhf/utils.py
|
@ -1,9 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Marking utilities for `Cq.Workplane`
|
Utility functions for cadquery objects
|
||||||
|
|
||||||
Adds the functions to `Cq.Workplane`:
|
|
||||||
1. `tagPoint`
|
|
||||||
2. `tagPlane`
|
|
||||||
"""
|
"""
|
||||||
import math
|
import math
|
||||||
import functools
|
import functools
|
||||||
|
@ -170,32 +166,3 @@ def get_abs_location(self: Cq.Assembly,
|
||||||
return loc
|
return loc
|
||||||
|
|
||||||
Cq.Assembly.get_abs_location = get_abs_location
|
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