Skip to content

Fix Calculation of Compound Center of Masses #1822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 13, 2025
49 changes: 37 additions & 12 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,35 @@ def CombinedCenter(objects: Iterable["Shape"]) -> Vector:

return Vector(sum_wc.multiply(1.0 / total_mass))

@staticmethod
def _mass_calc_function(obj: "Shape") -> Any:
"""
Helper to find the correct mass calculation function with special compound handling.
"""

type_ = shapetype(obj.wrapped)

# special handling of compounds - first non-compound child is assumed to define the type of the operation
if type_ == ta.TopAbs_COMPOUND:

# if the compound is not empty check its children
if obj:
# first child
child = next(iter(obj))

# if compound, go deeper
while child.ShapeType() == "Compound":
child = next(iter(child))

type_ = shapetype(child.wrapped)

# if the compound is empty assume it was meant to be a solid
else:
type_ = ta.TopAbs_SOLID

# get the function based on dimensionality of the object
return shape_properties_LUT[type_]

@staticmethod
def computeMass(obj: "Shape") -> float:
"""
Expand All @@ -764,13 +793,11 @@ def computeMass(obj: "Shape") -> float:
:param obj: Compute the mass of this object
"""
Properties = GProp_GProps()
calc_function = shape_properties_LUT[shapetype(obj.wrapped)]
calc_function = Shape._mass_calc_function(obj)

if calc_function:
calc_function(obj.wrapped, Properties)
return Properties.Mass()
else:
raise NotImplementedError
calc_function(obj.wrapped, Properties)

return Properties.Mass()

@staticmethod
def centerOfMass(obj: "Shape") -> Vector:
Expand All @@ -780,13 +807,11 @@ def centerOfMass(obj: "Shape") -> Vector:
:param obj: Compute the center of mass of this object
"""
Properties = GProp_GProps()
calc_function = shape_properties_LUT[shapetype(obj.wrapped)]
calc_function = Shape._mass_calc_function(obj)

if calc_function:
calc_function(obj.wrapped, Properties)
return Vector(Properties.CentreOfMass())
else:
raise NotImplementedError
calc_function(obj.wrapped, Properties)

return Vector(Properties.CentreOfMass())

@staticmethod
def CombinedCenterOfBoundBox(objects: List["Shape"]) -> Vector:
Expand Down
13 changes: 13 additions & 0 deletions tests/test_cadquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -5904,3 +5904,16 @@ def test_loft_to_vertex(self):
# in both cases we get a solid
assert w1.solids().size() == 1
assert w2.solids().size() == 1

def test_compound_faces_center(self):
sk = Sketch().rect(50, 50).faces()
face1 = sk.val()
face2 = face1.copy().translate(Vector(100, 0, 0))
compound = Compound.makeCompound([face1, face2])
expected_center = Shape.CombinedCenter([face1, face2])

assert (
compound.Center() == expected_center
), "Incorrect center of mass of the compound, expected {}, got {}".format(
expected_center, compound.Center()
)