diff --git a/nhf/materials.py b/nhf/materials.py index bc172c3..e931403 100644 --- a/nhf/materials.py +++ b/nhf/materials.py @@ -84,6 +84,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_BRASS = 8.5, _color('gold1', 0.8) def __init__(self, density: float, color: Cq.Color): self.density = density @@ -116,6 +117,9 @@ def add_with_material_role( Cq.Assembly.addS = add_with_material_role def color_by_material(self: Cq.Assembly) -> Cq.Assembly: + """ + Set colours in an assembly by material + """ for _, a in self.traverse(): if KEY_MATERIAL not in a.metadata: continue @@ -123,6 +127,9 @@ def color_by_material(self: Cq.Assembly) -> Cq.Assembly: return self Cq.Assembly.color_by_material = color_by_material def color_by_role(self: Cq.Assembly, avg: bool = True) -> Cq.Assembly: + """ + Set colours in an assembly by role + """ for _, a in self.traverse(): if KEY_ROLE not in a.metadata: continue diff --git a/nhf/touhou/__init__.py b/nhf/touhou/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nhf/touhou/shiki_eiki/__init__.py b/nhf/touhou/shiki_eiki/__init__.py new file mode 100644 index 0000000..c97eff5 --- /dev/null +++ b/nhf/touhou/shiki_eiki/__init__.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass, field +import cadquery as Cq +from nhf.build import Model, TargetKind, target, assembly, submodel +import nhf.touhou.shiki_eiki.rod as MR +import nhf.touhou.shiki_eiki.crown as MC +import nhf.touhou.shiki_eiki.epaulette as ME +import nhf.utils + +@dataclass +class Parameters(Model): + + rod: MR.Rod = field(default_factory=lambda: MR.Rod()) + crown: MC.Crown = field(default_factory=lambda: MC.Crown()) + epaulette_ze: ME.Epaulette = field(default_factory=lambda: ME.Epaulette(side="ze")) + epaulette_hi: ME.Epaulette = field(default_factory=lambda: ME.Epaulette(side="hi")) + + def __post_init__(self): + super().__init__(name="shiki-eiki") + + @submodel(name="rod") + def submodel_rod(self) -> Model: + return self.rod + @submodel(name="crown") + def submodel_crown(self) -> Model: + return self.crown + @submodel(name="epaulette_ze") + def submodel_epaulette_ze(self) -> Model: + return self.epaulette_ze + @submodel(name="epaulette_hi") + def submodel_epaulette_hi(self) -> Model: + return self.epaulette_hi + + +if __name__ == '__main__': + import sys + + p = Parameters() + if len(sys.argv) == 1: + p.build_all() + sys.exit(0) diff --git a/nhf/touhou/shiki_eiki/crown.py b/nhf/touhou/shiki_eiki/crown.py new file mode 100644 index 0000000..bbeb3d5 --- /dev/null +++ b/nhf/touhou/shiki_eiki/crown.py @@ -0,0 +1,790 @@ +from nhf import Material, Role +from nhf.build import Model, target, assembly, TargetKind +import nhf.utils + +import math +from typing import Optional +from dataclasses import dataclass, field +from enum import Enum +import cadquery as Cq + +class AttachPoint(Enum): + DOVETAIL_IN = 1 + DOVETAIL_OUT = 2 + NONE = 3 + # Inset slot for front surface attachment j + SLOT = 4 + +@dataclass +class Crown(Model): + + facets: int = 5 + # Lower circumference + base_circ: float = 538.0 + # Upper circumference, at the middle + tilt_circ: float = 640.0 + front_base_circ: float = (640.0 + 538.0) / 2 + # Total height + height: float = 120.0 + + # Front guard has a wing that inserts into the side guards. + front_wing_angle: float = 9.0 + front_wing_dh: float = 40.0 + front_wing_height: float = 20.0 + + margin: float = 10.0 + + thickness: float = 0.4 # 26 Gauge + side_guard_thickness: float = 15.0 + side_guard_channel_radius: float = 90 + side_guard_channel_height: float = 10 + side_guard_hole_height: float = 15.0 + side_guard_hole_diam: float = 1.5 + side_guard_dovetail_height: float = 30.0 + + side_guard_slot_width: float = 22.0 + side_guard_slot_angle: float = 18.0 + # brass insert thickness + slot_thickness: float = 2.0 + slot_width: float = 20.0 + slot_tilt: float = 60 + + material: Material = Material.METAL_BRASS + material_side: Material = Material.PLASTIC_PLA + + def __post_init__(self): + super().__init__(name="crown") + + assert self.tilt_circ > self.base_circ + assert self.facet_width_upper / 2 > self.height / 2, "Top angle must be > 90 degrees" + assert self.side_guard_channel_radius > self.radius_lower + + assert self.front_wing_angle < 180 / self.facets + assert self.front_wing_dh + self.front_wing_height < self.height + assert self.slot_phi < 2 * math.pi / self.facets + + @property + def facet_width_lower(self): + return self.base_circ / self.facets + @property + def facet_width_upper(self): + return self.tilt_circ / self.facets + @property + def radius_lower(self): + return self.base_circ / (2 * math.pi) + @property + def radius_middle(self): + return self.tilt_circ / (2 * math.pi) + @property + def radius_upper(self): + return (self.tilt_circ + (self.tilt_circ - self.base_circ)) / (2 * math.pi) + + @property + def radius_lower_front(self): + return self.front_base_circ / (2 * math.pi) + @property + def radius_middle_front(self): + return self.radius_lower_front + (self.radius_middle - self.radius_lower) + @property + def radius_upper_front(self): + return self.radius_lower_front + (self.radius_upper - self.radius_lower) + + @property + def slot_r0(self): + return self.radius_lower + self.thickness / 2 + @property + def slot_r1(self): + return self.radius_upper + self.thickness / 2 + + @property + def slot_h0(self) -> float: + """ + Phantom height formed by similar triangle, i.e. h0 in + + (h0 + h) / r2 = h0 / r1 + """ + rat = self.slot_r0 / (self.slot_r1 - self.slot_r0) + return self.height * rat + @property + def slot_theta(self) -> float: + """ + Cone tilt, related to other quantities by + h0 = r1 * cot theta + """ + h = self.height + return math.atan(self.slot_r0 / (self.height + self.slot_h0)) + @property + def slot_phi(self) -> float: + """ + When a slice of the crown is expanded (via Gauss's Theorema Egregium), + it does not form a full circle. phi is the angle of one of the slices. + + Note that on the cone itself, the angular slice is `2 pi / n` which `n` + is the number of sides. + """ + arc = self.slot_r0 * math.pi * 2 / self.facets + rho = self.slot_h0 / math.cos(self.slot_theta) + return arc / rho + + + def profile_base(self) -> Cq.Sketch: + # Generate a conical pentagonal shape + + y0 = self.slot_h0 / math.cos(self.slot_theta) + yh = (self.height/2 + self.slot_h0) / math.cos(self.slot_theta) + yq = (self.height*3/4 + self.slot_h0) / math.cos(self.slot_theta) + y1 = (self.height + self.slot_h0) / math.cos(self.slot_theta) + phi2 = self.slot_phi / 2 + + return ( + Cq.Sketch() + .segment( + (y0 * math.sin(phi2), y0 * (-1 + math.cos(phi2))), + (yh * math.sin(phi2), -y0 + yh * math.cos(phi2)), + ) + .arc( + (yh * math.sin(phi2), -y0 + yh * math.cos(phi2)), + (yq * math.sin(phi2/2), -y0 + yq * math.cos(phi2/2)), + (0, y1 - y0), + ) + .arc( + (-yh * math.sin(phi2), -y0 + yh * math.cos(phi2)), + (-yq * math.sin(phi2/2), -y0 + yq * math.cos(phi2/2)), + (0, y1 - y0), + ) + .segment( + (-y0 * math.sin(phi2), y0 * (-1 + math.cos(phi2))), + (-yh * math.sin(phi2), -y0 + yh * math.cos(phi2)), + ) + .arc( + (y0 * math.sin(phi2), -y0 + y0 * math.cos(phi2)), + (0, 0), + (-y0 * math.sin(phi2), y0 * (-1 + math.cos(phi2))), + ) + .assemble() + ) + + @target(name="side", kind=TargetKind.DXF) + def profile_side(self) -> Cq.Sketch: + dy = self.facet_width_upper * 0.1 + x_side = self.facet_width_upper + y_tip = self.height - self.margin + + eye = ( + Cq.Sketch() + .segment( + (0, y_tip), + (dy, y_tip - dy), + ) + .segment( + (0, y_tip), + (-dy, y_tip - dy), + ) + .bezier([ + (dy, y_tip - dy), + (0, y_tip - dy/2), + (0, y_tip - dy/2), + (-dy, y_tip - dy), + ]) + .assemble() + ) + return ( + self.profile_base() + .boolean(eye, mode='s') + ) + + @target(name="dot", kind=TargetKind.DXF) + def profile_dot(self) -> Cq.Sketch: + return ( + Cq.Sketch() + .circle(self.margin / 2) + ) + + def profile_front_wing(self, mirror: bool) -> Cq.Sketch: + # Add the two wings to the base profile + hw = self.front_wing_height / math.cos(self.slot_theta) + hw0 = (self.front_wing_dh + self.slot_h0) / math.cos(self.slot_theta) + hw1 = hw0 + hw + y0 = self.slot_h0 / math.cos(self.slot_theta) + # Calculate angle of wing analogously to `this.slot_phi`. This arc's + # radius is hw0. + wing_arc = self.slot_r0 * math.radians(self.front_wing_angle) + phi_w = wing_arc / hw0 + sign = -1 if mirror else 1 + phi2 = self.slot_phi / 2 + return ( + Cq.Sketch() + .segment( + (sign * hw0 * math.sin(phi2), -y0 + hw0 * math.cos(phi2)), + (sign * hw1 * math.sin(phi2), -y0 + hw1 * math.cos(phi2)), + ) + .segment( + (sign * hw0 * math.sin(phi2+phi_w), -y0 + hw0 * math.cos(phi2+phi_w)), + (sign * hw1 * math.sin(phi2+phi_w), -y0 + hw1 * math.cos(phi2+phi_w)), + ) + .arc( + (sign * hw0 * math.sin(phi2), -y0 + hw0 * math.cos(phi2)), + (sign * hw0 * math.sin(phi2+phi_w/2), -y0 + hw0 * math.cos(phi2+phi_w/2)), + (sign * hw0 * math.sin(phi2+phi_w), -y0 + hw0 * math.cos(phi2+phi_w)), + ) + .arc( + (sign * hw1 * math.sin(phi2), -y0 + hw1 * math.cos(phi2)), + (sign * hw1 * math.sin(phi2+phi_w/2), -y0 + hw1 * math.cos(phi2+phi_w/2)), + (sign * hw1 * math.sin(phi2+phi_w), -y0 + hw1 * math.cos(phi2+phi_w)), + ) + .assemble() + ) + + + @target(name="front", kind=TargetKind.DXF) + def profile_front(self) -> Cq.Sketch: + + profile_base = ( + self.profile_base() + .boolean(self.profile_front_wing(False), mode='a') + .boolean(self.profile_front_wing(True), mode='a') + ) + + + dx_l = self.facet_width_lower + dx_u = self.facet_width_upper + dy = self.height + + window_length = dy / 5 + window_height = self.margin / 2 + window = ( + Cq.Sketch() + .rect(window_length, window_height) + ) + window_p1 = Cq.Location.from2d( + dx_u/2 - self.margin - window_length * 0.4, + dy/2 + self.margin/2, + math.degrees(math.atan2(dy/2, -dx_u/2) * 0.95), + ) + window_p2 = Cq.Location.from2d( + dx_l/2 - self.margin + window_length * 0.15, + window_length/2 + self.margin, + math.degrees(math.atan2(dy/2, (dx_u-dx_l)/2)), + ) + + # Carve the scale + z = dy * 1/32 # "Pen" Thickness + scale_pan_x = dx_l / 2 * 0.6 + scale_pan_y = dy / 2 * 0.7 + pan_dx = dx_l * 1/4 + pan_dy = dy * 1/16 + + scale_pan = ( + Cq.Sketch() + .arc( + (- pan_dx/2, pan_dy), + (0, 0), + (+ pan_dx/2, pan_dy), + ) + .segment( + (+pan_dx/2, pan_dy), + (+pan_dx/2 - z, pan_dy), + ) + .arc( + (-pan_dx/2 + z, pan_dy), + (0, z), + (+pan_dx/2 - z, pan_dy), + ) + .segment( + (-pan_dx/2, pan_dy), + (-pan_dx/2 + z, pan_dy), + ) + .assemble() + ) + loc_scale_pan = Cq.Location.from2d(scale_pan_x, scale_pan_y) + loc_scale_pan2 = Cq.Location.from2d(-scale_pan_x, scale_pan_y) + + scale_base_y = dy / 2 * 0.36 + scale_base_x = dx_l / 10 + assert scale_base_y < scale_pan_y + assert scale_base_x < scale_pan_x + + scale_body = ( + Cq.Sketch() + .arc( + (scale_pan_x, scale_pan_y), + (0, scale_base_y), + (-scale_pan_x, scale_pan_y), + ) + .segment( + (-scale_pan_x, scale_pan_y), + (-scale_pan_x+z, scale_pan_y+z), + ) + .arc( + (scale_pan_x - z, scale_pan_y+z), + (0, scale_base_y + z), + (-scale_pan_x + z, scale_pan_y+z), + ) + .segment( + (scale_pan_x, scale_pan_y), + (scale_pan_x-z, scale_pan_y+z), + ) + .assemble() + .polygon([ + (scale_base_x, scale_base_y + z/2), + (scale_base_x, self.margin), + (scale_base_x-z, self.margin), + (scale_base_x-z, scale_base_y-z), + + (-scale_base_x+z, scale_base_y-z), + (-scale_base_x+z, self.margin), + (-scale_base_x, self.margin), + (-scale_base_x, scale_base_y + z/2), + ], mode='a') + ) + + # Needle + needle_y_top = dy - self.margin + needle_y_mid = dy * 0.7 + needle_dx = scale_base_x * 2 + y_shoulder = needle_y_mid - z * 2 + needle = ( + Cq.Sketch() + .segment( + (0, needle_y_mid), + (z, y_shoulder), + ) + .segment( + (z, y_shoulder), + (z, scale_base_y), + ) + .segment( + (z, scale_base_y), + (-z, scale_base_y), + ) + .segment( + (-z, y_shoulder), + (-z, scale_base_y), + ) + .segment( + (-z, y_shoulder), + (0, needle_y_mid), + ) + .assemble() + ) + z2 = z * 2 + y1 = needle_y_mid + z2 + needle_head = ( + Cq.Sketch() + .segment( + (z, needle_y_mid), + (z, y1), + ) + .segment( + (-z, needle_y_mid), + (-z, y1), + ) + # Outer edge + .bezier([ + (0, needle_y_top), + (0, (needle_y_top + needle_y_mid)/2), + (needle_dx, (needle_y_top + needle_y_mid)/2), + (z, needle_y_mid), + ]) + .bezier([ + (0, needle_y_top), + (0, (needle_y_top + needle_y_mid)/2), + (-needle_dx, (needle_y_top + needle_y_mid)/2), + (-z, needle_y_mid), + ]) + # Inner edge + .bezier([ + (0, needle_y_top - z2), + (0, (needle_y_top + needle_y_mid)/2), + (needle_dx-z2*2, (needle_y_top + needle_y_mid)/2), + (z, y1), + ]) + .bezier([ + (0, needle_y_top - z2), + (0, (needle_y_top + needle_y_mid)/2), + (-needle_dx+z2*2, (needle_y_top + needle_y_mid)/2), + (-z, y1), + ]) + .assemble() + ) + + return ( + profile_base + .boolean(window.moved(window_p1), mode='s') + .boolean(window.moved(window_p1.flip_x()), mode='s') + .boolean(window.moved(window_p2), mode='s') + .boolean(window.moved(window_p2.flip_x()), mode='s') + .boolean(scale_pan.moved(loc_scale_pan), mode='s') + .boolean(scale_pan.moved(loc_scale_pan2), mode='s') + .boolean(scale_body, mode='s') + .boolean(needle, mode='s') + .boolean(needle_head, mode='s') + .clean() + ) + + @target(name="side-guard", kind=TargetKind.DXF) + def profile_side_guard(self) -> Cq.Sketch: + dx = self.facet_width_lower / 2 + dy = self.height + + # Main control points + p_mid = Cq.Location.from2d(0, 0.5 * dy) + p_mid_v = Cq.Location.from2d(10/57 * dx, 0) + p_top1 = Cq.Location.from2d(0.408 * dx, 5/24 * dy) + p_top1_v = Cq.Location.from2d(0.13 * dx, 0) + p_top2 = Cq.Location.from2d(0.737 * dx, 0.255 * dy) + p_top2_c1 = p_top2 * Cq.Location.from2d(-0.105 * dx, 0.033 * dy) + p_top2_c2 = p_top2 * Cq.Location.from2d(-0.053 * dx, -0.09 * dy) + p_top3 = Cq.Location.from2d(0.929 * dx, 0.145 * dy) + p_top3_v = Cq.Location.from2d(0.066 * dx, 0.033 * dy) + p_top4 = Cq.Location.from2d(0.85 * dx, 0.374 * dy) + p_top4_v = Cq.Location.from2d(-0.053 * dx, 0.008 * dy) + p_top5 = Cq.Location.from2d(0.54 * dx, 0.349 * dy) + p_top5_c1 = p_top5 * Cq.Location.from2d(0.103 * dx, 0.017 * dy) + p_top5_c2 = p_top5 * Cq.Location.from2d(0.158 * dx, 0.034 * dy) + p_base_c = Cq.Location.from2d(1.245 * dx, 0.55 * dy) + + y0 = self.slot_h0 / math.cos(self.slot_theta) + phi2 = self.slot_phi / 2 + p_base = Cq.Location.from2d(y0 * math.sin(phi2), -y0 + y0 * math.cos(phi2)) + + bezier_groups = [ + [ + p_base, + p_base_c, + p_top5_c2, + p_top5, + ], + [ + p_top5, + p_top5_c1, + p_top4 * p_top4_v, + p_top4, + ], + [ + p_top4, + p_top4 * p_top4_v.inverse.scale(4), + p_top3 * p_top3_v, + p_top3, + ], + [ + p_top3, + p_top3 * p_top3_v.inverse, + p_top2_c2, + p_top2, + ], + [ + p_top2, + p_top2_c1, + p_top1 * p_top1_v, + p_top1, + ], + [ + p_top1, + p_top1 * p_top1_v.inverse, + p_mid * p_mid_v, + p_mid, + ], + ] + sketch = ( + Cq.Sketch() + .arc( + p_base.to2d_pos(), + (0, 0), + p_base.flip_x().to2d_pos(), + ) + ) + for bezier_group in bezier_groups: + sketch = ( + sketch + .bezier([p.to2d_pos() for p in bezier_group]) + .bezier([p.flip_x().to2d_pos() for p in bezier_group]) + ) + return sketch.assemble() + + def side_guard_dovetail(self) -> Cq.Solid: + """ + Generates a dovetail coupling for the side guard + """ + dx = self.side_guard_thickness / 2 + wire = Cq.Wire.makePolygon([ + (dx * 0.5, 0), + (dx * 0.7, dx), + (-dx * 0.7, dx), + (-dx * 0.5, 0), + ], close=True) + return Cq.Solid.extrudeLinear( + wire, + [], + (0,0,dx + self.side_guard_dovetail_height), + ).moved((0, 0, -dx)) + + def side_guard_frontal_slot(self) -> Cq.Workplane: + angle = 360 / self.facets + inner_d = self.thickness / 2 - self.slot_thickness / 2 + outer_d = self.thickness / 2 + self.slot_thickness / 2 + outer = Cq.Solid.makeCone( + radius1=self.radius_lower_front + outer_d, + radius2=self.radius_upper_front + outer_d, + height=self.height, + angleDegrees=angle, + ) + inner = Cq.Solid.makeCone( + radius1=self.radius_lower_front + inner_d, + radius2=self.radius_upper_front + inner_d, + height=self.height, + angleDegrees=angle, + ) + shell = ( + outer.cut(inner) + .rotate((0,0,0), (0,0,1), -angle/2) + ) + # Generate the sector intersector + intersector = Cq.Solid.makeCylinder( + radius=self.radius_upper + self.side_guard_thickness, + height=self.front_wing_height, + angleDegrees=self.front_wing_angle, + ).moved(Cq.Location(0,0,self.front_wing_dh,0,0,-self.front_wing_angle/2)) + return shell * intersector + + def side_guard( + self, + attach_left: AttachPoint, + attach_right: AttachPoint, + ) -> Cq.Workplane: + """ + Constructs the side guard using a cone. Via Gauss's Theorema Egregium, + the surface of the cone can be deformed into a plane. + """ + angle_span = 360 / self.facets + outer = Cq.Solid.makeCone( + radius1=self.radius_lower + self.side_guard_thickness, + radius2=self.radius_upper + self.side_guard_thickness, + height=self.height, + angleDegrees=angle_span, + ) + inner = Cq.Solid.makeCone( + radius1=self.radius_lower, + radius2=self.radius_upper, + height=self.height, + angleDegrees=angle_span, + ) + shell = (outer - inner).rotate((0,0,0), (0,0,1), -angle_span/2) + dx = math.sin(math.radians(angle_span / 2)) * (self.radius_middle + self.side_guard_thickness) + profile = ( + Cq.Workplane('YZ') + .polyline([ + (0, self.height), + (-dx, self.height / 2), + (-dx, 0), + (dx, 0), + (dx, self.height / 2), + ]) + .close() + .extrude(self.radius_upper + self.side_guard_thickness) + .val() + ) + #channel = ( + # Cq.Solid.makeCylinder( + # radius=self.side_guard_channel_radius + 1.0, + # height=self.side_guard_channel_height, + # ) - Cq.Solid.makeCylinder( + # radius=self.side_guard_channel_radius, + # height=self.side_guard_channel_height, + # ) + #) + result = shell * profile# - channel + + # Create the downward slots + for sign in [-1, 1]: + slot_box = Cq.Solid.makeBox( + length=self.height, + width=self.slot_width, + height=self.slot_thickness, + ).moved( + Cq.Location(-self.slot_thickness,-self.slot_width/2, -self.slot_thickness/2) + ) + # keyhole for threads to stay in place + slot_cyl = Cq.Solid.makeCylinder( + radius=self.slot_thickness/2, + height=self.height, + pnt=(0,0,self.slot_thickness/2), + dir=(1,0,0), + ) + slot = slot_box + slot_cyl + slot = slot.moved( + Cq.Location.rot2d(sign * self.side_guard_slot_angle) * + Cq.Location(self.radius_lower + self.side_guard_thickness/2, 0, 0) * + Cq.Location(0,0,0,0,-180 + self.slot_tilt,0) + ) + result = result - slot + + radius_attach = self.radius_lower + self.side_guard_thickness / 2 + # tilt the dovetail by radius differential + angle_tilt = math.degrees(math.atan2(self.radius_middle - self.radius_lower, self.height / 2)) + dovetail = self.side_guard_dovetail() + loc_dovetail_left = Cq.Location.rot2d(angle_span / 2) * Cq.Location(radius_attach, 0, 0, 0, angle_tilt, 0) + loc_dovetail_right = Cq.Location.rot2d(-angle_span / 2) * Cq.Location(radius_attach, 0, 0, 0, angle_tilt, 0) + + angle_slot = 180 / self.facets - self.front_wing_angle / 2 + match attach_left: + case AttachPoint.DOVETAIL_IN: + loc_dovetail_left *= Cq.Location.rot2d(180) + result = result - dovetail.moved(loc_dovetail_left) + case AttachPoint.DOVETAIL_OUT: + result = result + dovetail.moved(loc_dovetail_left) + case AttachPoint.SLOT: + result = result - self.side_guard_frontal_slot().moved(Cq.Location.rot2d(angle_slot)) + case AttachPoint.NONE: + pass + match attach_right: + case AttachPoint.DOVETAIL_IN: + result = result - dovetail.moved(loc_dovetail_right) + case AttachPoint.DOVETAIL_OUT: + loc_dovetail_right *= Cq.Location.rot2d(180) + result = result + dovetail.moved(loc_dovetail_right) + case AttachPoint.SLOT: + result = result - self.side_guard_frontal_slot().moved(Cq.Location.rot2d(-angle_slot)) + case AttachPoint.NONE: + pass + # Remove parts below the horizontal + cut_h = self.radius_lower + result -= Cq.Solid.makeCylinder( + radius=self.radius_lower + self.side_guard_thickness, + height=cut_h).moved((0,0,-cut_h)) + return result + + @target(name="side_guard_1", angularTolerance=0.01) + def side_guard_1(self) -> Cq.Workplane: + return self.side_guard( + attach_left=AttachPoint.SLOT, + attach_right=AttachPoint.DOVETAIL_IN, + ) + @target(name="side_guard_2", angularTolerance=0.01) + def side_guard_2(self) -> Cq.Workplane: + return self.side_guard( + attach_left=AttachPoint.DOVETAIL_OUT, + attach_right=AttachPoint.DOVETAIL_IN, + ) + @target(name="side_guard_3", angularTolerance=0.01) + def side_guard_3(self) -> Cq.Workplane: + return self.side_guard( + attach_left=AttachPoint.DOVETAIL_OUT, + attach_right=AttachPoint.DOVETAIL_IN, + ) + @target(name="side_guard_4", angularTolerance=0.01) + def side_guard_4(self) -> Cq.Workplane: + return self.side_guard( + attach_left=AttachPoint.DOVETAIL_OUT, + attach_right=AttachPoint.SLOT, + ) + + def front_surrogate(self) -> Cq.Workplane: + """ + Create a surrogate cylindrical section structure for the front since we + cannot bend extrusions + """ + angle = 360 / 5 + outer = Cq.Solid.makeCone( + radius1=self.radius_lower_front + self.thickness, + radius2=self.radius_upper_front + self.thickness, + height=self.height, + angleDegrees=angle, + ) + inner = Cq.Solid.makeCone( + radius1=self.radius_lower_front, + radius2=self.radius_upper_front, + height=self.height, + angleDegrees=angle, + ) + shell = ( + outer.cut(inner) + .rotate((0,0,0), (0,0,1), -angle/2) + ) + dx = math.sin(math.radians(angle / 2)) * self.radius_middle_front + profile = ( + Cq.Workplane('YZ') + .polyline([ + (0, self.height), + (-dx, self.height / 2), + (-dx, 0), + (dx, 0), + (dx, self.height / 2), + ]) + .close() + .extrude(self.radius_upper_front + self.side_guard_thickness) + .val() + ) + return shell * profile + + def assembly(self) -> Cq.Assembly: + """ + New assembly using conformal mapping on the cone. + """ + side_guards = [ + self.side_guard_1(), + self.side_guard_2(), + self.side_guard_3(), + self.side_guard_4(), + ] + a = Cq.Assembly() + for i,side_guard in enumerate(side_guards): + angle = -(i+1) * 360 / self.facets + a = a.addS( + side_guard, + name=f"side-{i}", + material=self.material_side, + loc=Cq.Location(rz=angle) + ) + a.addS( + self.front_surrogate(), + name="front", + material=self.material, + ) + return a + + def old_assembly(self) -> Cq.Assembly: + front = ( + Cq.Workplane('XY') + .placeSketch(self.profile_front()) + .extrude(self.thickness) + ) + side = ( + Cq.Workplane('XY') + .placeSketch(self.profile_side()) + .extrude(self.thickness) + ) + side_guard = ( + Cq.Workplane('XY') + .placeSketch(self.profile_side_guard()) + .extrude(self.thickness) + ) + assembly = ( + Cq.Assembly() + .addS( + front, + name="front", + material=self.material, + role=Role.DECORATION, + ) + ) + for i, pos in enumerate([-2, -1, 1, 2]): + x = self.facet_width_upper * pos + assembly = ( + assembly + .addS( + side, + name=f"side{i}", + material=self.material, + role=Role.DECORATION, + loc=Cq.Location.from2d(x, 0), + ) + .addS( + side_guard, + name=f"guard{i}", + material=self.material, + role=Role.DECORATION, + loc=Cq.Location(x, 0, self.thickness), + ) + ) + return assembly diff --git a/nhf/touhou/shiki_eiki/epaulette-hi.dxf b/nhf/touhou/shiki_eiki/epaulette-hi.dxf new file mode 100644 index 0000000..e7a188b --- /dev/null +++ b/nhf/touhou/shiki_eiki/epaulette-hi.dxf @@ -0,0 +1,1464 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1014 + 9 +$HANDSEED + 5 +FFFF + 9 +$INSUNITS + 70 + 4 + 9 +$MEASUREMENT + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +VPORT + 5 +2E +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +210.0 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +341.0 + 41 +1.24 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYBLOCK + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYLAYER + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +100 +AcDbSymbolTable + 70 +5 + 0 +LAYER + 5 +50 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +51 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Outline + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +52 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Cut + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +53 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Ze + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +54 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Hi + 70 +0 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +STANDARD + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +ISO-25 + 70 + 0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874016 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 0 + 75 + 0 + 76 + 0 + 77 + 1 + 78 + 8 +170 + 0 +171 + 3 +172 + 1 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +270 + 2 +271 + 2 +272 + 2 +273 + 2 +274 + 3 +340 +11 +275 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 0 +284 + 8 +285 + 0 +286 + 0 +287 + 3 +288 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*MODEL_SPACE + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*PAPER_SPACE + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*MODEL_SPACE + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*MODEL_SPACE + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*PAPER_SPACE + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +SPLINE + 5 +100 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +99.867708 + 30 +0.0 + 10 +22.458851 + 20 +99.867699 + 30 +0.0 + 10 +0.132301 + 20 +77.541149 + 30 +0.0 + 10 +0.132292 + 20 +49.999951 + 30 +0.0 + 0 +SPLINE + 5 +101 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +0.132292 + 20 +49.999951 + 30 +0.0 + 10 +0.132302 + 20 +22.458753 + 30 +0.0 + 10 +22.458851 + 20 +0.132204 + 30 +0.0 + 10 +50.000049 + 20 +0.132194 + 30 +0.0 + 0 +SPLINE + 5 +102 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +0.132194 + 30 +0.0 + 10 +77.541247 + 20 +0.132204 + 30 +0.0 + 10 +99.867796 + 20 +22.458753 + 30 +0.0 + 10 +99.867806 + 20 +49.999951 + 30 +0.0 + 0 +SPLINE + 5 +103 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +99.867806 + 20 +49.999951 + 30 +0.0 + 10 +99.867796 + 20 +77.541149 + 30 +0.0 + 10 +77.541247 + 20 +99.867698 + 30 +0.0 + 10 +50.000049 + 20 +99.867708 + 30 +0.0 + 0 +SPLINE + 5 +104 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +94.880933 + 30 +0.0 + 10 +74.787127 + 20 +94.880924 + 30 +0.0 + 10 +94.881021 + 20 +74.787029 + 30 +0.0 + 10 +94.881030 + 20 +49.999951 + 30 +0.0 + 0 +SPLINE + 5 +105 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +94.881030 + 20 +49.999951 + 30 +0.0 + 10 +94.881021 + 20 +25.212873 + 30 +0.0 + 10 +74.787127 + 20 +5.118979 + 30 +0.0 + 10 +50.000049 + 20 +5.118970 + 30 +0.0 + 0 +SPLINE + 5 +106 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +5.118970 + 30 +0.0 + 10 +25.212971 + 20 +5.118979 + 30 +0.0 + 10 +5.119076 + 20 +25.212873 + 30 +0.0 + 10 +5.119067 + 20 +49.999951 + 30 +0.0 + 0 +SPLINE + 5 +107 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +5.119067 + 20 +49.999951 + 30 +0.0 + 10 +5.119076 + 20 +74.787029 + 30 +0.0 + 10 +25.212971 + 20 +94.880924 + 30 +0.0 + 10 +50.000049 + 20 +94.880933 + 30 +0.0 + 0 +LWPOLYLINE + 5 +108 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +50.000049 + 20 +99.867708 + 30 +0.0 + 0 +LWPOLYLINE + 5 +109 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +50.000049 + 20 +94.880933 + 30 +0.0 + 0 +LWPOLYLINE + 5 +10a +100 +AcDbEntity + 8 +Hi + 62 +5 +100 +AcDbPolyline + 90 +16 + 70 +1 + 10 +54.786837 + 20 +80.000187 + 30 +0.0 + 10 +54.786837 + 20 +20.000232 + 30 +0.0 + 10 +59.999955 + 20 +20.000232 + 30 +0.0 + 10 +59.999955 + 20 +28.943872 + 30 +0.0 + 10 +79.708830 + 20 +28.943872 + 30 +0.0 + 10 +79.708830 + 20 +37.224504 + 30 +0.0 + 10 +59.999955 + 20 +37.224504 + 30 +0.0 + 10 +59.999955 + 20 +45.859635 + 30 +0.0 + 10 +79.708830 + 20 +45.859635 + 30 +0.0 + 10 +79.708830 + 20 +54.140267 + 30 +0.0 + 10 +59.999955 + 20 +54.140267 + 30 +0.0 + 10 +59.999955 + 20 +62.775399 + 30 +0.0 + 10 +79.708830 + 20 +62.775399 + 30 +0.0 + 10 +79.708830 + 20 +71.056030 + 30 +0.0 + 10 +59.999955 + 20 +71.056030 + 30 +0.0 + 10 +59.999955 + 20 +80.000187 + 30 +0.0 + 0 +LWPOLYLINE + 5 +10b +100 +AcDbEntity + 8 +Hi + 62 +5 +100 +AcDbPolyline + 90 +16 + 70 +1 + 10 +45.245359 + 20 +20.000023 + 30 +0.0 + 10 +45.245359 + 20 +79.999978 + 30 +0.0 + 10 +40.032241 + 20 +79.999978 + 30 +0.0 + 10 +40.032241 + 20 +71.056338 + 30 +0.0 + 10 +20.323366 + 20 +71.056338 + 30 +0.0 + 10 +20.323366 + 20 +62.775706 + 30 +0.0 + 10 +40.032241 + 20 +62.775706 + 30 +0.0 + 10 +40.032241 + 20 +54.140575 + 30 +0.0 + 10 +20.323366 + 20 +54.140575 + 30 +0.0 + 10 +20.323366 + 20 +45.859943 + 30 +0.0 + 10 +40.032241 + 20 +45.859943 + 30 +0.0 + 10 +40.032241 + 20 +37.224811 + 30 +0.0 + 10 +20.323366 + 20 +37.224811 + 30 +0.0 + 10 +20.323366 + 20 +28.944180 + 30 +0.0 + 10 +40.032241 + 20 +28.944180 + 30 +0.0 + 10 +40.032241 + 20 +20.000023 + 30 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary + 3 +ACAD_GROUP +350 +D + 3 +ACAD_MLINESTYLE +350 +17 + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +1A +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +17 +330 +C +100 +AcDbDictionary + 3 +STANDARD +350 +18 + 0 +DICTIONARY + 5 +19 +330 +C +100 +AcDbDictionary + 0 +ENDSEC + 0 +EOF diff --git a/nhf/touhou/shiki_eiki/epaulette-ze.dxf b/nhf/touhou/shiki_eiki/epaulette-ze.dxf new file mode 100644 index 0000000..cc43962 --- /dev/null +++ b/nhf/touhou/shiki_eiki/epaulette-ze.dxf @@ -0,0 +1,2426 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1014 + 9 +$HANDSEED + 5 +FFFF + 9 +$INSUNITS + 70 + 4 + 9 +$MEASUREMENT + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +VPORT + 5 +2E +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +210.0 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +341.0 + 41 +1.24 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYBLOCK + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYLAYER + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +100 +AcDbSymbolTable + 70 +5 + 0 +LAYER + 5 +50 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +51 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Outline + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +52 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Cut + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +53 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Ze + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +54 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Hi + 70 +0 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +STANDARD + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +ISO-25 + 70 + 0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874016 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 0 + 75 + 0 + 76 + 0 + 77 + 1 + 78 + 8 +170 + 0 +171 + 3 +172 + 1 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +270 + 2 +271 + 2 +272 + 2 +273 + 2 +274 + 3 +340 +11 +275 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 0 +284 + 8 +285 + 0 +286 + 0 +287 + 3 +288 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*MODEL_SPACE + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*PAPER_SPACE + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*MODEL_SPACE + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*MODEL_SPACE + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*PAPER_SPACE + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +SPLINE + 5 +100 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +99.867708 + 30 +0.0 + 10 +22.458851 + 20 +99.867699 + 30 +0.0 + 10 +0.132301 + 20 +77.541149 + 30 +0.0 + 10 +0.132292 + 20 +49.999951 + 30 +0.0 + 0 +SPLINE + 5 +101 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +0.132292 + 20 +49.999951 + 30 +0.0 + 10 +0.132302 + 20 +22.458753 + 30 +0.0 + 10 +22.458851 + 20 +0.132204 + 30 +0.0 + 10 +50.000049 + 20 +0.132194 + 30 +0.0 + 0 +SPLINE + 5 +102 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +0.132194 + 30 +0.0 + 10 +77.541247 + 20 +0.132204 + 30 +0.0 + 10 +99.867796 + 20 +22.458753 + 30 +0.0 + 10 +99.867806 + 20 +49.999951 + 30 +0.0 + 0 +SPLINE + 5 +103 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +99.867806 + 20 +49.999951 + 30 +0.0 + 10 +99.867796 + 20 +77.541149 + 30 +0.0 + 10 +77.541247 + 20 +99.867698 + 30 +0.0 + 10 +50.000049 + 20 +99.867708 + 30 +0.0 + 0 +SPLINE + 5 +104 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +94.880933 + 30 +0.0 + 10 +74.787127 + 20 +94.880924 + 30 +0.0 + 10 +94.881021 + 20 +74.787029 + 30 +0.0 + 10 +94.881030 + 20 +49.999951 + 30 +0.0 + 0 +SPLINE + 5 +105 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +94.881030 + 20 +49.999951 + 30 +0.0 + 10 +94.881021 + 20 +25.212873 + 30 +0.0 + 10 +74.787127 + 20 +5.118979 + 30 +0.0 + 10 +50.000049 + 20 +5.118970 + 30 +0.0 + 0 +SPLINE + 5 +106 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +5.118970 + 30 +0.0 + 10 +25.212971 + 20 +5.118979 + 30 +0.0 + 10 +5.119076 + 20 +25.212873 + 30 +0.0 + 10 +5.119067 + 20 +49.999951 + 30 +0.0 + 0 +SPLINE + 5 +107 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +5.119067 + 20 +49.999951 + 30 +0.0 + 10 +5.119076 + 20 +74.787029 + 30 +0.0 + 10 +25.212971 + 20 +94.880924 + 30 +0.0 + 10 +50.000049 + 20 +94.880933 + 30 +0.0 + 0 +LWPOLYLINE + 5 +108 +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +50.000049 + 20 +99.867708 + 30 +0.0 + 0 +SPLINE + 5 +109 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +87.354777 + 30 +0.0 + 10 +31.310151 + 20 +87.344028 + 30 +0.0 + 10 +15.504624 + 20 +73.521864 + 30 +0.0 + 10 +13.002824 + 20 +55.000163 + 30 +0.0 + 0 +LWPOLYLINE + 5 +10a +100 +AcDbEntity + 8 +Outline + 62 +7 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +50.000049 + 20 +94.880933 + 30 +0.0 + 0 +SPLINE + 5 +10b +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +86.997274 + 20 +55.000163 + 30 +0.0 + 10 +84.495474 + 20 +73.521864 + 30 +0.0 + 10 +68.689947 + 20 +87.344028 + 30 +0.0 + 10 +50.000049 + 20 +87.354777 + 30 +0.0 + 0 +LWPOLYLINE + 5 +10c +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +6 + 70 +0 + 10 +13.002824 + 20 +55.000163 + 30 +0.0 + 10 +15.549438 + 20 +55.000163 + 30 +0.0 + 10 +18.019055 + 20 +55.000163 + 30 +0.0 + 10 +81.984143 + 20 +55.000163 + 30 +0.0 + 10 +83.422298 + 20 +55.000163 + 30 +0.0 + 10 +86.997274 + 20 +55.000163 + 30 +0.0 + 0 +SPLINE + 5 +10d +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +82.374202 + 30 +0.0 + 10 +58.239794 + 20 +82.362203 + 30 +0.0 + 10 +66.164913 + 20 +79.208825 + 30 +0.0 + 10 +72.160453 + 20 +73.556653 + 30 +0.0 + 0 +LWPOLYLINE + 5 +10e +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +50.000049 + 20 +87.354777 + 30 +0.0 + 0 +SPLINE + 5 +10f +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +27.842745 + 20 +73.556653 + 30 +0.0 + 10 +33.837505 + 20 +79.208092 + 30 +0.0 + 10 +41.761375 + 20 +82.361414 + 30 +0.0 + 10 +50.000049 + 20 +82.374202 + 30 +0.0 + 0 +LWPOLYLINE + 5 +110 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +72.160453 + 20 +73.556653 + 30 +0.0 + 10 +27.842745 + 20 +73.556653 + 30 +0.0 + 0 +LWPOLYLINE + 5 +111 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +50.000049 + 20 +82.374202 + 30 +0.0 + 0 +SPLINE + 5 +112 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +76.278031 + 20 +68.798287 + 30 +0.0 + 10 +78.258337 + 20 +66.042028 + 30 +0.0 + 10 +79.792920 + 20 +62.991520 + 30 +0.0 + 10 +80.825557 + 20 +59.758529 + 30 +0.0 + 0 +LWPOLYLINE + 5 +113 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +23.725167 + 20 +68.798287 + 30 +0.0 + 10 +76.278031 + 20 +68.798287 + 30 +0.0 + 0 +SPLINE + 5 +114 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +19.177641 + 20 +59.758529 + 30 +0.0 + 10 +20.210278 + 20 +62.991520 + 30 +0.0 + 10 +21.744861 + 20 +66.042028 + 30 +0.0 + 10 +23.725167 + 20 +68.798287 + 30 +0.0 + 0 +LWPOLYLINE + 5 +115 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +80.825557 + 20 +59.758529 + 30 +0.0 + 10 +19.177641 + 20 +59.758529 + 30 +0.0 + 0 +LWPOLYLINE + 5 +116 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +23.725167 + 20 +68.798287 + 30 +0.0 + 0 +SPLINE + 5 +117 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +21.704102 + 20 +26.193136 + 30 +0.0 + 10 +28.753604 + 20 +17.878949 + 30 +0.0 + 10 +39.099541 + 20 +13.081227 + 30 +0.0 + 10 +50.000049 + 20 +13.071456 + 30 +0.0 + 0 +SPLINE + 5 +118 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +13.071456 + 30 +0.0 + 10 +58.459978 + 20 +13.097594 + 30 +0.0 + 10 +66.657490 + 20 +16.011070 + 30 +0.0 + 10 +73.236357 + 20 +21.329867 + 30 +0.0 + 0 +LWPOLYLINE + 5 +119 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +30.468424 + 20 +34.957458 + 30 +0.0 + 10 +21.704102 + 20 +26.193136 + 30 +0.0 + 0 +SPLINE + 5 +11a +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +70.147139 + 20 +24.747229 + 30 +0.0 + 10 +64.432180 + 20 +20.158004 + 30 +0.0 + 10 +57.329500 + 20 +17.642998 + 30 +0.0 + 10 +50.000049 + 20 +17.613298 + 30 +0.0 + 0 +SPLINE + 5 +11b +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +50.000049 + 20 +17.613298 + 30 +0.0 + 10 +42.031806 + 20 +17.643491 + 30 +0.0 + 10 +34.403936 + 20 +20.612407 + 30 +0.0 + 10 +28.529008 + 20 +25.870158 + 30 +0.0 + 0 +LWPOLYLINE + 5 +11c +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +73.236357 + 20 +21.329867 + 30 +0.0 + 10 +70.147139 + 20 +24.747229 + 30 +0.0 + 0 +LWPOLYLINE + 5 +11d +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +3 + 70 +0 + 10 +28.529008 + 20 +25.870158 + 30 +0.0 + 10 +34.042367 + 20 +31.383516 + 30 +0.0 + 10 +30.468424 + 20 +34.957458 + 30 +0.0 + 0 +SPLINE + 5 +11e +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +13.231234 + 20 +45.000258 + 30 +0.0 + 10 +13.474885 + 20 +43.310754 + 30 +0.0 + 10 +13.834717 + 20 +41.640056 + 30 +0.0 + 10 +14.308171 + 20 +40.000047 + 30 +0.0 + 0 +LWPOLYLINE + 5 +11f +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +3 + 70 +0 + 10 +53.049475 + 20 +45.027128 + 30 +0.0 + 10 +53.049475 + 20 +45.000258 + 30 +0.0 + 10 +13.231234 + 20 +45.000258 + 30 +0.0 + 0 +SPLINE + 5 +120 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +85.691927 + 20 +40.000045 + 30 +0.0 + 10 +86.165381 + 20 +41.640054 + 30 +0.0 + 10 +86.525213 + 20 +43.310752 + 30 +0.0 + 10 +86.768864 + 20 +45.000256 + 30 +0.0 + 0 +LWPOLYLINE + 5 +121 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +10 + 70 +0 + 10 +14.308171 + 20 +40.000047 + 30 +0.0 + 10 +53.049475 + 20 +40.000047 + 30 +0.0 + 10 +53.049475 + 20 +24.633024 + 30 +0.0 + 10 +58.103430 + 20 +24.633024 + 30 +0.0 + 10 +58.103430 + 20 +32.302840 + 30 +0.0 + 10 +73.684908 + 20 +32.302840 + 30 +0.0 + 10 +73.684908 + 20 +37.356795 + 30 +0.0 + 10 +58.103430 + 20 +37.356795 + 30 +0.0 + 10 +58.103430 + 20 +40.000045 + 30 +0.0 + 10 +85.691927 + 20 +40.000045 + 30 +0.0 + 0 +LWPOLYLINE + 5 +122 +100 +AcDbEntity + 8 +Ze + 62 +3 +100 +AcDbPolyline + 90 +4 + 70 +0 + 10 +86.768864 + 20 +45.000256 + 30 +0.0 + 10 +58.103430 + 20 +45.000256 + 30 +0.0 + 10 +58.103430 + 20 +45.027126 + 30 +0.0 + 10 +53.049475 + 20 +45.027128 + 30 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary + 3 +ACAD_GROUP +350 +D + 3 +ACAD_MLINESTYLE +350 +17 + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +1A +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +17 +330 +C +100 +AcDbDictionary + 3 +STANDARD +350 +18 + 0 +DICTIONARY + 5 +19 +330 +C +100 +AcDbDictionary + 0 +ENDSEC + 0 +EOF diff --git a/nhf/touhou/shiki_eiki/epaulette.py b/nhf/touhou/shiki_eiki/epaulette.py new file mode 100644 index 0000000..6150c64 --- /dev/null +++ b/nhf/touhou/shiki_eiki/epaulette.py @@ -0,0 +1,36 @@ +import math +from dataclasses import dataclass, field +from pathlib import Path +import cadquery as Cq +from nhf import Material, Role +from nhf.build import Model, target, assembly +import nhf.utils + +@dataclass +class Epaulette(Model): + + side: str + diam: float = 100.0 + thickness_brass: float = 0.4 # 26 Gauge + thickness_fabric: float = 0.3 + material: Material = Material.METAL_BRASS + + def __post_init__(self): + super().__init__(name=f"epaulette-{self.side}") + + def surface(self) -> Cq.Solid: + path = Path(__file__).resolve().parent / f"epaulette-{self.side}.dxf" + return ( + Cq.importers.importDXF(path).wires().toPending().extrude(self.thickness_brass) + ) + def assembly(self) -> Cq.Assembly: + assembly = ( + Cq.Assembly() + .addS( + self.surface(), + name="surface", + material=self.material, + role=Role.DECORATION, + ) + ) + return assembly diff --git a/nhf/touhou/shiki_eiki/rod.py b/nhf/touhou/shiki_eiki/rod.py new file mode 100644 index 0000000..cbf1f61 --- /dev/null +++ b/nhf/touhou/shiki_eiki/rod.py @@ -0,0 +1,587 @@ +import math +from dataclasses import dataclass, field +from typing import Tuple +import cadquery as Cq +from nhf import Material, Role +from nhf.build import Model, target, assembly, TargetKind +import nhf.utils + +@dataclass +class Rod(Model): + + width: float = 120.0 + length: float = 550.0 + length_tip: float = 100.0 + width_tail: float = 60.0 + margin: float = 10.0 + + thickness_top: float = 25.4 / 8 + # The side which has mounted hinges must be thicker + thickness_side: float = 25.4 / 4 + + height_internal: float = 30.0 + + material_shell: Material = Material.WOOD_BIRCH + + # Considering the glyph on the top ... + + # counted from middle to the bottom + fac_bar_top: float = 0.1 + # counted from bottom to top + fac_window_tsumi_bot: float = 0.63 + fac_window_tsumi_top: float = 0.88 + + fac_window_footer_bot: float = 0.36 + fac_window_footer_top: float = 0.6 + + # Considering the side ... + hinge_plate_pos: list[float] = field(default_factory=lambda: [0.1, 0.9]) + hinge_plate_length: float = 30.0 + hinge_hole_diam: float = 2.5 + # Hole distance to axis + hinge_hole_axis_dist: float = 12.5 / 2 + # Distance between holes + hinge_hole_sep: float = 15.89 + + # Consider the reference objects + ref_object_width: float = 50.0 + ref_object_length: float = 50.0 + + def __post_init__(self): + super().__init__(name="rod") + self.loc_core = Cq.Location.from2d(self.length - self.length_tip, 0) + assert self.length_tip * 2 < self.length + #assert self.fac_bar_top + self.fac_window_tsumi_top < 1 + assert self.fac_window_tsumi_bot < self.fac_window_tsumi_top + + @property + def length_tail(self): + return self.length - self.length_tip + + @property + def _reduced_tip_x(self): + return self.length_tip - self.margin + @property + def _reduced_y(self): + return self.width / 2 - self.margin + @property + def _reduced_tail_y(self): + return self.width_tail / 2 - self.margin + + def profile_points(self) -> list[Tuple[str, Tuple[float, float]]]: + """ + Points in polygon line order, labaled + """ + return [ + ("tip", (self.length, 0)), + ("mid_r", (self.length - self.length_tip, self.width/2)), + ("bot_r", (0, self.width_tail / 2)), + ("bot_l", (0, -self.width_tail / 2)), + ("mid_l", (self.length - self.length_tip, -self.width/2)), + ] + + def _window_tip(self) -> Cq.Sketch: + dxh = self._reduced_tip_x + dy = self._reduced_y + return ( + Cq.Sketch() + .segment( + (dxh, 0), + (dxh / 2, dy / 2), + ) + .bezier([ + (dxh / 2, dy / 2), + (dxh * 0.6, dy * 0.4), + (dxh * 0.6, -dy * 0.4), + (dxh / 2, -dy / 2), + ]) + .segment( + (dxh, 0), + ) + .assemble() + .moved(self.loc_core.to2d_pos()) + ) + def _window_eye(self, refl: bool = False) -> Cq.Sketch: + + sign = -1 if refl else 1 + dxh = self._reduced_tip_x + xm = dxh * 0.45 + dy = sign * self._reduced_y + fac = 0.05 + + p1 = Cq.Location.from2d(xm, sign * self.margin / 2) + p2 = Cq.Location.from2d(dxh * 0.1, sign * self.margin / 2) + p3 = Cq.Location.from2d(dxh * 0.15, dy * 0.55) + p4 = Cq.Location.from2d(dxh * 0.4, dy * 0.45) + d4 = Cq.Location.from2d(dxh * fac, -dy * fac) + + return ( + Cq.Sketch() + .segment( + p1.to2d_pos(), + p2.to2d_pos(), + ) + .bezier([ + p2.to2d_pos(), + (p2 * Cq.Location.from2d(0, dy * fac)).to2d_pos(), + (p3 * Cq.Location.from2d(-dxh * fac, -dy * fac)).to2d_pos(), + p3.to2d_pos(), + ]) + .bezier([ + p3.to2d_pos(), + (p3 * Cq.Location.from2d(0, dy * fac)).to2d_pos(), + (p4 * d4.inverse).to2d_pos(), + p4.to2d_pos(), + ]) + .bezier([ + p4.to2d_pos(), + (p4 * d4).to2d_pos(), + (p1 * Cq.Location.from2d(0, dy * fac)).to2d_pos(), + p1.to2d_pos(), + ]) + .assemble() + .moved(self.loc_core.to2d_pos()) + ) + + def _window_bar(self) -> Cq.Sketch(): + dxh = self._reduced_tip_x + dy = self._reduced_y + dyt = self._reduced_tail_y + dxt = self.length_tail + + ext_fac = self.fac_bar_top + + p_corner = Cq.Location.from2d(0, dy) + p_top = Cq.Location.from2d(0.3 * dxh, 0.7 * dy) + p_bot = Cq.Location.from2d(-ext_fac * dxt, dy + ext_fac * (dyt - dy)) + p_top_int = p_corner * Cq.Location.from2d(.05 * dxh, -.2 * dy) + p_top_ctrl = Cq.Location.from2d(0, .3 * dy) + p_bot_int = p_corner * Cq.Location.from2d(-.15 * dxh, -.2 * dy) + p_bot_ctrl = Cq.Location.from2d(-.25 * dxh, .3 * dy) + + return ( + Cq.Sketch() + .segment( + p_corner.to2d_pos(), + p_top.to2d_pos(), + ) + .segment(p_top_int.to2d_pos()) + .bezier([ + p_top_int.to2d_pos(), + p_top_ctrl.to2d_pos(), + p_top_ctrl.flip_y().to2d_pos(), + p_top_int.flip_y().to2d_pos(), + ]) + .segment(p_top.flip_y().to2d_pos()) + .segment(p_corner.flip_y().to2d_pos()) + .segment(p_bot.flip_y().to2d_pos()) + .segment(p_bot_int.flip_y().to2d_pos()) + .bezier([ + p_bot_int.flip_y().to2d_pos(), + p_bot_ctrl.flip_y().to2d_pos(), + p_bot_ctrl.to2d_pos(), + p_bot_int.to2d_pos(), + ]) + .segment(p_bot.to2d_pos()) + .segment(p_corner.to2d_pos()) + .assemble() + .moved(self.loc_core.to2d_pos()) + ) + + def _window_tsumi(self) -> Cq.Sketch: + dx = (self.fac_window_tsumi_top - self.fac_window_tsumi_bot) * self.length_tail + dy = 2 * self._reduced_y * 0.8 + loc = Cq.Location(self.fac_window_tsumi_bot * self.length_tail, 0) + + # Construction of the top part of the kanji + + dx_top = dx * 0.3 + x_top = dx - dx_top / 2 + dy_top = dy + dy_eye = dy * 0.2 + dy_border = (dy_top - 3 * dy_eye) / 4 + # The skip must follow 3 * eye + 4 * border = dy_top + y_skip = dy_eye + dy_border + + # Construction of the bottom part + x_bot = dx * 0.65 + y3 = dy * 0.4 + y2 = dy * 0.2 + y1 = dy * 0.1 + # x/y-centers of the legs + x_leg0 = x_bot / 14 + dx_leg = x_bot / 7 + y_leg = (y3 + y1) / 2 + + return ( + Cq.Sketch() + .push([(x_top, 0)]) + .rect(dx_top, dy_top) + .push([ + (x_top, -y_skip), + (x_top, 0), + (x_top, y_skip), + ]) + .rect(dx_top / 3, dy_eye, mode='s') + + # Construct the two sides + .push([ + (x_bot / 2, (y2 + y1) / 2), + (x_bot / 2, -(y2 + y1) / 2), + ]) + .rect(x_bot, y2 - y1, mode='a') + .push([ + (x_leg0 + dx_leg, y_leg), + (x_leg0 + 3 * dx_leg, y_leg), + (x_leg0 + 5 * dx_leg, y_leg), + (x_leg0 + dx_leg, -y_leg), + (x_leg0 + 3 * dx_leg, -y_leg), + (x_leg0 + 5 * dx_leg, -y_leg), + ]) + .rect(dx_leg, y3 - y1, mode='a') + + .moved(loc) + ) + + def _window_footer(self) -> Cq.Sketch: + x_bot = self.fac_window_footer_bot * self.length_tail + dx = (self.fac_window_footer_top - self.fac_window_footer_bot) * self.length_tail + loc = Cq.Location(x_bot, 0) + + dy = self._reduced_y * 0.8 + + # eyes + eye_y2 = dy * .5 + eye_y1 = dy * .2 + eye_width = eye_y2 - eye_y1 + eye_x = dx - eye_width / 2 + + # bar polygon + bar_x0 = dx * 0.65 + bar_dx = dx * 0.1 + bar_x1 = bar_x0 + bar_dx + bar_x2 = bar_x0 + bar_dx * 2 + bar_x3 = bar_x0 + bar_dx * 3 + bar_y1 = dy * .75 + assert bar_y1 > eye_y2 + bar_y2 = dy * .9 + assert bar_y1 < bar_y2 + + # Construction of the cross + cross_dx = dx * 0.7 / math.sqrt(2) + cross_dy = dy * 0.2 + + cross = ( + Cq.Sketch() + .rect(cross_dx, cross_dy) + .rect(cross_dy, cross_dx, mode='a') + .moved(Cq.Location.from2d(dx * 0.5, 0, 45)) + ) + return ( + Cq.Sketch() + # eyes + .push([ + (eye_x, (eye_y1 + eye_y2)/2), + (eye_x, -(eye_y1 + eye_y2)/2), + ]) + .rect(eye_width, eye_width, mode='a') + # middle bar + .push([(0,0)]) + .polygon([ + (bar_x1, bar_y1), + (bar_x0, bar_y1), + (bar_x0, bar_y2), + (bar_x3, bar_y2), + (bar_x3, bar_y1), + (bar_x2, bar_y1), + + (bar_x2, -bar_y1), + (bar_x3, -bar_y1), + (bar_x3, -bar_y2), + (bar_x0, -bar_y2), + (bar_x0, -bar_y1), + (bar_x1, -bar_y1), + ], mode='a') + # cross + .boolean(cross, mode='a') + + #.push([(0,0)]) + #.rect(10, 10) + + .moved(loc) + ) + + @target(name="bottom", kind=TargetKind.DXF) + def profile_bottom(self) -> Cq.Sketch: + return ( + Cq.Sketch() + .polygon([p for _, p in self.profile_points()]) + ) + + @target(name="top", kind=TargetKind.DXF) + def profile_top(self) -> Cq.Sketch: + return ( + self.profile_bottom() + .boolean(self._window_tip(), mode='s') + .boolean(self._window_eye(True), mode='s') + .boolean(self._window_eye(False), mode='s') + .boolean(self._window_bar(), mode='s') + .boolean(self._window_tsumi(), mode='s') + .boolean(self._window_footer(), mode='s') + ) + + def surface_top(self) -> Cq.Workplane: + return ( + Cq.Workplane('XY') + .placeSketch(self.profile_top()) + .extrude(self.thickness_top) + ) + + def surface_bottom(self) -> Cq.Workplane: + surface = ( + Cq.Workplane('XY') + .placeSketch(self.profile_bottom()) + .extrude(self.thickness_top) + ) + plane = surface.faces(">Z").workplane() + + for (name, p) in self.profile_points(): + plane.moveTo(*p).tagPlane(name) + + return surface + + # Properties of the side surfaces + + @property + def length_edge_tip(self): + return math.sqrt(self.length_tip ** 2 + (self.width / 2) ** 2) + @property + def length_edge_tail(self): + dw = (self.width - self.width_tail) / 2 + return math.sqrt(self.length_tail ** 2 + dw ** 2) + @property + def tip_incident_angle(self): + """ + Angle (measuring from vertical) at which the tip edge pieces must be + sanded in order to make them not collide into each other. + """ + return math.atan2(self.length_tip, self.width / 2) + @property + def shoulder_incident_angle(self) -> float: + angle_tip = math.atan2(self.width / 2, self.length_tip) + angle_tail = math.atan2((self.width - self.width_tail) / 2, self.length_tail) + return (angle_tip + angle_tail) / 2 + + @target(name="ref-tip") + def ref_tip(self) -> Cq.Workplane: + angle = self.tip_incident_angle + w = self.ref_object_width + drop = math.sin(angle) * w + profile = ( + Cq.Sketch() + .polygon([ + (0, 0), + (0, w), + (w, w), + (w - drop, 0), + ]) + ) + return ( + Cq.Workplane() + .placeSketch(profile) + .extrude(self.ref_object_length) + ) + @target(name="ref-shoulder") + def ref_shoulder(self) -> Cq.Workplane: + angle = self.shoulder_incident_angle + w = self.ref_object_width + drop = math.sin(angle) * w + profile = ( + Cq.Sketch() + .polygon([ + (0, 0), + (0, w), + (w, w), + (w - drop, 0), + ]) + ) + return ( + Cq.Workplane() + .placeSketch(profile) + .extrude(self.ref_object_length) + ) + + @target(name="side-tip-2x", kind=TargetKind.DXF) + def profile_side_tip(self): + l = self.length_edge_tip + w = self.height_internal + return ( + Cq.Sketch() + .push([(l/2, w/2)]) + .rect(l, w) + ) + @target(name="side-tail", kind=TargetKind.DXF) + def profile_side_tail(self): + """ + Plain side 2 with no hinge + """ + l = self.length_edge_tail + w = self.height_internal + return ( + Cq.Sketch() + .push([(l/2, w/2)]) + .rect(l, w) + ) + @target(name="side-hinge-plate", kind=TargetKind.DXF) + def profile_side_hinge_plate(self): + l = self.hinge_plate_length + w = self.height_internal / 2 + return ( + Cq.Sketch() + .push([(0, w/2)]) + .rect(l, w) + .push([ + (self.hinge_hole_sep / 2, self.hinge_hole_axis_dist), + (-self.hinge_hole_sep / 2, self.hinge_hole_axis_dist), + ]) + .circle(self.hinge_hole_diam / 2, mode='s') + ) + @target(name="side-tail-hinged", kind=TargetKind.DXF) + def profile_side_tail_hinged(self): + """ + Plain side 2 with no hinge + """ + l = self.length_edge_tail + w = self.height_internal + + # Holes for hinge + plate_pos = [ + (t * l, w * 3/4) for t in self.hinge_plate_pos + ] + hole_pos = [ + (self.hinge_hole_sep / 2, self.hinge_hole_axis_dist), + (-self.hinge_hole_sep / 2, self.hinge_hole_axis_dist), + ] + return ( + self.profile_side_tail() + .push(plate_pos) + .rect(self.hinge_plate_length, w/2, mode='s') + .push([ + (hx + px, w/2 - hy) + for hx, hy in hole_pos + for px, _ in plate_pos + ]) + .circle(self.hinge_hole_diam / 2, mode='s') + ) + @target(name="side-bot", kind=TargetKind.DXF) + def profile_side_bot(self): + l = self.width_tail - self.thickness_side * 2 + w = self.height_internal + return ( + Cq.Sketch() + .rect(l, w) + ) + + def surface_side_tip(self): + result = ( + Cq.Workplane('XY') + .placeSketch(self.profile_side_tip()) + .extrude(self.thickness_side) + ) + plane = result.faces(">Y").workplane() + plane.moveTo(0, 0).tagPlane("bot") + plane.moveTo(-self.length_edge_tip, 0).tagPlane("top") + return result + def surface_side_tail(self): + result = ( + Cq.Workplane('XY') + .placeSketch(self.profile_side_tail()) + .extrude(self.thickness_side) + ) + plane = result.faces(">Y").workplane() + plane.moveTo(0, 0).tagPlane("bot") + plane.moveTo(-self.length_edge_tail, 0).tagPlane("top") + return result + def surface_side_tail_hinged(self): + result = ( + Cq.Workplane('XY') + .placeSketch(self.profile_side_tail_hinged()) + .extrude(self.thickness_side) + ) + plane = result.faces(">Y").workplane() + plane.moveTo(0, 0).tagPlane("bot") + plane.moveTo(-self.length_edge_tail, 0).tagPlane("top") + return result + def surface_side_bot(self): + result = ( + Cq.Workplane('XY') + .placeSketch(self.profile_side_bot()) + .extrude(self.thickness_side) + ) + plane = result.faces(">Y").workplane() + plane.moveTo(self.width_tail / 2, 0).tagPlane("bot") + plane.moveTo(-self.width_tail / 2, 0).tagPlane("top") + return result + + @assembly() + def assembly(self) -> Cq.Assembly: + a = ( + Cq.Assembly() + .addS( + self.surface_top(), + name="top", + material=self.material_shell, + role=Role.STRUCTURE | Role.DECORATION + ) + .constrain("top", "Fixed") + .addS( + self.surface_bottom(), + name="bottom", + material=self.material_shell, + role=Role.STRUCTURE, + loc=Cq.Location(0, 0, -self.thickness_top - self.height_internal) + ) + .constrain("bottom", "Fixed") + .addS( + self.surface_side_tip(), + name="side_tip_l", + material=self.material_shell, + role=Role.STRUCTURE, + ) + .constrain("bottom?tip", "side_tip_l?top", "Plane") + .constrain("bottom?mid_l", "side_tip_l?bot", "Plane") + .addS( + self.surface_side_tip(), + name="side_tip_r", + material=self.material_shell, + role=Role.STRUCTURE, + ) + .constrain("bottom?tip", "side_tip_r?bot", "Plane") + .constrain("bottom?mid_r", "side_tip_r?top", "Plane") + .addS( + self.surface_side_tail(), + name="side_tail_l", + material=self.material_shell, + role=Role.STRUCTURE, + ) + .constrain("bottom?mid_l", "side_tail_l?top", "Plane") + .constrain("bottom?bot_l", "side_tail_l?bot", "Plane") + .addS( + self.surface_side_tail_hinged(), + name="side_tail_r", + material=self.material_shell, + role=Role.STRUCTURE, + ) + .constrain("bottom?mid_r", "side_tail_r?bot", "Plane") + .constrain("bottom?bot_r", "side_tail_r?top", "Plane") + .addS( + self.surface_side_bot(), + name="side_bot", + material=self.material_shell, + role=Role.STRUCTURE, + ) + .constrain("bottom?bot_l", "side_bot?top", "Plane") + .constrain("bottom?bot_r", "side_bot?bot", "Plane") + .solve() + ) + return a diff --git a/nhf/touhou/shiki_eiki/zehi.svg b/nhf/touhou/shiki_eiki/zehi.svg new file mode 100644 index 0000000..cfa56e0 --- /dev/null +++ b/nhf/touhou/shiki_eiki/zehi.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nhf/utils.py b/nhf/utils.py index 13469fd..0c83db7 100644 --- a/nhf/utils.py +++ b/nhf/utils.py @@ -1,13 +1,11 @@ """ Utility functions for cadquery objects """ -import functools -import math -from typing import Optional +import functools, math +from typing import Optional, Union, Tuple, cast import cadquery as Cq from cadquery.occ_impl.solver import ConstraintSpec from nhf import Role -from typing import Union, Tuple, cast from nhf.materials import KEY_ITEM, KEY_MATERIAL # Bug fixes @@ -55,6 +53,11 @@ def is2d(self: Cq.Location) -> bool: return z == 0 and rx == 0 and ry == 0 Cq.Location.is2d = is2d +def scale(self: Cq.Location, fac: float) -> bool: + (x, y, z), (rx, ry, rz) = self.toTuple() + return Cq.Location(x*fac, y*fac, z*fac, rx, ry, rz) +Cq.Location.scale = scale + def to2d(self: Cq.Location) -> Tuple[Tuple[float, float], float]: """ Returns position and angle @@ -93,17 +96,24 @@ Cq.Location.with_angle_2d = with_angle_2d def flip_x(self: Cq.Location) -> Cq.Location: (x, y), a = self.to2d() - return Cq.Location.from2d(-x, y, 90 - a) + return Cq.Location.from2d(-x, y, 180 - a) Cq.Location.flip_x = flip_x def flip_y(self: Cq.Location) -> Cq.Location: (x, y), a = self.to2d() return Cq.Location.from2d(x, -y, -a) Cq.Location.flip_y = flip_y -def boolean(self: Cq.Sketch, obj, **kwargs) -> Cq.Sketch: +def boolean( + self: Cq.Sketch, + obj: Union[Cq.Face, Cq.Sketch, Cq.Compound], + **kwargs) -> Cq.Sketch: + """ + Performs Boolean operation between a sketch and a sketch-like object + """ return ( self .reset() + # Has to be 0, 0. Translation doesn't work. .push([(0, 0)]) .each(lambda _: obj, **kwargs) )