cosplay: Touhou/Houjuu Nue #4
|
@ -31,7 +31,7 @@ class Item:
|
||||||
def role(self) -> Optional[Role]:
|
def role(self) -> Optional[Role]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def generate(self, **kwargs) -> Union[Cq.Assembly, Cq.Workplane]:
|
def generate(self, **kwargs) -> Union[Cq.Solid, Cq.Assembly, Cq.Workplane]:
|
||||||
"""
|
"""
|
||||||
Creates an assembly for this item. Subclass should implement this
|
Creates an assembly for this item. Subclass should implement this
|
||||||
"""
|
"""
|
||||||
|
@ -42,7 +42,7 @@ class Item:
|
||||||
Interface for creating assembly with the necessary metadata
|
Interface for creating assembly with the necessary metadata
|
||||||
"""
|
"""
|
||||||
a = self.generate(**kwargs)
|
a = self.generate(**kwargs)
|
||||||
if isinstance(a, Cq.Workplane):
|
if isinstance(a, Cq.Workplane) or isinstance(a, Cq.Solid):
|
||||||
a = Cq.Assembly(a)
|
a = Cq.Assembly(a)
|
||||||
if role := self.role:
|
if role := self.role:
|
||||||
a.metadata[KEY_ROLE] = role
|
a.metadata[KEY_ROLE] = role
|
||||||
|
|
27
nhf/test.py
27
nhf/test.py
|
@ -1,9 +1,11 @@
|
||||||
"""
|
"""
|
||||||
Unit tests for tooling
|
Unit tests for tooling
|
||||||
"""
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
import unittest
|
import unittest
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf.build import Model, target
|
from nhf.build import Model, target
|
||||||
|
from nhf.parts.item import Item
|
||||||
import nhf.checks
|
import nhf.checks
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
|
@ -17,6 +19,20 @@ def makeSphere(r: float) -> Cq.Solid:
|
||||||
"""
|
"""
|
||||||
return Cq.Solid.makeSphere(r, angleDegrees1=-90)
|
return Cq.Solid.makeSphere(r, angleDegrees1=-90)
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MassBall(Item):
|
||||||
|
"""
|
||||||
|
A ball with fixed mass
|
||||||
|
"""
|
||||||
|
radius: float = 0.2
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f"MassBall {self.mass}"
|
||||||
|
def generate(self) -> Cq.Solid:
|
||||||
|
return makeSphere(self.radius)
|
||||||
|
|
||||||
|
|
||||||
class BuildScaffold(Model):
|
class BuildScaffold(Model):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -180,5 +196,16 @@ class TestUtils(unittest.TestCase):
|
||||||
self.assertAlmostEqual(ry, 1)
|
self.assertAlmostEqual(ry, 1)
|
||||||
self.assertAlmostEqual(rz, 0)
|
self.assertAlmostEqual(rz, 0)
|
||||||
|
|
||||||
|
def test_centre_of_mass(self):
|
||||||
|
assembly = (
|
||||||
|
Cq.Assembly()
|
||||||
|
.add(MassBall(mass=3).assembly(), name="s1", loc=Cq.Location((0, 0, 0)))
|
||||||
|
.add(MassBall(mass=7).assembly(), name="s2", loc=Cq.Location((0, 0, 10)))
|
||||||
|
)
|
||||||
|
com = assembly.centre_of_mass()
|
||||||
|
self.assertAlmostEqual(com.x, 0)
|
||||||
|
self.assertAlmostEqual(com.y, 0)
|
||||||
|
self.assertAlmostEqual(com.z, 7)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
32
nhf/utils.py
32
nhf/utils.py
|
@ -3,6 +3,7 @@ Utility functions for cadquery objects
|
||||||
"""
|
"""
|
||||||
import functools
|
import functools
|
||||||
import math
|
import math
|
||||||
|
from typing import Optional
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from cadquery.occ_impl.solver import ConstraintSpec
|
from cadquery.occ_impl.solver import ConstraintSpec
|
||||||
from nhf import Role
|
from nhf import Role
|
||||||
|
@ -236,16 +237,37 @@ Cq.Assembly.get_abs_direction = get_abs_direction
|
||||||
|
|
||||||
# Tallying functions
|
# Tallying functions
|
||||||
|
|
||||||
|
def assembly_this_mass(self: Cq.Assembly) -> Optional[float]:
|
||||||
|
"""
|
||||||
|
Gets the mass of an assembly, without considering its components.
|
||||||
|
"""
|
||||||
|
if item := self.metadata.get(KEY_ITEM):
|
||||||
|
return item.mass
|
||||||
|
elif material := self.metadata.get(KEY_MATERIAL):
|
||||||
|
vol = self.toCompound().Volume()
|
||||||
|
return (vol / 1000) * material.density
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def total_mass(self: Cq.Assembly) -> float:
|
def total_mass(self: Cq.Assembly) -> float:
|
||||||
"""
|
"""
|
||||||
Calculates the total mass in units of g
|
Calculates the total mass in units of g
|
||||||
"""
|
"""
|
||||||
total = 0.0
|
total = 0.0
|
||||||
for _, a in self.traverse():
|
for _, a in self.traverse():
|
||||||
if item := a.metadata.get(KEY_ITEM):
|
if m := assembly_this_mass(a):
|
||||||
total += item.mass
|
total += m
|
||||||
elif material := a.metadata.get(KEY_MATERIAL):
|
|
||||||
vol = a.toCompound().Volume()
|
|
||||||
total += (vol / 1000) * material.density
|
|
||||||
return total
|
return total
|
||||||
Cq.Assembly.total_mass = total_mass
|
Cq.Assembly.total_mass = total_mass
|
||||||
|
|
||||||
|
def centre_of_mass(self: Cq.Assembly) -> Optional[float]:
|
||||||
|
moment = Cq.Vector()
|
||||||
|
total = 0.0
|
||||||
|
for n, a in self.traverse():
|
||||||
|
if m := assembly_this_mass(a):
|
||||||
|
moment += m * a.toCompound().Center()
|
||||||
|
total += m
|
||||||
|
if total == 0.0:
|
||||||
|
return None
|
||||||
|
return moment / total
|
||||||
|
Cq.Assembly.centre_of_mass = centre_of_mass
|
||||||
|
|
Loading…
Reference in New Issue