diff --git a/nhf/parts/planar.py b/nhf/parts/planar.py new file mode 100644 index 0000000..9405943 --- /dev/null +++ b/nhf/parts/planar.py @@ -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").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 diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index 697f2e1..2501043 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -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( diff --git a/nhf/utils.py b/nhf/utils.py index 7b7a16a..db8c73d 100644 --- a/nhf/utils.py +++ b/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").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