cosplay: Touhou/Houjuu Nue #4
102
nhf/materials.py
102
nhf/materials.py
|
@ -1,30 +1,55 @@
|
|||
"""
|
||||
A catalog of material properties
|
||||
"""
|
||||
from enum import Enum
|
||||
from enum import Enum, Flag, auto
|
||||
from typing import Union, Optional
|
||||
import cadquery as Cq
|
||||
|
||||
def _color(name: str, alpha: float) -> Cq.Color:
|
||||
r, g, b, _ = Cq.Color(name).toTuple()
|
||||
return Cq.Color(r, g, b, alpha)
|
||||
|
||||
class Role(Enum):
|
||||
class Role(Flag):
|
||||
"""
|
||||
Describes the role of a part
|
||||
"""
|
||||
|
||||
# Parent and child components in a load bearing joint
|
||||
PARENT = _color('blue4', 0.6)
|
||||
CASING = _color('dodgerblue3', 0.6)
|
||||
CHILD = _color('darkorange2', 0.6)
|
||||
DAMPING = _color('springgreen', 0.5)
|
||||
STRUCTURE = _color('gray', 0.4)
|
||||
DECORATION = _color('lightseagreen', 0.4)
|
||||
ELECTRONIC = _color('mediumorchid', 0.5)
|
||||
MARKER = _color('cyan', 1.0)
|
||||
PARENT = auto()
|
||||
CHILD = auto()
|
||||
CASING = auto()
|
||||
DAMPING = auto()
|
||||
STRUCTURE = auto()
|
||||
DECORATION = auto()
|
||||
ELECTRONIC = auto()
|
||||
CONNECTION = auto()
|
||||
|
||||
# Parent and child components in a load bearing joint
|
||||
|
||||
def color_avg(self) -> Cq.Color:
|
||||
r, g, b, a = zip(*[ROLE_COLOR_MAP[component].toTuple() for component in self])
|
||||
|
||||
def avg(li):
|
||||
assert li
|
||||
return sum(li) / len(li)
|
||||
r, g, b, a = avg(r), avg(g), avg(b), avg(a)
|
||||
return Cq.Color(r, g, b, a)
|
||||
|
||||
def color_head(self) -> Cq.Color:
|
||||
head = next(iter(self))
|
||||
return ROLE_COLOR_MAP[head]
|
||||
|
||||
# Maps roles to their colours
|
||||
ROLE_COLOR_MAP = {
|
||||
Role.PARENT: _color('blue4', 0.6),
|
||||
Role.CASING: _color('dodgerblue3', 0.6),
|
||||
Role.CHILD: _color('darkorange2', 0.6),
|
||||
Role.DAMPING: _color('springgreen', 0.8),
|
||||
Role.STRUCTURE: _color('gray', 0.4),
|
||||
Role.DECORATION: _color('lightseagreen', 0.4),
|
||||
Role.ELECTRONIC: _color('mediumorchid', 0.5),
|
||||
Role.CONNECTION: _color('steelblue3', 0.8)
|
||||
}
|
||||
|
||||
def __init__(self, color: Cq.Color):
|
||||
self.color = color
|
||||
|
||||
class Material(Enum):
|
||||
"""
|
||||
|
@ -32,11 +57,56 @@ class Material(Enum):
|
|||
"""
|
||||
|
||||
WOOD_BIRCH = 0.8, _color('bisque', 0.9)
|
||||
PLASTIC_PLA = 0.5, _color('azure3', 0.6)
|
||||
RESIN_TRANSPARENT = 1.1, _color('cadetblue2', 0.6)
|
||||
ACRYLIC_BLACK = 0.5, _color('gray50', 0.6)
|
||||
PLASTIC_PLA = 0.5, _color('mistyrose', 0.8)
|
||||
RESIN_TRANSPERENT = 1.1, _color('cadetblue2', 0.6)
|
||||
RESIN_TOUGH_1500 = 1.1, _color('seashell3', 0.7)
|
||||
ACRYLIC_BLACK = 0.5, _color('gray5', 0.8)
|
||||
ACRYLIC_TRANSLUSCENT = 0.5, _color('ivory2', 0.8)
|
||||
ACRYLIC_TRANSPARENT = 0.5, _color('ghostwhite', 0.5)
|
||||
STEEL_SPRING = 1.0, _color('gray', 0.8)
|
||||
|
||||
def __init__(self, density: float, color: Cq.Color):
|
||||
self.density = density
|
||||
self.color = color
|
||||
|
||||
def add_with_material_role(
|
||||
self: Cq.Assembly,
|
||||
obj: Union[Cq.Shape, Cq.Workplane, None],
|
||||
loc: Optional[Cq.Location] = None,
|
||||
name: Optional[str] = None,
|
||||
material: Optional[Material] = None,
|
||||
role: Optional[Role] = None) -> Cq.Assembly:
|
||||
"""
|
||||
Structural add function which allows specifying material and role
|
||||
"""
|
||||
metadata = {}
|
||||
color = None
|
||||
if material:
|
||||
metadata["material"] = material
|
||||
color = material.color
|
||||
if role:
|
||||
metadata["role"] = role
|
||||
color = role.color_avg()
|
||||
if len(metadata) == 0:
|
||||
metadata = None
|
||||
|
||||
self.add(obj, loc=loc, name=name, color=color, metadata=metadata)
|
||||
return self
|
||||
|
||||
Cq.Assembly.addS = add_with_material_role
|
||||
|
||||
def color_by_material(self: Cq.Assembly) -> Cq.Assembly:
|
||||
for _, a in self.traverse():
|
||||
if 'material' not in a.metadata:
|
||||
continue
|
||||
a.color = a.metadata["material"].color
|
||||
return self
|
||||
Cq.Assembly.color_by_material = color_by_material
|
||||
def color_by_role(self: Cq.Assembly, avg: bool = True) -> Cq.Assembly:
|
||||
for _, a in self.traverse():
|
||||
if 'role' not in a.metadata:
|
||||
continue
|
||||
role = a.metadata["role"]
|
||||
a.color = role.color_avg() if avg else role.color_head()
|
||||
return self
|
||||
Cq.Assembly.color_by_role = color_by_role
|
||||
|
|
|
@ -141,8 +141,8 @@ class HirthJoint:
|
|||
angle = offset * self.tooth_angle
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(obj1, name="obj1", color=Role.PARENT.color)
|
||||
.add(obj2, name="obj2", color=Role.CHILD.color)
|
||||
.addS(obj1, name="obj1", role=Role.PARENT)
|
||||
.addS(obj2, name="obj2", role=Role.CHILD)
|
||||
.constrain("obj1", "Fixed")
|
||||
.constrain("obj1?mate", "obj2?mate", "Plane")
|
||||
.constrain("obj1?dir", "obj2?dir", "Axis", param=angle)
|
||||
|
@ -488,9 +488,9 @@ class TorsionJoint:
|
|||
spring = self.spring()
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(spring, name="spring", color=Role.DAMPING.color)
|
||||
.add(track, name="track", color=Role.PARENT.color)
|
||||
.add(rider, name="rider", color=Role.CHILD.color)
|
||||
.addS(spring, name="spring", role=Role.DAMPING)
|
||||
.addS(track, name="track", role=Role.PARENT)
|
||||
.addS(rider, name="rider", role=Role.CHILD)
|
||||
)
|
||||
TorsionJoint.add_constraints(
|
||||
result,
|
||||
|
|
|
@ -31,7 +31,6 @@ shoulder, elbow, wrist in analogy with human anatomy.
|
|||
"""
|
||||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, TargetKind, target, assembly
|
||||
from nhf.parts.joints import HirthJoint, TorsionJoint
|
||||
from nhf.parts.handle import Handle, BayonetMount
|
||||
|
@ -99,9 +98,6 @@ class Parameters(Model):
|
|||
trident_terminal_hole_diam: float = 24
|
||||
trident_terminal_bottom_thickness: float = 10
|
||||
|
||||
material_panel: Material = Material.ACRYLIC_TRANSPARENT
|
||||
material_bracket: Material = Material.ACRYLIC_TRANSPARENT
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="houjuu-nue")
|
||||
self.harness.hs_hirth_joint = self.hs_hirth_joint
|
||||
|
|
|
@ -154,14 +154,18 @@ class Harness:
|
|||
harness = self.surface()
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(harness, name="base", color=Material.WOOD_BIRCH.color)
|
||||
.addS(harness, name="base",
|
||||
material=Material.WOOD_BIRCH,
|
||||
role=Role.STRUCTURE)
|
||||
.constrain("base", "Fixed")
|
||||
)
|
||||
for name in ["l1", "l2", "l3", "r1", "r2", "r3"]:
|
||||
j = self.hs_joint_parent()
|
||||
(
|
||||
result
|
||||
.add(j, name=name, color=Role.PARENT.color)
|
||||
.addS(j, name=name,
|
||||
role=Role.PARENT,
|
||||
material=Material.PLASTIC_PLA)
|
||||
#.constrain("base?mount", f"{name}?base", "Axis")
|
||||
)
|
||||
for i in range(4):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Tuple
|
||||
import cadquery as Cq
|
||||
from nhf import Role
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, target, assembly
|
||||
import nhf.parts.springs as springs
|
||||
from nhf.parts.joints import TorsionJoint
|
||||
|
@ -182,21 +182,23 @@ class ShoulderJoint(Model):
|
|||
wing_root_wall_thickness: float = 25.4/16,
|
||||
) -> Cq.Assembly:
|
||||
directrix = 0
|
||||
mat = Material.RESIN_TRANSPERENT
|
||||
mat_spring = Material.STEEL_SPRING
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(self.child(), name="child",
|
||||
color=Role.CHILD.color)
|
||||
.addS(self.child(), name="child",
|
||||
role=Role.CHILD, material=mat)
|
||||
.constrain("child/core", "Fixed")
|
||||
.add(self.torsion_joint.spring(), name="spring_top",
|
||||
color=Role.DAMPING.color)
|
||||
.add(self.parent(wing_root_wall_thickness),
|
||||
.addS(self.torsion_joint.spring(), name="spring_top",
|
||||
role=Role.DAMPING, material=mat_spring)
|
||||
.addS(self.parent(wing_root_wall_thickness),
|
||||
name="parent_top",
|
||||
color=Role.PARENT.color)
|
||||
.add(self.torsion_joint.spring(), name="spring_bot",
|
||||
color=Role.DAMPING.color)
|
||||
.add(self.parent(wing_root_wall_thickness),
|
||||
role=Role.PARENT, material=mat)
|
||||
.addS(self.torsion_joint.spring(), name="spring_bot",
|
||||
role=Role.DAMPING, material=mat_spring)
|
||||
.addS(self.parent(wing_root_wall_thickness),
|
||||
name="parent_bot",
|
||||
color=Role.PARENT.color)
|
||||
role=Role.PARENT, material=mat)
|
||||
)
|
||||
TorsionJoint.add_constraints(result,
|
||||
rider="child/rider_top",
|
||||
|
@ -516,9 +518,9 @@ class DiskJoint(Model):
|
|||
assert 0 <= angle <= self.movement_angle
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(self.disk(), name="disk", color=Role.CHILD.color)
|
||||
.add(self.housing_lower(), name="housing_lower", color=Role.PARENT.color)
|
||||
.add(self.housing_upper(), name="housing_upper", color=Role.CASING.color)
|
||||
.addS(self.disk(), name="disk", role=Role.CHILD)
|
||||
.addS(self.housing_lower(), name="housing_lower", role=Role.PARENT)
|
||||
.addS(self.housing_upper(), name="housing_upper", role=Role.CASING)
|
||||
#.constrain("housing_lower", "Fixed")
|
||||
)
|
||||
result = self.add_constraints(
|
||||
|
@ -564,6 +566,8 @@ class ElbowJoint:
|
|||
# Size of the mounting holes
|
||||
hole_diam: float = 8.0
|
||||
|
||||
material: Material = Material.RESIN_TRANSPERENT
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.child_arm_radius > self.disk_joint.radius_housing
|
||||
assert self.parent_arm_radius > self.disk_joint.radius_housing
|
||||
|
@ -638,9 +642,12 @@ class ElbowJoint:
|
|||
def assembly(self, angle: float = 0) -> Cq.Assembly:
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(self.child_joint(), name="child", color=Role.CHILD.color)
|
||||
.add(self.parent_joint_lower(), name="parent_lower", color=Role.CASING.color)
|
||||
.add(self.parent_joint_upper(), name="parent_upper", color=Role.PARENT.color)
|
||||
.addS(self.child_joint(), name="child",
|
||||
role=Role.CHILD, material=self.material)
|
||||
.addS(self.parent_joint_lower(), name="parent_lower",
|
||||
role=Role.CASING, material=self.material)
|
||||
.addS(self.parent_joint_upper(), name="parent_upper",
|
||||
role=Role.PARENT, material=self.material)
|
||||
#.constrain("child/disk?mate_bot", "Fixed")
|
||||
)
|
||||
result = self.disk_joint.add_constraints(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import math
|
||||
import cadquery as Cq
|
||||
from nhf import Material
|
||||
from nhf import Material, Role
|
||||
from nhf.parts.handle import Handle
|
||||
|
||||
def trident_assembly(
|
||||
|
@ -16,27 +16,40 @@ def trident_assembly(
|
|||
.faces(">Z")
|
||||
.hole(15, terminal_height + handle.insertion_length - 10)
|
||||
)
|
||||
mat_i = Material.PLASTIC_PLA
|
||||
mat_c = Material.PLASTIC_PLA
|
||||
mat_i = Material.RESIN_TOUGH_1500
|
||||
mat_s = Material.ACRYLIC_BLACK
|
||||
role_i = Role.CONNECTION
|
||||
role_c = Role.CONNECTION
|
||||
role_s = Role.STRUCTURE
|
||||
assembly = (
|
||||
Cq.Assembly()
|
||||
.add(handle.insertion(), name="i0", color=mat_i.color)
|
||||
.addS(handle.insertion(), name="i0",
|
||||
material=mat_i, role=role_i)
|
||||
.constrain("i0", "Fixed")
|
||||
.add(segment(), name="s1", color=mat_s.color)
|
||||
.addS(segment(), name="s1",
|
||||
material=mat_s, role=role_s)
|
||||
.constrain("i0?rim", "s1?mate1", "Plane", param=0)
|
||||
.add(handle.insertion(), name="i1", color=mat_i.color)
|
||||
.add(handle.connector(), name="c1", color=mat_i.color)
|
||||
.add(handle.insertion(), name="i2", color=mat_i.color)
|
||||
.addS(handle.insertion(), name="i1",
|
||||
material=mat_i, role=role_i)
|
||||
.addS(handle.connector(), name="c1",
|
||||
material=mat_c, role=role_c)
|
||||
.addS(handle.insertion(), name="i2",
|
||||
material=mat_i, role=role_i)
|
||||
.constrain("s1?mate2", "i1?rim", "Plane", param=0)
|
||||
.constrain("i1?mate", "c1?mate1", "Plane")
|
||||
.constrain("i2?mate", "c1?mate2", "Plane")
|
||||
.add(segment(), name="s2", color=mat_s.color)
|
||||
.addS(segment(), name="s2",
|
||||
material=mat_s, role=role_s)
|
||||
.constrain("i2?rim", "s2?mate1", "Plane", param=0)
|
||||
.add(handle.insertion(), name="i3", color=mat_i.color)
|
||||
.addS(handle.insertion(), name="i3",
|
||||
material=mat_i, role=role_i)
|
||||
.constrain("s2?mate2", "i3?rim", "Plane", param=0)
|
||||
.add(handle.one_side_connector(), name="head", color=mat_i.color)
|
||||
.addS(handle.one_side_connector(), name="head",
|
||||
material=mat_c, role=role_c)
|
||||
.constrain("i3?mate", "head?mate", "Plane")
|
||||
.add(terminal, name="terminal", color=mat_i.color)
|
||||
.addS(terminal, name="terminal",
|
||||
material=mat_c, role=role_c)
|
||||
.constrain("i0?mate", "terminal?mate", "Plane")
|
||||
)
|
||||
return assembly.solve()
|
||||
|
|
|
@ -63,8 +63,10 @@ class WingProfile(Model):
|
|||
ring_y: float = 20
|
||||
ring_radius_inner: float = 22
|
||||
|
||||
material_panel: Material = Material.ACRYLIC_TRANSPARENT
|
||||
material_bracket: Material = Material.ACRYLIC_TRANSPARENT
|
||||
mat_panel: Material = Material.ACRYLIC_TRANSLUSCENT
|
||||
mat_bracket: Material = Material.ACRYLIC_TRANSPARENT
|
||||
mat_hs_joint: Material = Material.PLASTIC_PLA
|
||||
role_panel: Role = Role.STRUCTURE
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name=self.name)
|
||||
|
@ -170,8 +172,10 @@ class WingProfile(Model):
|
|||
def assembly_s0(self) -> Cq.Assembly:
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(self.surface_s0(top=True), name="bot", color=self.material_panel.color)
|
||||
.add(self.surface_s0(top=False), name="top", color=self.material_panel.color)
|
||||
.addS(self.surface_s0(top=True), name="bot",
|
||||
material=self.mat_panel, role=self.role_panel)
|
||||
.addS(self.surface_s0(top=False), name="top",
|
||||
material=self.mat_panel, role=self.role_panel)
|
||||
.constrain("bot@faces@>Z", "top@faces@<Z", "Point",
|
||||
param=self.shoulder_joint.height)
|
||||
)
|
||||
|
@ -181,7 +185,9 @@ class WingProfile(Model):
|
|||
]:
|
||||
(
|
||||
result
|
||||
.add(o, name=tag, color=self.material_bracket.color)
|
||||
.addS(o, name=tag,
|
||||
role=Role.STRUCTURE | Role.CONNECTION,
|
||||
material=self.mat_bracket)
|
||||
.constrain(f"{tag}?bot", f"bot?{tag}", "Plane")
|
||||
.constrain(f"{tag}?top", f"top?{tag}", "Plane")
|
||||
.constrain(f"{tag}?dir", f"top?{tag}_dir", "Axis")
|
||||
|
@ -189,7 +195,7 @@ class WingProfile(Model):
|
|||
hirth = self.base_joint.generate()
|
||||
(
|
||||
result
|
||||
.add(hirth, name="hs", color=Role.CHILD.color)
|
||||
.addS(hirth, name="hs", role=Role.CHILD, material=self.mat_hs_joint)
|
||||
.constrain("hs@faces@<Z", "base?dir", "Plane")
|
||||
)
|
||||
return result.solve()
|
||||
|
@ -216,9 +222,11 @@ class WingProfile(Model):
|
|||
angle = 0
|
||||
(
|
||||
a
|
||||
.add(spacer,
|
||||
.addS(
|
||||
spacer,
|
||||
name=point_tag,
|
||||
color=self.material_bracket.color)
|
||||
material=self.mat_bracket,
|
||||
role=self.role_panel)
|
||||
.constrain(f"{front_tag}?{point_tag}",
|
||||
f"{point_tag}?{site_front}", "Plane")
|
||||
.constrain(f"{back_tag}?{point_tag}",
|
||||
|
@ -374,11 +382,11 @@ class WingProfile(Model):
|
|||
def assembly_s1(self) -> Cq.Assembly:
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(self.surface_s1(front=True), name="front",
|
||||
color=self.material_panel.color)
|
||||
.addS(self.surface_s1(front=True), name="front",
|
||||
material=self.mat_panel, role=self.role_panel)
|
||||
.constrain("front", "Fixed")
|
||||
.add(self.surface_s1(front=False), name="back",
|
||||
color=self.material_panel.color)
|
||||
.addS(self.surface_s1(front=False), name="back",
|
||||
material=self.mat_panel, role=self.role_panel)
|
||||
.constrain("front@faces@>Z", "back@faces@<Z", "Point",
|
||||
param=self.s1_thickness)
|
||||
)
|
||||
|
@ -459,11 +467,11 @@ class WingProfile(Model):
|
|||
def assembly_s2(self) -> Cq.Assembly:
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(self.surface_s2(front=True), name="front",
|
||||
color=self.material_panel.color)
|
||||
.addS(self.surface_s2(front=True), name="front",
|
||||
material=self.mat_panel, role=self.role_panel)
|
||||
.constrain("front", "Fixed")
|
||||
.add(self.surface_s2(front=False), name="back",
|
||||
color=self.material_panel.color)
|
||||
.addS(self.surface_s2(front=False), name="back",
|
||||
material=self.mat_panel, role=self.role_panel)
|
||||
.constrain("front@faces@>Z", "back@faces@<Z", "Point",
|
||||
param=self.s1_thickness)
|
||||
)
|
||||
|
@ -516,11 +524,11 @@ class WingProfile(Model):
|
|||
def assembly_s3(self) -> Cq.Assembly:
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(self.surface_s3(front=True), name="front",
|
||||
color=self.material_panel.color)
|
||||
.addS(self.surface_s3(front=True), name="front",
|
||||
material=self.mat_panel, role=self.role_panel)
|
||||
.constrain("front", "Fixed")
|
||||
.add(self.surface_s3(front=False), name="back",
|
||||
color=self.material_panel.color)
|
||||
.addS(self.surface_s3(front=False), name="back",
|
||||
material=self.mat_panel, role=self.role_panel)
|
||||
.constrain("front@faces@>Z", "back@faces@<Z", "Point",
|
||||
param=self.s1_thickness)
|
||||
)
|
||||
|
|
|
@ -11,6 +11,8 @@ import cadquery as Cq
|
|||
from nhf import Role
|
||||
from typing import Union, Tuple, cast
|
||||
|
||||
COLOR_MARKER = Cq.Color(0, 1, 1, 1)
|
||||
|
||||
# Bug fixes
|
||||
def _subloc(self, name: str) -> Tuple[Cq.Location, str]:
|
||||
"""
|
||||
|
@ -101,7 +103,7 @@ def to_marker_name(tag: str) -> str:
|
|||
def mark_point(self: Cq.Assembly,
|
||||
tag: str,
|
||||
size: float = 2,
|
||||
color: Cq.Color = Role.MARKER.color) -> Cq.Assembly:
|
||||
color: Cq.Color = COLOR_MARKER) -> Cq.Assembly:
|
||||
"""
|
||||
Adds a marker to make a point visible
|
||||
"""
|
||||
|
@ -117,7 +119,7 @@ Cq.Assembly.markPoint = mark_point
|
|||
def mark_plane(self: Cq.Assembly,
|
||||
tag: str,
|
||||
size: float = 2,
|
||||
color: Cq.Color = Role.MARKER.color) -> Cq.Assembly:
|
||||
color: Cq.Color = COLOR_MARKER) -> Cq.Assembly:
|
||||
"""
|
||||
Adds a marker to make a plane visible
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue