From 6dcebdba8825ebb1afc792efb9e632ccd0302f35 Mon Sep 17 00:00:00 2001 From: Victor Blomqvist Date: Sun, 23 Nov 2025 22:38:49 +0100 Subject: [PATCH 1/2] Added .impulse_signed and .impulse_vector on constraints --- pymunk/constraints.py | 41 ++++++++ pymunk/tests/test_constraint.py | 162 +++++++++++++++++++++++++++++++- pymunk_cffi/extensions.c | 146 +++++++++++++++++++--------- pymunk_cffi/extensions_cdef.h | 30 +++++- 4 files changed, 334 insertions(+), 45 deletions(-) diff --git a/pymunk/constraints.py b/pymunk/constraints.py index 145ab80a..1bfad7a6 100644 --- a/pymunk/constraints.py +++ b/pymunk/constraints.py @@ -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. @@ -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. @@ -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. @@ -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. @@ -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.""" @@ -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.""" @@ -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.""" @@ -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) \ No newline at end of file diff --git a/pymunk/tests/test_constraint.py b/pymunk/tests/test_constraint.py index 5a3c4233..1c0c2f88 100644 --- a/pymunk/tests/test_constraint.py +++ b/pymunk/tests/test_constraint.py @@ -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)) @@ -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) @@ -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)) @@ -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)) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) diff --git a/pymunk_cffi/extensions.c b/pymunk_cffi/extensions.c index a8b8d379..1d40b12d 100644 --- a/pymunk_cffi/extensions.c +++ b/pymunk_cffi/extensions.c @@ -106,8 +106,8 @@ cpFloat pmFloatArrayPop(pmFloatArray *arr) { cpFloat value = (cpFloat)arr->arr[arr->num]; - arr->num++; - return value; + arr->num++; + return value; } void pmFloatArrayPushVect(pmFloatArray *arr, cpVect v) @@ -125,9 +125,9 @@ void pmFloatArrayPushVect(pmFloatArray *arr, cpVect v) cpVect pmFloatArrayPopVect(pmFloatArray *arr) { - cpVect value = cpv((cpFloat)arr->arr[arr->num], (cpFloat)arr->arr[arr->num+1]); - arr->num += 2; - return value; + cpVect value = cpv((cpFloat)arr->arr[arr->num], (cpFloat)arr->arr[arr->num + 1]); + arr->num += 2; + return value; } pmIntArray * @@ -166,10 +166,10 @@ void pmIntArrayPush(pmIntArray *arr, uintptr_t v) uintptr_t pmIntArrayPop(pmIntArray *arr) -{ - uintptr_t value = (uintptr_t)arr->arr[arr->num]; - arr->num++; - return value; +{ + uintptr_t value = (uintptr_t)arr->arr[arr->num]; + arr->num++; + return value; } void pmSpaceBodyGetIteratorFuncBatched(cpBody *body, void *data) @@ -422,25 +422,28 @@ void cpSpaceAddCachedArbiter(cpSpace *space, cpArbiter *arb) // Set handlers to their defaults cpCollisionType typeA = a->type, typeB = b->type; - //cpCollisionHandler *handler = arb->handler = cpSpaceLookupHandler(space, typeA, typeB); - arb->handlerAB = cpSpaceLookupHandler(space, typeA, typeB); - arb->handlerA = cpSpaceLookupHandler(space, typeA, CP_WILDCARD_COLLISION_TYPE); - - if (typeA != typeB){ - arb->handlerBA = cpSpaceLookupHandler(space, typeB, typeA); - arb->handlerB = cpSpaceLookupHandler(space, typeB, CP_WILDCARD_COLLISION_TYPE); - } else{ - arb->handlerBA = &cpCollisionHandlerDoNothing; - arb->handlerB = &cpCollisionHandlerDoNothing; - } - arb->swapped = (typeA != arb->handlerAB->typeA); + // cpCollisionHandler *handler = arb->handler = cpSpaceLookupHandler(space, typeA, typeB); + arb->handlerAB = cpSpaceLookupHandler(space, typeA, typeB); + arb->handlerA = cpSpaceLookupHandler(space, typeA, CP_WILDCARD_COLLISION_TYPE); + + if (typeA != typeB) + { + arb->handlerBA = cpSpaceLookupHandler(space, typeB, typeA); + arb->handlerB = cpSpaceLookupHandler(space, typeB, CP_WILDCARD_COLLISION_TYPE); + } + else + { + arb->handlerBA = &cpCollisionHandlerDoNothing; + arb->handlerB = &cpCollisionHandlerDoNothing; + } + arb->swapped = (typeA != arb->handlerAB->typeA); // Check if the types match, but don't swap for a default handler which use the wildcard for type A. - //cpBool swapped = arb->swapped = (typeA != handler->typeA && handler->typeA != CP_WILDCARD_COLLISION_TYPE); + // cpBool swapped = arb->swapped = (typeA != handler->typeA && handler->typeA != CP_WILDCARD_COLLISION_TYPE); // The order of the main handler swaps the wildcard handlers too. Uffda. - //arb->handlerA = cpSpaceLookupHandler(space, (swapped ? typeB : typeA), CP_WILDCARD_COLLISION_TYPE); - //arb->handlerB = cpSpaceLookupHandler(space, (swapped ? typeA : typeB), CP_WILDCARD_COLLISION_TYPE); + // arb->handlerA = cpSpaceLookupHandler(space, (swapped ? typeB : typeA), CP_WILDCARD_COLLISION_TYPE); + // arb->handlerB = cpSpaceLookupHandler(space, (swapped ? typeA : typeB), CP_WILDCARD_COLLISION_TYPE); // Update the arbiter's state cpArrayPush(space->arbiters, arb); @@ -459,41 +462,100 @@ cpContact *cpContactArrAlloc(int count) return (cpContact *)cpcalloc(count, sizeof(struct cpContact)); } -cpFloat defaultSpringForce(cpDampedSpring *spring, cpFloat dist){ - return (spring->restLength - dist)*spring->stiffness; +cpFloat defaultSpringForce(cpDampedSpring *spring, cpFloat dist) +{ + return (spring->restLength - dist) * spring->stiffness; } -cpFloat defaultSpringTorque(cpDampedRotarySpring *spring, cpFloat relativeAngle){ - return (relativeAngle - spring->restAngle)*spring->stiffness; +cpFloat defaultSpringTorque(cpDampedRotarySpring *spring, cpFloat relativeAngle) +{ + return (relativeAngle - spring->restAngle) * spring->stiffness; } - // // Functions to forward logs (printfs) to python code instead of directly printing to stderr // -//Python side, already formatted message -static void ext_pyLog(const char *formattedMessage); +// Python side, already formatted message +static void ext_pyLog(const char *formattedMessage); -//Chipmunk side, follows existing function declaration +// Chipmunk side, follows existing function declaration void cpMessage(const char *condition, const char *file, int line, int isError, int isHardError, const char *message, ...) { - ext_pyLog((isError ? "Aborting due to Chipmunk error: " : "Chipmunk warning: ")); - + ext_pyLog((isError ? "Aborting due to Chipmunk error: " : "Chipmunk warning: ")); + static char formattedMessage[256]; formattedMessage[0] = 0; va_list vargs; - va_start(vargs, message); { + va_start(vargs, message); + { vsnprintf(formattedMessage, sizeof(formattedMessage), message, vargs); - ext_pyLog(formattedMessage); - } va_end(vargs); - + ext_pyLog(formattedMessage); + } + va_end(vargs); + snprintf(formattedMessage, sizeof(formattedMessage), "\tFailed condition: %s", condition); - ext_pyLog(formattedMessage); - + ext_pyLog(formattedMessage); + snprintf(formattedMessage, sizeof(formattedMessage), "\tSource: %s:%d", file, line); - ext_pyLog(formattedMessage); + ext_pyLog(formattedMessage); +} + +static void DoNothing(cpArbiter *arb, cpSpace *space, cpDataPointer data) {} + +cpFloat +cpPinJointGetImpulse(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsPinJoint(constraint), "Constraint is not a pin joint."); + return ((cpPinJoint *)constraint)->jnAcc; } -static void DoNothing(cpArbiter *arb, cpSpace *space, cpDataPointer data){} \ No newline at end of file +cpFloat +cpSlideJointGetImpulse(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + return ((cpSlideJoint *)constraint)->jnAcc; +} + +cpVect +cpPivotJointGetImpulse(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsPivotJoint(constraint), "Constraint is not a pivot joint."); + return ((cpPivotJoint *)constraint)->jAcc; +} + +cpVect +cpGrooveJointGetImpulse(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsGrooveJoint(constraint), "Constraint is not a groove joint."); + return ((cpGrooveJoint *)constraint)->jAcc; +} + +cpFloat +cpRotaryLimitJointGetImpulse(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsRotaryLimitJoint(constraint), "Constraint is not a rotary limit joint."); + return ((cpRotaryLimitJoint *)constraint)->jAcc; +} + +cpFloat +cpRatchetJointGetImpulse(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsRatchetJoint(constraint), "Constraint is not a ratchet joint."); + return ((cpRatchetJoint *)constraint)->jAcc; +} + +cpFloat +cpGearJointGetImpulse(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsGearJoint(constraint), "Constraint is not a gear joint."); + return ((cpGearJoint *)constraint)->jAcc; +} + +cpFloat +cpSimpleMotorGetImpulse(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsSimpleMotor(constraint), "Constraint is not a simple motor."); + return ((cpSimpleMotor *)constraint)->jAcc; +} diff --git a/pymunk_cffi/extensions_cdef.h b/pymunk_cffi/extensions_cdef.h index 3b4dd441..8cb2f727 100644 --- a/pymunk_cffi/extensions_cdef.h +++ b/pymunk_cffi/extensions_cdef.h @@ -10,7 +10,7 @@ typedef enum pmBatchableBodyFields VELOCITY = 1 << 3, ANGULAR_VELOCITY = 1 << 4, FORCE = 1 << 5, - TORQUE = 1 << 6, + TORQUE = 1 << 6, } pmBatchableBodyFields; typedef enum pmBatchableArbiterFields @@ -104,4 +104,30 @@ cpFloat defaultSpringForce(cpDampedSpring *spring, cpFloat dist); cpFloat defaultSpringTorque(cpDampedRotarySpring *spring, cpFloat relativeAngle); -static void DoNothing(cpArbiter *arb, cpSpace *space, cpDataPointer data); \ No newline at end of file +static void DoNothing(cpArbiter *arb, cpSpace *space, cpDataPointer data); + +/// Get the last impulse applied by this PinJoint. +cpFloat cpPinJointGetImpulse(cpConstraint *joint); + +/// Get the last impulse applied by this SlideJoint. +cpFloat cpSlideJointGetImpulse(cpConstraint *joint); + +/// Get the last impulse applied by this PivotJoint. +cpVect cpPivotJointGetImpulse(cpConstraint *joint); + +/// Get the last impulse applied by this GrooveJoint. +cpVect cpGrooveJointGetImpulse(cpConstraint *joint); + +// DampedSpring and DampedRotarySpring are already covered by the common GetImpulse() function + +/// Get the last impulse applied by this RotaryLimitJoint. +cpFloat cpRotaryLimitJointGetImpulse(cpConstraint *joint); + +/// Get the last impulse applied by this RatchetJoint. +cpFloat cpRatchetJointGetImpulse(cpConstraint *joint); + +/// Get the last impulse applied by this GearJoint. +cpFloat cpGearJointGetImpulse(cpConstraint *joint); + +/// Get the last impulse applied by this SimpleMotor. +cpFloat cpSimpleMotorGetImpulse(cpConstraint *joint); From c62a3d09ee4d883a6915f2182fc8f2e505399763 Mon Sep 17 00:00:00 2001 From: Victor Blomqvist Date: Sun, 23 Nov 2025 23:04:52 +0100 Subject: [PATCH 2/2] Temp change to let whl files be built for jacc branch --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 35ba809c..454969a5 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -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