From c73675bbe36fe22940f78b025b6006291c488930 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Tue, 16 Jul 2024 11:55:38 -0700 Subject: [PATCH] feat: Colouring assembly by role and material --- nhf/materials.py | 102 +++++++++++++++++++++++++----- nhf/parts/joints.py | 10 +-- nhf/touhou/houjuu_nue/__init__.py | 4 -- nhf/touhou/houjuu_nue/harness.py | 8 ++- nhf/touhou/houjuu_nue/joints.py | 45 +++++++------ nhf/touhou/houjuu_nue/trident.py | 35 ++++++---- nhf/touhou/houjuu_nue/wing.py | 50 +++++++++------ nhf/utils.py | 6 +- 8 files changed, 180 insertions(+), 80 deletions(-) diff --git a/nhf/materials.py b/nhf/materials.py index 2f96bda..9408164 100644 --- a/nhf/materials.py +++ b/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 diff --git a/nhf/parts/joints.py b/nhf/parts/joints.py index 9867b4f..77dcc94 100644 --- a/nhf/parts/joints.py +++ b/nhf/parts/joints.py @@ -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, diff --git a/nhf/touhou/houjuu_nue/__init__.py b/nhf/touhou/houjuu_nue/__init__.py index 4e57fae..ea07fad 100644 --- a/nhf/touhou/houjuu_nue/__init__.py +++ b/nhf/touhou/houjuu_nue/__init__.py @@ -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 diff --git a/nhf/touhou/houjuu_nue/harness.py b/nhf/touhou/houjuu_nue/harness.py index 3df4cf8..2bcba19 100644 --- a/nhf/touhou/houjuu_nue/harness.py +++ b/nhf/touhou/houjuu_nue/harness.py @@ -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): diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index d25c22c..05acc97 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -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), - 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), - name="parent_bot", - color=Role.PARENT.color) + .addS(self.torsion_joint.spring(), name="spring_top", + role=Role.DAMPING, material=mat_spring) + .addS(self.parent(wing_root_wall_thickness), + name="parent_top", + 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", + 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( diff --git a/nhf/touhou/houjuu_nue/trident.py b/nhf/touhou/houjuu_nue/trident.py index 312ff85..bd5c5ea 100644 --- a/nhf/touhou/houjuu_nue/trident.py +++ b/nhf/touhou/houjuu_nue/trident.py @@ -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() diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index b832dff..9d84552 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -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@ 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@ 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@ 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@ 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 """