Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ jobs:

- name: Upload wheel artifacts
uses: actions/upload-artifact@v4
if: ${{ github.ref == 'refs/heads/master'}}
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/jacc' }}
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl
retention-days: 7
retention-days: 21

- name: Upload sdist artifact
uses: actions/upload-artifact@v4
Expand Down
41 changes: 41 additions & 0 deletions pymunk/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ def distance(self) -> float:
def distance(self, distance: float) -> None:
lib.cpPinJointSetDist(self._constraint, distance)

@property
def impulse_signed(self) -> float:
return lib.cpPinJointGetImpulse(self._constraint)


class SlideJoint(Constraint):
"""SlideJoint is like a PinJoint, but have a minimum and maximum distance.
Expand Down Expand Up @@ -428,6 +432,14 @@ def max(self) -> float:
def max(self, max: float) -> None:
lib.cpSlideJointSetMax(self._constraint, max)

@property
def impulse_signed(self) -> float:
"""Currently not useful, will always be -joint.impulse

TODO: fix?
"""
return lib.cpSlideJointGetImpulse(self._constraint)


class PivotJoint(Constraint):
"""PivotJoint allow two objects to pivot about a single point.
Expand Down Expand Up @@ -496,6 +508,11 @@ def anchor_b(self, anchor: tuple[float, float]) -> None:
assert len(anchor) == 2
lib.cpPivotJointSetAnchorB(self._constraint, anchor)

@property
def impulse_vector(self) -> Vec2d:
v = lib.cpPivotJointGetImpulse(self._constraint)
return Vec2d(v.x, v.y)


class GrooveJoint(Constraint):
"""GrooveJoint is similar to a PivotJoint, but with a linear slide.
Expand Down Expand Up @@ -560,6 +577,11 @@ def groove_b(self, groove: tuple[float, float]) -> None:
assert len(groove) == 2
lib.cpGrooveJointSetGrooveB(self._constraint, groove)

@property
def impulse_vector(self) -> Vec2d:
v = lib.cpGrooveJointGetImpulse(self._constraint)
return Vec2d(v.x, v.y)


class DampedSpring(Constraint):
"""DampedSpring is a damped spring.
Expand Down Expand Up @@ -821,6 +843,14 @@ def max(self) -> float:
def max(self, max: float) -> None:
lib.cpRotaryLimitJointSetMax(self._constraint, max)

@property
def impulse_signed(self) -> float:
"""
TODO: Investigate if/when this can be negative
"""
return lib.cpRotaryLimitJointGetImpulse(self._constraint)



class RatchetJoint(Constraint):
"""RatchetJoint is a rotary ratchet, it works like a socket wrench."""
Expand Down Expand Up @@ -860,6 +890,10 @@ def ratchet(self) -> float:
def ratchet(self, ratchet: float) -> None:
lib.cpRatchetJointSetRatchet(self._constraint, ratchet)

@property
def impulse_signed(self) -> float:
return lib.cpRatchetJointGetImpulse(self._constraint)


class GearJoint(Constraint):
"""GearJoint keeps the angular velocity ratio of a pair of bodies constant."""
Expand Down Expand Up @@ -892,6 +926,9 @@ def ratio(self) -> float:
def ratio(self, ratio: float) -> None:
lib.cpGearJointSetRatio(self._constraint, ratio)

@property
def impulse_signed(self) -> float:
return lib.cpGearJointGetImpulse(self._constraint)

class SimpleMotor(Constraint):
"""SimpleMotor keeps the relative angular velocity constant."""
Expand All @@ -916,3 +953,7 @@ def rate(self) -> float:
@rate.setter
def rate(self, rate: float) -> None:
lib.cpSimpleMotorSetRate(self._constraint, rate)

@property
def impulse_signed(self) -> float:
return lib.cpSimpleMotorGetImpulse(self._constraint)
162 changes: 161 additions & 1 deletion pymunk/tests/test_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,31 @@ def testAnchor(self) -> None:
self.assertEqual(j.anchor_a, (5, 6))
self.assertEqual(j.anchor_b, (7, 8))

def testDistane(self) -> None:
def testDistance(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = PinJoint(a, b, (0, 0), (10, 0))
self.assertEqual(j.distance, 10)
j.distance = 20
self.assertEqual(j.distance, 20)

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
a.position = 0, 10
j = PinJoint(a, b, (0, 0), (0, 0))
s = p.Space()
s.add(a, b, j)
s.step(0.1)
self.assertEqual(j.impulse_signed, 0)
self.assertEqual(j.impulse, 0)
a.position = 0, 0
s.step(0.1)
self.assertAlmostEqual(j.impulse_signed, 2343, 0)
self.assertAlmostEqual(j.impulse, 2343,0)
a.position = 0, 20
s.step(0.1)
self.assertAlmostEqual(j.impulse_signed, -234, 0)
self.assertAlmostEqual(j.impulse, 234, 0)

def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = PinJoint(a, b, (1, 2), (3, 4))
Expand Down Expand Up @@ -269,6 +287,26 @@ def testMax(self) -> None:
j.max = 2
self.assertEqual(j.max, 2)

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
a.position = 0, 20
j = SlideJoint(a, b, (0, 0), (0, 0), 20, 30)
s = p.Space()
s.add(a, b, j)
s.step(0.1)
self.assertEqual(j.impulse_signed, 0)
self.assertEqual(j.impulse, 0)
a.position = 0, 40
s.step(0.1)
self.assertAlmostEqual(j.impulse_signed, -234, 0)
self.assertAlmostEqual(j.impulse, 234,0)
# TODO: FIX ME when SlideJoint jnAcc is fixed.
# a.position = 0,0
# s.step(0.1)
# self.assertAlmostEqual(j.impulse_signed, 234, 0)
# self.assertAlmostEqual(j.impulse, 234, 0)


def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = SlideJoint(a, b, (1, 2), (3, 4), 5, 6)
Expand Down Expand Up @@ -302,6 +340,20 @@ def testAnchorByAnchor(self) -> None:
self.assertEqual(j.anchor_a, (5, 6))
self.assertEqual(j.anchor_b, (7, 8))

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
a.position = 0, 10
j = PivotJoint(a, b, (10,0))
s = p.Space()
s.add(a, b, j)
s.step(0.1)
almostEqualVector(self, j.impulse_vector, p.Vec2d(0,0), 0)
self.assertEqual(j.impulse, 0)
a.position = 0, 0
s.step(0.1)
almostEqualVector(self, j.impulse_vector, p.Vec2d(4,-5), 0)
self.assertAlmostEqual(j.impulse, 6,0)

def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = PivotJoint(a, b, (1, 2), (3, 4))
Expand Down Expand Up @@ -333,6 +385,22 @@ def testGroove(self) -> None:
self.assertEqual(j.groove_a, (5, 6))
self.assertEqual(j.groove_b, (7, 8))

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
a.position = 0, 2.5
b.position = 0, 2.5
j = GrooveJoint(a, b, (0,0), (10, 0), (0,0))
s = p.Space()
s.add(a, b, j)
almostEqualVector(self, j.impulse_vector, p.Vec2d(0,0))
self.assertAlmostEqual(j.impulse, 0)

a.position = 0, 0
s.step(0.1)
almostEqualVector(self, j.impulse_vector, p.Vec2d(0, -59), 0)
self.assertAlmostEqual(j.impulse, 59,0)


def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = GrooveJoint(a, b, (1, 2), (3, 4), (5, 6))
Expand Down Expand Up @@ -403,6 +471,20 @@ def f(spring: p.DampedSpring, dist: float) -> float:
s.step(1)
self.assertAlmostEqual(j.impulse, -100.15)

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
a.position = 0, 0
b.position = 0, 10
j = DampedSpring(a, b, (0,0), (0,0), 10, 9, 0.01)
s = p.Space()
s.add(a, b, j)
s.step(0.1)
self.assertAlmostEqual(j.impulse, 0)
b.position = 0,25
s.step(0.1)
self.assertAlmostEqual(j.impulse, -13.497300269982)


def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = DampedSpring(a, b, (1, 2), (3, 4), 5, 6, 7)
Expand Down Expand Up @@ -464,6 +546,18 @@ def f(spring: p.DampedRotarySpring, relative_angle: float) -> float:
j.torque_func = DampedRotarySpring.spring_torque
s.step(1)
self.assertAlmostEqual(j.impulse, -21.5)

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
a.angle = 1
j = DampedRotarySpring(a, b, 1, 9, 0.01)
s = p.Space()
s.add(a, b, j)
s.step(0.1)
self.assertAlmostEqual(j.impulse, 0)
a.angle = 2
s.step(0.1)
self.assertAlmostEqual(j.impulse, 0.89982001)

def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
Expand Down Expand Up @@ -494,6 +588,19 @@ def testMax(self) -> None:
j.max = 2
self.assertEqual(j.max, 2)

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
j = RotaryLimitJoint(a, b, 0, 1)
s = p.Space()
s.add(a, b, j)
s.step(0.1)
self.assertAlmostEqual(j.impulse, 0)
self.assertAlmostEqual(j.impulse_signed, 0)
a.angle = 2
s.step(0.1)
self.assertAlmostEqual(j.impulse, 46.855908447)
self.assertAlmostEqual(j.impulse_signed, 46.855908447)

def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = RotaryLimitJoint(a, b, 1, 2)
Expand Down Expand Up @@ -529,6 +636,19 @@ def testRatchet(self) -> None:
j.ratchet = 2
self.assertEqual(j.ratchet, 2)

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
j = RatchetJoint(a, b, 0.1, 0.1)
s = p.Space()
s.add(a, b, j)
s.step(0.1)
self.assertAlmostEqual(j.impulse, 0)
self.assertAlmostEqual(j.impulse_signed, 0)
a.angle = 2
s.step(0.1)
self.assertAlmostEqual(j.impulse, 46.855908447)
self.assertAlmostEqual(j.impulse_signed, 46.855908447)

def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = RatchetJoint(a, b, 1, 2)
Expand Down Expand Up @@ -557,6 +677,24 @@ def testRatio(self) -> None:
j.ratio = 2
self.assertEqual(j.ratio, 2)

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
b.angle = 0.03
j = GearJoint(a, b, 0.1, 3)
s = p.Space()
s.add(a, b, j)
self.assertAlmostEqual(j.impulse, 0)
self.assertAlmostEqual(j.impulse_signed, 0)
a.angle = 2
s.step(0.1)
self.assertAlmostEqual(j.impulse, 28.25411279)
self.assertAlmostEqual(j.impulse_signed, 28.25411279)
a.angular_velocity = -0.1
s.step(0.1)
self.assertAlmostEqual(j.impulse, 9.5300055)
self.assertAlmostEqual(j.impulse_signed, -9.5300055)


def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = GearJoint(a, b, 1, 2)
Expand All @@ -570,6 +708,7 @@ def testPickle(self) -> None:
self.assertEqual(j.b.mass, j2.b.mass)



class UnitTestSimleMotor(unittest.TestCase):
def testSimpleMotor(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
Expand All @@ -578,6 +717,23 @@ def testSimpleMotor(self) -> None:
j.rate = 0.4
self.assertEqual(j.rate, 0.4)

def testImpulse(self) -> None:
a, b = p.Body(10, 10), p.Body(10, 10)
a.angular_velocity = .3
j = SimpleMotor(a, b, .3)
s = p.Space()
s.add(a, b, j)
self.assertAlmostEqual(j.impulse, 0)
self.assertAlmostEqual(j.impulse_signed, 0)
a.angular_velocity = 1
s.step(0.1)
self.assertAlmostEqual(j.impulse, 3.5)
self.assertAlmostEqual(j.impulse_signed, 3.5)
a.angular_velocity = -0.1
s.step(0.1)
self.assertAlmostEqual(j.impulse, 3.75)
self.assertAlmostEqual(j.impulse_signed, -3.75)

def testPickle(self) -> None:
a, b = p.Body(10, 10), p.Body(20, 20)
j = SimpleMotor(a, b, 1)
Expand All @@ -597,3 +753,7 @@ def pre_solve(c: Constraint, s: p.Space) -> None:

def post_solve(c: Constraint, s: p.Space) -> None:
pass

def almostEqualVector(self, first:p.Vec2d, second:p.Vec2d, places:int=7) -> None:
self.assertAlmostEqual(first.x, second.x, places)
self.assertAlmostEqual(first.y, second.y, places)
Loading
Loading