2024-06-26 06:48:32 -07:00
|
|
|
"""
|
|
|
|
A catalog of material properties
|
|
|
|
"""
|
2024-07-16 11:55:38 -07:00
|
|
|
from enum import Enum, Flag, auto
|
|
|
|
from typing import Union, Optional
|
2024-06-26 06:48:32 -07:00
|
|
|
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)
|
|
|
|
|
2024-07-16 11:55:38 -07:00
|
|
|
class Role(Flag):
|
2024-06-27 20:26:21 -07:00
|
|
|
"""
|
|
|
|
Describes the role of a part
|
|
|
|
"""
|
|
|
|
|
2024-07-18 21:33:17 -07:00
|
|
|
# Externally supplied object
|
|
|
|
FIXTURE = auto()
|
|
|
|
# Parent and child sides of joints
|
2024-07-16 11:55:38 -07:00
|
|
|
PARENT = auto()
|
|
|
|
CHILD = auto()
|
|
|
|
CASING = auto()
|
2024-07-18 21:33:17 -07:00
|
|
|
# Springs, cushions
|
2024-07-16 11:55:38 -07:00
|
|
|
DAMPING = auto()
|
2024-07-18 21:33:17 -07:00
|
|
|
# Main structural support
|
2024-07-16 11:55:38 -07:00
|
|
|
STRUCTURE = auto()
|
|
|
|
DECORATION = auto()
|
|
|
|
ELECTRONIC = auto()
|
|
|
|
CONNECTION = auto()
|
|
|
|
|
2024-06-27 20:26:21 -07:00
|
|
|
# Parent and child components in a load bearing joint
|
2024-07-16 11:55:38 -07:00
|
|
|
|
|
|
|
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]
|
|
|
|
|
2024-07-18 21:33:17 -07:00
|
|
|
|
2024-07-16 11:55:38 -07:00
|
|
|
# Maps roles to their colours
|
|
|
|
ROLE_COLOR_MAP = {
|
2024-07-18 21:33:17 -07:00
|
|
|
Role.FIXTURE: _color('black', 0.2),
|
2024-07-16 11:55:38 -07:00
|
|
|
Role.PARENT: _color('blue4', 0.6),
|
|
|
|
Role.CASING: _color('dodgerblue3', 0.6),
|
|
|
|
Role.CHILD: _color('darkorange2', 0.6),
|
2024-07-16 13:28:53 -07:00
|
|
|
Role.DAMPING: _color('springgreen', 1.0),
|
2024-07-16 11:55:38 -07:00
|
|
|
Role.STRUCTURE: _color('gray', 0.4),
|
|
|
|
Role.DECORATION: _color('lightseagreen', 0.4),
|
|
|
|
Role.ELECTRONIC: _color('mediumorchid', 0.5),
|
|
|
|
Role.CONNECTION: _color('steelblue3', 0.8)
|
|
|
|
}
|
|
|
|
|
2024-06-27 20:26:21 -07:00
|
|
|
|
2024-06-26 06:48:32 -07:00
|
|
|
class Material(Enum):
|
2024-06-27 20:26:21 -07:00
|
|
|
"""
|
|
|
|
A catalog of common material properties
|
2024-07-17 19:13:06 -07:00
|
|
|
|
|
|
|
Density listed is in g/cm^3 or mg/mm^3
|
2024-06-27 20:26:21 -07:00
|
|
|
"""
|
2024-06-26 06:48:32 -07:00
|
|
|
|
2024-07-17 19:13:06 -07:00
|
|
|
WOOD_BIRCH = 0.71, _color('bisque', 0.9)
|
|
|
|
PLASTIC_PLA = 1.2, _color('mistyrose', 0.8)
|
|
|
|
RESIN_TRANSPERENT = 1.17, _color('cadetblue2', 0.6)
|
|
|
|
RESIN_TOUGH_1500 = 1.17, _color('seashell3', 0.7)
|
|
|
|
ACRYLIC_BLACK = 1.18, _color('gray5', 0.8)
|
|
|
|
ACRYLIC_TRANSLUSCENT = 1.18, _color('ivory2', 0.8)
|
|
|
|
ACRYLIC_TRANSPARENT = 1.18, _color('ghostwhite', 0.5)
|
|
|
|
STEEL_SPRING = 7.8, _color('gray', 0.8)
|
2024-06-26 06:48:32 -07:00
|
|
|
|
|
|
|
def __init__(self, density: float, color: Cq.Color):
|
|
|
|
self.density = density
|
|
|
|
self.color = color
|
2024-07-16 11:55:38 -07:00
|
|
|
|
|
|
|
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
|
2024-07-17 19:13:06 -07:00
|
|
|
|
|
|
|
def total_mass(self: Cq.Assembly) -> float:
|
|
|
|
"""
|
|
|
|
Calculates the total mass in units of g
|
|
|
|
"""
|
|
|
|
total = 0.0
|
|
|
|
for _, a in self.traverse():
|
|
|
|
if 'material' not in a.metadata:
|
|
|
|
continue
|
|
|
|
vol = a.toCompound().Volume()
|
|
|
|
total += vol * a.metadata['material'].density
|
|
|
|
return total / 1000.0
|
|
|
|
Cq.Assembly.total_mass = total_mass
|