feat: Colouring assembly by role and material

This commit is contained in:
Leni Aniva 2024-07-16 11:55:38 -07:00
parent 027eec7264
commit c73675bbe3
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
8 changed files with 180 additions and 80 deletions

View File

@ -1,30 +1,55 @@
""" """
A catalog of material properties A catalog of material properties
""" """
from enum import Enum from enum import Enum, Flag, auto
from typing import Union, Optional
import cadquery as Cq import cadquery as Cq
def _color(name: str, alpha: float) -> Cq.Color: def _color(name: str, alpha: float) -> Cq.Color:
r, g, b, _ = Cq.Color(name).toTuple() r, g, b, _ = Cq.Color(name).toTuple()
return Cq.Color(r, g, b, alpha) return Cq.Color(r, g, b, alpha)
class Role(Enum): class Role(Flag):
""" """
Describes the role of a part Describes the role of a part
""" """
# Parent and child components in a load bearing joint PARENT = auto()
PARENT = _color('blue4', 0.6) CHILD = auto()
CASING = _color('dodgerblue3', 0.6) CASING = auto()
CHILD = _color('darkorange2', 0.6) DAMPING = auto()
DAMPING = _color('springgreen', 0.5) STRUCTURE = auto()
STRUCTURE = _color('gray', 0.4) DECORATION = auto()
DECORATION = _color('lightseagreen', 0.4) ELECTRONIC = auto()
ELECTRONIC = _color('mediumorchid', 0.5) CONNECTION = auto()
MARKER = _color('cyan', 1.0)
# 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): class Material(Enum):
""" """
@ -32,11 +57,56 @@ class Material(Enum):
""" """
WOOD_BIRCH = 0.8, _color('bisque', 0.9) WOOD_BIRCH = 0.8, _color('bisque', 0.9)
PLASTIC_PLA = 0.5, _color('azure3', 0.6) PLASTIC_PLA = 0.5, _color('mistyrose', 0.8)
RESIN_TRANSPARENT = 1.1, _color('cadetblue2', 0.6) RESIN_TRANSPERENT = 1.1, _color('cadetblue2', 0.6)
ACRYLIC_BLACK = 0.5, _color('gray50', 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) ACRYLIC_TRANSPARENT = 0.5, _color('ghostwhite', 0.5)
STEEL_SPRING = 1.0, _color('gray', 0.8)
def __init__(self, density: float, color: Cq.Color): def __init__(self, density: float, color: Cq.Color):
self.density = density self.density = density
self.color = color 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

View File

@ -141,8 +141,8 @@ class HirthJoint:
angle = offset * self.tooth_angle angle = offset * self.tooth_angle
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(obj1, name="obj1", color=Role.PARENT.color) .addS(obj1, name="obj1", role=Role.PARENT)
.add(obj2, name="obj2", color=Role.CHILD.color) .addS(obj2, name="obj2", role=Role.CHILD)
.constrain("obj1", "Fixed") .constrain("obj1", "Fixed")
.constrain("obj1?mate", "obj2?mate", "Plane") .constrain("obj1?mate", "obj2?mate", "Plane")
.constrain("obj1?dir", "obj2?dir", "Axis", param=angle) .constrain("obj1?dir", "obj2?dir", "Axis", param=angle)
@ -488,9 +488,9 @@ class TorsionJoint:
spring = self.spring() spring = self.spring()
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(spring, name="spring", color=Role.DAMPING.color) .addS(spring, name="spring", role=Role.DAMPING)
.add(track, name="track", color=Role.PARENT.color) .addS(track, name="track", role=Role.PARENT)
.add(rider, name="rider", color=Role.CHILD.color) .addS(rider, name="rider", role=Role.CHILD)
) )
TorsionJoint.add_constraints( TorsionJoint.add_constraints(
result, result,

View File

@ -31,7 +31,6 @@ shoulder, elbow, wrist in analogy with human anatomy.
""" """
from dataclasses import dataclass, field from dataclasses import dataclass, field
import cadquery as Cq import cadquery as Cq
from nhf import Material, Role
from nhf.build import Model, TargetKind, target, assembly from nhf.build import Model, TargetKind, target, assembly
from nhf.parts.joints import HirthJoint, TorsionJoint from nhf.parts.joints import HirthJoint, TorsionJoint
from nhf.parts.handle import Handle, BayonetMount from nhf.parts.handle import Handle, BayonetMount
@ -99,9 +98,6 @@ class Parameters(Model):
trident_terminal_hole_diam: float = 24 trident_terminal_hole_diam: float = 24
trident_terminal_bottom_thickness: float = 10 trident_terminal_bottom_thickness: float = 10
material_panel: Material = Material.ACRYLIC_TRANSPARENT
material_bracket: Material = Material.ACRYLIC_TRANSPARENT
def __post_init__(self): def __post_init__(self):
super().__init__(name="houjuu-nue") super().__init__(name="houjuu-nue")
self.harness.hs_hirth_joint = self.hs_hirth_joint self.harness.hs_hirth_joint = self.hs_hirth_joint

View File

@ -154,14 +154,18 @@ class Harness:
harness = self.surface() harness = self.surface()
result = ( result = (
Cq.Assembly() 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") .constrain("base", "Fixed")
) )
for name in ["l1", "l2", "l3", "r1", "r2", "r3"]: for name in ["l1", "l2", "l3", "r1", "r2", "r3"]:
j = self.hs_joint_parent() j = self.hs_joint_parent()
( (
result 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") #.constrain("base?mount", f"{name}?base", "Axis")
) )
for i in range(4): for i in range(4):

View File

@ -1,7 +1,7 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Optional, Tuple from typing import Optional, Tuple
import cadquery as Cq import cadquery as Cq
from nhf import Role from nhf import Material, Role
from nhf.build import Model, target, assembly from nhf.build import Model, target, assembly
import nhf.parts.springs as springs import nhf.parts.springs as springs
from nhf.parts.joints import TorsionJoint from nhf.parts.joints import TorsionJoint
@ -182,21 +182,23 @@ class ShoulderJoint(Model):
wing_root_wall_thickness: float = 25.4/16, wing_root_wall_thickness: float = 25.4/16,
) -> Cq.Assembly: ) -> Cq.Assembly:
directrix = 0 directrix = 0
mat = Material.RESIN_TRANSPERENT
mat_spring = Material.STEEL_SPRING
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.child(), name="child", .addS(self.child(), name="child",
color=Role.CHILD.color) role=Role.CHILD, material=mat)
.constrain("child/core", "Fixed") .constrain("child/core", "Fixed")
.add(self.torsion_joint.spring(), name="spring_top", .addS(self.torsion_joint.spring(), name="spring_top",
color=Role.DAMPING.color) role=Role.DAMPING, material=mat_spring)
.add(self.parent(wing_root_wall_thickness), .addS(self.parent(wing_root_wall_thickness),
name="parent_top", name="parent_top",
color=Role.PARENT.color) role=Role.PARENT, material=mat)
.add(self.torsion_joint.spring(), name="spring_bot", .addS(self.torsion_joint.spring(), name="spring_bot",
color=Role.DAMPING.color) role=Role.DAMPING, material=mat_spring)
.add(self.parent(wing_root_wall_thickness), .addS(self.parent(wing_root_wall_thickness),
name="parent_bot", name="parent_bot",
color=Role.PARENT.color) role=Role.PARENT, material=mat)
) )
TorsionJoint.add_constraints(result, TorsionJoint.add_constraints(result,
rider="child/rider_top", rider="child/rider_top",
@ -516,9 +518,9 @@ class DiskJoint(Model):
assert 0 <= angle <= self.movement_angle assert 0 <= angle <= self.movement_angle
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.disk(), name="disk", color=Role.CHILD.color) .addS(self.disk(), name="disk", role=Role.CHILD)
.add(self.housing_lower(), name="housing_lower", color=Role.PARENT.color) .addS(self.housing_lower(), name="housing_lower", role=Role.PARENT)
.add(self.housing_upper(), name="housing_upper", color=Role.CASING.color) .addS(self.housing_upper(), name="housing_upper", role=Role.CASING)
#.constrain("housing_lower", "Fixed") #.constrain("housing_lower", "Fixed")
) )
result = self.add_constraints( result = self.add_constraints(
@ -564,6 +566,8 @@ class ElbowJoint:
# Size of the mounting holes # Size of the mounting holes
hole_diam: float = 8.0 hole_diam: float = 8.0
material: Material = Material.RESIN_TRANSPERENT
def __post_init__(self): def __post_init__(self):
assert self.child_arm_radius > self.disk_joint.radius_housing assert self.child_arm_radius > self.disk_joint.radius_housing
assert self.parent_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: def assembly(self, angle: float = 0) -> Cq.Assembly:
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.child_joint(), name="child", color=Role.CHILD.color) .addS(self.child_joint(), name="child",
.add(self.parent_joint_lower(), name="parent_lower", color=Role.CASING.color) role=Role.CHILD, material=self.material)
.add(self.parent_joint_upper(), name="parent_upper", color=Role.PARENT.color) .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") #.constrain("child/disk?mate_bot", "Fixed")
) )
result = self.disk_joint.add_constraints( result = self.disk_joint.add_constraints(

View File

@ -1,6 +1,6 @@
import math import math
import cadquery as Cq import cadquery as Cq
from nhf import Material from nhf import Material, Role
from nhf.parts.handle import Handle from nhf.parts.handle import Handle
def trident_assembly( def trident_assembly(
@ -16,27 +16,40 @@ def trident_assembly(
.faces(">Z") .faces(">Z")
.hole(15, terminal_height + handle.insertion_length - 10) .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 mat_s = Material.ACRYLIC_BLACK
role_i = Role.CONNECTION
role_c = Role.CONNECTION
role_s = Role.STRUCTURE
assembly = ( assembly = (
Cq.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") .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) .constrain("i0?rim", "s1?mate1", "Plane", param=0)
.add(handle.insertion(), name="i1", color=mat_i.color) .addS(handle.insertion(), name="i1",
.add(handle.connector(), name="c1", color=mat_i.color) material=mat_i, role=role_i)
.add(handle.insertion(), name="i2", color=mat_i.color) .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("s1?mate2", "i1?rim", "Plane", param=0)
.constrain("i1?mate", "c1?mate1", "Plane") .constrain("i1?mate", "c1?mate1", "Plane")
.constrain("i2?mate", "c1?mate2", "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) .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) .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") .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") .constrain("i0?mate", "terminal?mate", "Plane")
) )
return assembly.solve() return assembly.solve()

View File

@ -63,8 +63,10 @@ class WingProfile(Model):
ring_y: float = 20 ring_y: float = 20
ring_radius_inner: float = 22 ring_radius_inner: float = 22
material_panel: Material = Material.ACRYLIC_TRANSPARENT mat_panel: Material = Material.ACRYLIC_TRANSLUSCENT
material_bracket: Material = Material.ACRYLIC_TRANSPARENT mat_bracket: Material = Material.ACRYLIC_TRANSPARENT
mat_hs_joint: Material = Material.PLASTIC_PLA
role_panel: Role = Role.STRUCTURE
def __post_init__(self): def __post_init__(self):
super().__init__(name=self.name) super().__init__(name=self.name)
@ -170,8 +172,10 @@ class WingProfile(Model):
def assembly_s0(self) -> Cq.Assembly: def assembly_s0(self) -> Cq.Assembly:
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.surface_s0(top=True), name="bot", color=self.material_panel.color) .addS(self.surface_s0(top=True), name="bot",
.add(self.surface_s0(top=False), name="top", color=self.material_panel.color) 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", .constrain("bot@faces@>Z", "top@faces@<Z", "Point",
param=self.shoulder_joint.height) param=self.shoulder_joint.height)
) )
@ -181,7 +185,9 @@ class WingProfile(Model):
]: ]:
( (
result 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}?bot", f"bot?{tag}", "Plane")
.constrain(f"{tag}?top", f"top?{tag}", "Plane") .constrain(f"{tag}?top", f"top?{tag}", "Plane")
.constrain(f"{tag}?dir", f"top?{tag}_dir", "Axis") .constrain(f"{tag}?dir", f"top?{tag}_dir", "Axis")
@ -189,7 +195,7 @@ class WingProfile(Model):
hirth = self.base_joint.generate() hirth = self.base_joint.generate()
( (
result 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") .constrain("hs@faces@<Z", "base?dir", "Plane")
) )
return result.solve() return result.solve()
@ -216,9 +222,11 @@ class WingProfile(Model):
angle = 0 angle = 0
( (
a a
.add(spacer, .addS(
name=point_tag, spacer,
color=self.material_bracket.color) name=point_tag,
material=self.mat_bracket,
role=self.role_panel)
.constrain(f"{front_tag}?{point_tag}", .constrain(f"{front_tag}?{point_tag}",
f"{point_tag}?{site_front}", "Plane") f"{point_tag}?{site_front}", "Plane")
.constrain(f"{back_tag}?{point_tag}", .constrain(f"{back_tag}?{point_tag}",
@ -374,11 +382,11 @@ class WingProfile(Model):
def assembly_s1(self) -> Cq.Assembly: def assembly_s1(self) -> Cq.Assembly:
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.surface_s1(front=True), name="front", .addS(self.surface_s1(front=True), name="front",
color=self.material_panel.color) material=self.mat_panel, role=self.role_panel)
.constrain("front", "Fixed") .constrain("front", "Fixed")
.add(self.surface_s1(front=False), name="back", .addS(self.surface_s1(front=False), name="back",
color=self.material_panel.color) material=self.mat_panel, role=self.role_panel)
.constrain("front@faces@>Z", "back@faces@<Z", "Point", .constrain("front@faces@>Z", "back@faces@<Z", "Point",
param=self.s1_thickness) param=self.s1_thickness)
) )
@ -459,11 +467,11 @@ class WingProfile(Model):
def assembly_s2(self) -> Cq.Assembly: def assembly_s2(self) -> Cq.Assembly:
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.surface_s2(front=True), name="front", .addS(self.surface_s2(front=True), name="front",
color=self.material_panel.color) material=self.mat_panel, role=self.role_panel)
.constrain("front", "Fixed") .constrain("front", "Fixed")
.add(self.surface_s2(front=False), name="back", .addS(self.surface_s2(front=False), name="back",
color=self.material_panel.color) material=self.mat_panel, role=self.role_panel)
.constrain("front@faces@>Z", "back@faces@<Z", "Point", .constrain("front@faces@>Z", "back@faces@<Z", "Point",
param=self.s1_thickness) param=self.s1_thickness)
) )
@ -516,11 +524,11 @@ class WingProfile(Model):
def assembly_s3(self) -> Cq.Assembly: def assembly_s3(self) -> Cq.Assembly:
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.surface_s3(front=True), name="front", .addS(self.surface_s3(front=True), name="front",
color=self.material_panel.color) material=self.mat_panel, role=self.role_panel)
.constrain("front", "Fixed") .constrain("front", "Fixed")
.add(self.surface_s3(front=False), name="back", .addS(self.surface_s3(front=False), name="back",
color=self.material_panel.color) material=self.mat_panel, role=self.role_panel)
.constrain("front@faces@>Z", "back@faces@<Z", "Point", .constrain("front@faces@>Z", "back@faces@<Z", "Point",
param=self.s1_thickness) param=self.s1_thickness)
) )

View File

@ -11,6 +11,8 @@ import cadquery as Cq
from nhf import Role from nhf import Role
from typing import Union, Tuple, cast from typing import Union, Tuple, cast
COLOR_MARKER = Cq.Color(0, 1, 1, 1)
# Bug fixes # Bug fixes
def _subloc(self, name: str) -> Tuple[Cq.Location, str]: 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, def mark_point(self: Cq.Assembly,
tag: str, tag: str,
size: float = 2, 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 Adds a marker to make a point visible
""" """
@ -117,7 +119,7 @@ Cq.Assembly.markPoint = mark_point
def mark_plane(self: Cq.Assembly, def mark_plane(self: Cq.Assembly,
tag: str, tag: str,
size: float = 2, 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 Adds a marker to make a plane visible
""" """