More pymunk explorations #763
Replies: 3 comments 9 replies
-
|
@hx2A I have tried moving the classes to another file for importing and something weird is going on... now I'm feeling very stupid. I'll see if I can prepare a minimal example to reproduce the problem. EDIT: Removed outdated code ... see comments bellow for a more recent version. |
Beta Was this translation helpful? Give feedback.
-
|
So, I'm still working on the importable Today I struggled to add two constraints/joints, From https://github.com/villares/sketch-a-day/blob/main/2026/sketch_2026_04_11/ : # This is a py5 "module mode" sketch
# learn about py5 modes at https://py5coding.org
"""
Press " " (space bar) to create many balls
Press "w" to drag the mouse and create a static segment (wall)
Press "b" to drag the mouse and create a box
Press "k" to drag the mouse and create a kinematic box
Press "p" to drag the mouse and create a polygon
Press <delete> or <backspace> to remove objects under the mouse
Press "c" to remove dynamic objects
You can drag kinematic objects (and nudge a bit the dynamic ones)
You can use the mouse wheel to rotate some objects
"""
import py5
import shapely
from pymunk_helpers import Simulation, SObj, Constraint
from pymunk_helpers import Segment, Ball, Box, Poly, PivotJoint, PinJoint
ongoing_creation = None
def setup():
py5.size(600, 600)
global sim
sim = Simulation(gravity=(0,0))
#
Segment(50, 550, 550, 550, static=True)
Segment(50, 50, 550, 50, static=True)
Segment(50, 50, 50, 550, fill_color=128, kinematic=True)
Segment(550, 50, 550, 550, fill_color=128, kinematic=True)
ba = Ball(100, 100, 50)
bb = Ball(200, 50, 100)
PinJoint(ba, bb, anchor_a=(100, 120))
sa = Segment(300, 100, 400, 100, fill_color=255, radius=20)
sb = Segment(400, 100, 500, 200, fill_color=255, radius=20)
PivotJoint(sa, sb, (400, 100))
def draw():
py5.background(200)
sim.draw_and_update(step=1/60) # draw and clean up
# create many balls
if py5.is_key_pressed and py5.key == " ":
d = py5.random(10, 50)
c = py5.color(d * 5, 0, 255 - d * 5)
Ball(py5.mouse_x + py5.random(-1, 1), py5.mouse_y, d, c)
# preview & object creation
py5.no_fill()
py5.stroke_weight(2)
py5.stroke(255)
x, y = py5.mouse_x, py5.mouse_y
match ongoing_creation:
case pts, "p":
# poly creation ongoing!
with py5.begin_shape():
py5.vertices(pts)
py5.vertex(x, y)
if (len(pts) == 0) or py5.dist(x, y, *pts[-1]) > 20:
pts.append((x, y))
case sx, sy, "w":
with py5.push_style(), py5.begin_shape():
py5.line(sx, sy, x, y)
case sx, sy, "b" | "k":
py5.rect_mode(py5.CORNERS)
py5.rect(sx, sy, x, y)
def key_pressed():
if py5.key in (py5.BACKSPACE, py5.DELETE):
for obj in sim:
if obj.under_mouse():
obj.remove_from_sim()
elif py5.key == "c":
for obj in sim:
if isinstance(obj, Constraint) or obj.body.body_type not in (sim.STATIC, sim.KINEMATIC):
obj.remove_from_sim()
elif py5.key == 's':
py5.save_frame('###.png')
def mouse_pressed():
global ongoing_creation
if py5.key == "k" and py5.is_key_pressed:
ongoing_creation = (py5.mouse_x, py5.mouse_y, "k")
elif py5.key == "b" and py5.is_key_pressed:
ongoing_creation = (py5.mouse_x, py5.mouse_y, "c")
elif py5.key == "W" and py5.is_key_pressed:
ongoing_creation = (py5.mouse_x, py5.mouse_y, "W")
elif py5.key == "w" and py5.is_key_pressed:
ongoing_creation = (py5.mouse_x, py5.mouse_y, "w")
elif py5.key == "p" and py5.is_key_pressed:
ongoing_creation = ([], "p")
def mouse_released():
global ongoing_creation
mx, my = py5.mouse_x, py5.mouse_y
match ongoing_creation:
case sx, sy, "w":
sim.add_kinematic_segment(sx, sy, mx, my)
case sx, sy, "W":
sim.add_segment(sx, sy, mx, my, radius=20)
case sx, sy, box_type:
w, h = abs(sx - mx), abs(sy - my)
x, y = (sx + mx) / 2, (sy + my) / 2
Box(x, y, w, h, kinematic=(box_type == "k"))
case pts, "p":
shapely_poly = shapely.Polygon(pts) if len(pts) >= 3 else shapely.Polygon()
if shapely_poly.area > 100 and shapely_poly.is_simple:
Poly(shapely_poly, fill_color=py5.color(255))
ongoing_creation = None
def mouse_dragged():
dx, dy = py5.mouse_x - py5.pmouse_x, py5.mouse_y - py5.pmouse_y
if not py5.is_key_pressed:
for obj in sim:
if obj.under_mouse():
if obj.type == sim.KINEMATIC:
obj.translate(dx, dy)
if obj.type == sim.DYNAMIC:
obj.vel_update(dx, dy)
def mouse_wheel(e):
for obj in sim:
if obj.under_mouse() and isinstance(obj, SObj):
obj.rotate(py5.radians(e.get_count()))
py5.run_sketch(block=False) |
Beta Was this translation helpful? Give feedback.
-
|
Today I updated the
class Poly(SObj):
"""
A simulated polygonal, Kinematic or Dynamic, custom object.
It can be created from a sequence of vertices, or shapely [Multi]Polygons.
"""
def __init__(self, geometry:
Sequence[Sequence] | shapely.Polygon | shapely.MultiPolygon,
fill_color=None,
kinematic=False,
simulation=None):
super().__init__(simulation)
if isinstance(geometry, shapely.Polygon):
cx, cy = geometry.centroid.x, geometry.centroid.y
geometry = shapely.affinity.translate(geometry, -cx, -cy)
polygons = (geometry,)
elif isinstance(geometry, shapely.MultiPolygon):
cx, cy = geometry.centroid.x, geometry.centroid.y
geometry = shapely.affinity.translate(geometry, -cx, -cy)
polygons = tuple(geometry.geoms)
else:
geometry = shapely.Polygon(geometry)
cx, cy = geometry.centroid.x, geometry.centroid.y
geometry = shapely.affinity.translate(geometry, -cx, -cy)
polygons = (geometry,)
self.fill_color = fill_color or py5.color(255)
self.geometry = geometry
self.py5shape = py5.convert_shape(geometry)
self.py5shape.disable_style()
self.shapes = []
triangles = [] # the translated to origin triangles
for poly in polygons:
vs, faces = triangulate_polygon(poly)
triangles.extend((tuple(vs[a]), tuple(vs[b]), tuple(vs[c]))
for a, b, c in faces)
if kinematic:
self.body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
else:
mass = geometry.area * self.simulation.mass_scale
moment = sum(
pymunk.moment_for_poly(mass / len(triangles), tri, (0, 0))
for tri in triangles
)
self.body = pymunk.Body(mass, moment)
for tri in triangles:
shape = pymunk.Poly(self.body, tri)
shape.friction = self.friction
shape.elasticity = self.elasticity
self.shapes.append(shape)
self.body.position = (cx, cy)
self.register_object()
def draw(self):
s = self.simulation.current_sketch
s.fill(self.fill_color)
s.no_stroke()
if self.py5shape is None:
self.py5shape = py5.convert_shape(self.geometry)
with s.push_matrix():
s.translate(self.body.position.x, self.body.position.y)
s.rotate(self.body.angle)
s.shape(self.py5shape)
# trigger removal of falling objects... might be improved
if self.body.position.y > s.height + 200:
self.remove_from_sim() |
Beta Was this translation helpful? Give feedback.


Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I think that the first time I saw pymunk was thanks to some post by @tabreturn. So much fun!
From time to time I try to put my students to use it, but there is a lot of complexity to deal with. Along the years I have always tought of ways of making it simpler to draw the simulations, but nothing really worked very well. Talking to @ramalho, he suggested a "wrapper class" approach, and I now think I have something of a proof of concept!
I even tried my hand at a tiny little bit of meta-programming to help add factory methods ... that stuff might be"hidden" if this becomes a library. Maybe using
match/caseon the main sketch part could be a bit excessive for beginners, I'm not sure. This is work in progress.This depends on pymunk, shapely, trimesh and trimesh's "optional dependency" mapbox_earcut.
If
python -m pip install pymunk trimesh[easy]doesn't work, then trypython -m pip install trimesh shapely mapbox_earcut.Beta Was this translation helpful? Give feedback.
All reactions