{"id":14065583,"url":"https://github.com/jshaffstall/PyPhysicsSandbox","last_synced_at":"2025-07-29T20:33:22.578Z","repository":{"id":62582517,"uuid":"76296679","full_name":"jshaffstall/PyPhysicsSandbox","owner":"jshaffstall","description":"pyPhysicsSandbox is a simple wrapper around Pymunk that makes it easy to write code to explore 2D physics simulations. It's intended for use in introductory programming classrooms.","archived":false,"fork":false,"pushed_at":"2023-11-29T19:08:47.000Z","size":216,"stargazers_count":49,"open_issues_count":11,"forks_count":9,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-23T05:06:27.403Z","etag":null,"topics":["educational","physics-simulation","python","python3","sandbox-playground"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jshaffstall.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-12-12T21:23:45.000Z","updated_at":"2025-05-09T11:11:49.000Z","dependencies_parsed_at":"2024-01-16T12:46:24.318Z","dependency_job_id":"b472e964-a0c8-4ed1-8db8-0b5b91c4a6cd","html_url":"https://github.com/jshaffstall/PyPhysicsSandbox","commit_stats":{"total_commits":241,"total_committers":5,"mean_commits":48.2,"dds":0.03319502074688796,"last_synced_commit":"d58539c1a2bb7b4b37a9a2dec1b378a361f88223"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/jshaffstall/PyPhysicsSandbox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jshaffstall%2FPyPhysicsSandbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jshaffstall%2FPyPhysicsSandbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jshaffstall%2FPyPhysicsSandbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jshaffstall%2FPyPhysicsSandbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jshaffstall","download_url":"https://codeload.github.com/jshaffstall/PyPhysicsSandbox/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jshaffstall%2FPyPhysicsSandbox/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266724740,"owners_count":23974894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["educational","physics-simulation","python","python3","sandbox-playground"],"created_at":"2024-08-13T07:04:34.520Z","updated_at":"2025-07-29T20:33:22.326Z","avatar_url":"https://github.com/jshaffstall.png","language":"Python","funding_links":[],"categories":["Python","工具与库"],"sub_categories":["游戏，图形与仿真"],"readme":"## Synopsis\n\npyPhysicsSandbox is a simple wrapper around Pymunk that makes it easy to write code to explore 2D physics simulations. It's intended for use in introductory programming classrooms.\n\nCaution! The simulation does not behave well if you start out with shapes overlapping each other, especially if overlapping shapes are connected with joints.  To have overlapping shapes connected by joints, set the group on each shape to the same number to disable collision detection between those shape.\n\nOn the other hand, see the volcano example for a situation where overlapping shapes that collide with each other are useful.\n\nShapes far enough outside the simulation window (generally, above or below by the height of the window, or to either side by the width of the window) are automatically removed from the simulation and their active property set to False.  The distance can be modified, but be wary of making it too large...this keeps shapes that are not visible in the simulation and can slow the simulation down if the number of shapes grows too large.\n\n## Code Example\n\n```python\nfrom pyphysicssandbox import *\n\nwindow(\"My Window\", 400, 300)\ngravity(0.0, 500.0)\n\nb1          = ball((100, 10), 30)\nb1.color    = Color('green')\nb1.friction = 0.25\n\nb2       = static_ball((98, 100), 30)\nb2.color = Color('blue')\n\nbox1       = static_rounded_box((0, 290), 400, 10, 3)\nbox1.color = Color('red')\n\ntri1       = triangle((260, 35), (250, 35), (240, -15))\ntri1.color = Color('red')\n\npoly1       = polygon(((195, 35), (245, 35), (220, -15)))\npoly1.color = Color('blue')\npoly1.wrap  = True\n\nrun()\n\nprint('Done!')\n```\n\n## Motivation\n\nMy introductory programming students love writing physics simulations, but the previous physics engine we used did not expose enough features (pins and motors, for example) to be interesting enough to more advanced students.  PyPhysicsSandbox retains the simplicity needed for intro programming students, but exposes more advanced tools.\n\nAlso, being IDE agnostic, this library can be used with your favorite IDE.\n\n## In Use At\n\nIf you use PyPhysicsSandbox, let me know where and I'll add you here.\n\n[Muskingum University](http://muskingum.edu/) for their Intro to Computer Science course\n\nJohn Glenn High School for their after school coding club\n\n## Summary of Features\n\npyPhysicsSandbox provides an easy Python interface to a rigid-body physics sandbox.  Features include:\n\n### Shapes\n\n* Circles\n* Rectangles\n* Triangles\n* Solid Polygons (both convex and concave)\n* Line Segments\n* Text\n\n### Constraints\n\n* Pivot Joints\n* Pin Joints\n* Motors\n* Slip Motors\n* Springs\n* Gears\n\n### Other\n\n* User Specified Collision Handlers\n* User Specified Observer Functions\n* Disable Collisions Between Specific Objects\n* Custom Shape Properties - color, friction, gravity, damping, elasticity\n* Set shapes to constant velocities\n* Allow Shapes to Wrap Around the Screen\n* Conveyor Belt Like Behavior\n* Pasting One Shape Onto Another - so they behave as one shape\n* Hit shapes in a specific direction with a given force\n* Handles Thousands of Shapes\n* Built in debug output that can be turned on for individual shapes\n\n## Tutorials\n\nScreencasts highlighting various features of the sandbox are available on the [PyPhysicsSandbox YouTube channel](https://www.youtube.com/channel/UCybNk1XwGtiPyiLVitMFmsQ)\n\n## Installation\n\n### Python 3\n\nhttps://www.python.org/\n\nThis library was written with Python 3.5, but should run on any Python 3.  Python 3 must be installed first.\n\n### pyPhysicsSandbox\n\nGiven a suitable Python 3 installation, you should be able to install pyPhysicsSandbox by opening a command prompt in the Scripts folder of your Python installation and typing:\n\n```\npip install pyphysicssandbox\n```\n\n### Dependencies\n\nhttp://www.pygame.org/\nhttp://www.pymunk.org/\n\nBoth pygame and pymunk should be automatically installed when you install pyPhysicsSandbox.  If something goes wrong and you need to install them manually, see their respective sites for directions.\n\n## API Reference\n\n### Simulation-wide functions\n\n```python\nwindow(caption, width, height)\n```\n\nSpecifies the width and height and caption of the simulation window.  Multiple calls to this overwrite the old values.  You only get one window regardless.\n\n```python\nset_margins(x, y)\n```\n\nSets the minimum distance outside the visible window a shape can be and still be in the simulation.  Outside of this distance the shape is deactivated.  The default x margin is the window's width and the default y margin is the window's height.\n\nNote that if you create a shape and give it an initial position outside these margins, the simulation will expand the margins to include the shape.\n\nUse set_margins to increase the y margin particularly if you expect a shape on screen to be fired high above the top of the screen.\n\n```python\ncolor(v)\n```\n\nSets the default color for shapes drawn after this function is called.  Color must be a string containing a valid color name.\n\nSee https://sites.google.com/site/meticulosslacker/pygame-thecolors for a list of colors. Hover your mouse over a color to see its name.\n\n```python\ngravity(x, y)\n```\n\nSets the gravity of the simulation.  Positive y is downward, positive x is rightward.  Default is (0, 500).\n\n```python\nresistance(v)\n```\n\nSets how much velocity each object in the simulation keeps each second.  Must be a floating point number.  Default is 0.95.  Values higher than 1.0 cause objects to increase in speed rather than lose it.  A value of 1.0 means objects will not lose any velocity artificially.   \n\n```python\nadd_observer(observer_func)\n```\n\nProvide a function of yours that will get called once per frame.  In this function you can use the various objects you've created to either affect the simulation or simply measure something.\n\nYou may call add_observer multiple times to add different observer functions.\n\nThe function should be defined like this:\n\n```python\n        def function_name(keys):\n            # do something each time step\n```\n\nThe observer function must take a single parameter which is a\nlist of keys pressed this step.  To see if a particular key has\nbeen pressed, use something like this:\n    \n```python\n            if constants.K_UP in keys:\n                # do something based on the up arrow being pressed\n```\n\n```python\nmouse_clicked ()\n```\n\nReturns True if the mouse has been clicked this time step. Usable only in an observer function.\n\n```python\nmouse_point ()\n```\n\nReturns the current location of the mouse pointer as an (x, y) tuple.\n\nIf the mouse is out of the simulation window, this will return the last location of the mouse that was in the simulation window.\n\n```python\nnum_shapes()\n```\n\nReturns the number of active shapes in the simulation.  Mostly useful for debugging.\n\n```python\ndeactivate(shape)\n```\n\nRemoves the given shape from the simulation.\n\n```python\nreactivate(shape)\n```\n\nAdds the given shape back to the simulation.\n\n```python\nadd_collision(shape1, shape2, handler)\n```\n\nTells the sandbox to call a function when the two given shapes collide. The handler function is called once per collision, at the very start of the collision.\n\nThe handler function is passed three parameters. The first two are the colliding shapes, the third is the point of the collision, e.g.:\n\n```python\n        handler(shape1, shape2, p)\n```\n\nThe handler function must return True to allow the collision to happen.  If the handler returns False, then the collision will not happen.\n\nNote that you will never have a collision with a deactivated object or with a cosmetic object.\n\n```python\nrun(do_physics=True)\n```\n\nCall this after you have created all your shapes to actually run the simulation.  This function returns only when the user has closed the simulation window.\n\nPass False to this method to do the drawing but not activate physics.  Useful for getting the scene right before running the simulation.\n\n```python\ndraw()\n```\n\nCall this after you have created all your shapes to draw the shapes.  This function returns only when the user has closed the window.\n\nThis is an alias for run(False).\n\n### Shape creation functions\n\nAll the shapes have both a static and cosmetic variation shown.\n\nStatic shapes will interact with the physics simulation but will never move.  Other shapes will collide with the static shapes, but the static shapes are immovable objects.\n\nCosmetic shapes also will never move, but they also do not interact with the physics simulation in any way.  Other shapes will fall through the cosmetic shapes.  This means you may not also use a cosmetic shape as part of a paste_on call.\n\n```python\nball(p, radius, mass)\nstatic_ball(p, radius)\ncosmetic_ball(p, radius)\n```\n\nCreate a ball object and return its instance.\n\np is a tuple containing the x and y coordinates of the center of the ball.  \n\nYou can omit the mass parameter and the mass will be set proportional to the area of the shape.\n\n```python\nbox(p, width, height, mass)\nstatic_box(p, width, height)\ncosmetic_box(p, width, height)\n```\n\nCreate a box object and return its instance.\n\np is a tuple containing the x and  y coordinates of the upper left corner of the box.\n\nYou can omit the mass parameter and the mass will be set proportional to the area of the shape.\n\n```python\nrounded_box(p, width, height, radius, mass)\nstatic_rounded_box(p, width, height, radius)\ncosmetic_rounded_box(p, width, height, radius)\n```\n\nCreate a box object and returns its instance. These boxes are drawn with rounded corners.\n\np is a tuple containing the x and  y coordinates of the upper left corner of the box.\nradius is the radius of the corner curve.  3 works well, but you can pass any integer.\n\nYou can omit the mass parameter and the mass will be set proportional to the area of the shape.\n\n```python\ntriangle(p1, p2, p3, mass)\nstatic_triangle(p1, p2, p3)\ncosmetic_triangle(p1, p2, p3)\n```\n\nCreates a triangle out of the given points and returns its instance.\n\nYou can omit the mass parameter and the mass will be set proportional to the area of the shape.\n\n```python\npolygon(vertices, mass)\nstatic_polygon(vertices)\ncosmetic_polygon(vertices)\n```\n\nCreates a closed polygon out of the given points and returns its instance.  The last point is automatically connected back to the first point.\n\nvertices is a tuple of points, where each point is a tuple of x and y coordinates.  The order of these points matters!\n\nYou can omit the mass parameter and the mass will be set proportional to the area of the shape.\n\n```python\ntext(p, caption, mass)\nstatic_text(p, caption)\ncosmetic_text(p, caption)\ntext_with_font(p, caption, font, size, mass)\nstatic_text_with_font(p, caption, font, size)\ncosmetic_text_with_font(p, caption, font, size)\n```\n\nCreates text that will interact with the world as if it were a rectangle.\n\np is a tuple containing the x and  y coordinates of the upper left corner of the text.\n\nYou can omit the mass parameter and the mass will be set proportional to the area of the shape.\n\nTo change the text set the object to a variable and use the `.text(\"new text here\")` function.\n\n```python\nline(p1, p2, thickness, mass)\nstatic_line(p1, p2, thickness)\ncosmetic_line(p1, p2, thickness)\n```\n\nCreates a line from coordinates p1 to coordinates p2 of the given thickness.\n\nYou can omit the mass parameter and the mass will be set proportional to the area of the shape.\n\n### Constraints\n\nConstraints will limit or control the motion of other shapes in some fashion.\n\n```python\npivot1 = pivot(p)\npivot1.connect(other_shape)\n```\n\nCreate a pivot joint at point p in the world.  The other_shape should be a shape whose coordinates intersect the location of the pivot joint.  \n\nThe pivot joint pins the other shape to the background, not allowing it to fall.  The other shape can rotate around the pivot joint.\n \n```python\ngear1 = gear(shape1, shape2)\n```\n\nCreates a gear joint connecting the two shapes.  A gear joint keeps the angle of the two shapes constant.  As one shape rotates, the other rotates to match automatically.\n\nNote that the gear has no visible representation in the simulation.\n\n```python\nmotor(shape1, speed)\n```\n\nCreates a motor to give the shape a constant rotation. The direction of rotation is controlled by the sign of the speed.  Positive speed is clockwise, negative speed is counter-clockwise.\n\nIf you want other shapes to also rotate at the same rate, use a gear joint to connect them to the shape with the motor.\n\nThe motor displays as a semicircle with a dot in the direction of rotation.\n\n```python\nspring(p1, shape1, p2, shape2, length, stiffness, damping)\n```\n\nCreates a spring that connects two shapes at the given points.  The spring wants to remain at the given length, but forces can make it be longer or shorter temporarily. \n\n```python\nrotary_spring(shape1, shape2, angle, stiffness, damping)\n```\n\nCreates a spring that constrains the rotations of the given shapes. The angle between the two shapes prefers to be at the given angle, but may be varied by forces on the objects. The spring will bring the objects back to the desired angle.  The initial positioning of the shapes is considered to be at an angle of 0.\n\nA normal scenario for this is for shape1 to be a shape rotating around shape2, which is a pivot joint or other static object, but play around with different ways of using rotary springs.\n\n```python\nslip_motor(shape1, shape2, rest_angle, stiffness, damping, slip_angle, speed)\n```\n\nCreates a combination spring and motor.  The motor will rotate shape1 around shape2 at the given speed.  When shape1 reaches the slip angle it will spring back to the rest_angle.  Then the motor will start to rotate the object again.\n \n```python\npin((100, 580), ball1, (150, 580), ball2)\n```\n\nCreates a pin joint between the two shapes at the given points.  A pin joint creates a fixed separation between the two bodies (as if there were a metal pin connecting them).  You'll get strange effects when wrapping these shapes.\n\n###Shape Methods and Properties\n\nEach shape object that gets returned has some methods and properties that can be called to adjust the shape.  \n\n```python\nshape.debug=True\n```\n\nTurns on debug output for the given shape.  Each time step the shape will print out information about its current location and other pertinent characteristics.\n\n```python\nshape.hit(direction, position)\n```\n\nHits the shape at the given position in the given direction.  This is an instantaneous impulse.\n\nDirection is a tuple containing the x direction and y direction (in the same orientation as the gravity tuple).\n\nPosition is a tuple containing the x and y position of the spot on the shape to hit.\n\n```python\nshape.color=Color('blue')\n```\n\nSets the color for the shape.  The value must be a pygame Color instance.  The default color is black.\n\n```python\nshape.angle=90\n```\n\nSets the angle for the shape.  Can be used to start shapes off rotated.\n\n```python\nshape.elasticity=0.0\n```\n\nSets how bouncy the object is.  The default is 0.9.\n\n```python\nshape.friction=0.95\n```\n\nSets how much friction the object should have.  The default is 0.6.  The Wikipedia article on friction has examples of values for different materials: https://en.wikipedia.org/wiki/Friction\n\n```python\nshape.velocity=(200,0)\n```\n\nSets a constant velocity for the shape.  The shape will still interact with other shapes, but will always move in the given direction.\n\nTo disable a constant velocity and return the shape to reacting to gravity normally, set the velocity to None.\n\n```python\nshape.surface_velocity=(200,0)\n```\n\nSets how much surface velocity the object should have.  The default is (0, 0).  \n \nThis is the amount of movement objects touching this surface will have imparted to them.  You can use this to set up a conveyor belt.  \n\n```python\nshape.wrap=True\n```\n\nSets whether the shape should wrap when going off the edges of the screen or not.  A True value means the shape can never be off screen, and if it starts off screen it's immediately brought on as if it were wrapping.\n \nThis is a convenience function for setting wrap_x and wrap_y at the same time. \n\n```python\nshape.wrap_x=True\n```\n\nSets whether the shape should wrap when going off the sides of the screen or not. \n\n```python\nshape.wrap_y+True\n```\n\nSets whether the shape should wrap when going off the top or bottom of the screen or not. \n\n```python\nshape.visible=False\n```\n\nSets whether the shape draws itself or not.  Defaults to True.  Most useful to set this to False for joints you don't want shown on screen. \n\n```python\nshape.group=1\n```\n\nSet to an integer.  Shapes that share the same group number will not collide with each other.  Useful to have overlapping objects connected by joints that do not make the physics crazy. \n\n```python\nshape.gravity=(0,-300)\n```\n\nSet to an (x, y) vector in the same format as the overall gravity vector.  This overrides the overall gravity for this shape only.\n\n```python\nshape.damping=0.9\n```\n\nSet a damping value specific for this shape.  This overrides the overall damping value for this shape only.\n\n```python\nshape.paste_on(other_shape)\n```\n\nPaste one shape onto another shape.  The coordinates for the shape must be inside that of the other_shape and their group must be set to the same value to disable collision detection between them.\n  \nThis can be used, for example, to draw some text inside a shape. \n\nThis is only suitable for calling on actual shapes!  The various joints already attach themselves to objects.\n\n```python\nshape.inside(p)\n```\n\nReturns True is the given point is inside the given shape.  Does not care if the shape is visible or not or active or not.\n\n```python\nshape.draw_radius_line=True\n```\n\nOnly for balls, this sets whether a line from the center of the ball to the 0 degree point on the outer edge is drawn.  Defaults to False.  Can be set to True to gauge rotation of the ball. \n\n```python\nshape.text=\"Some text\"\n```\n\nOnly for text, this sets the text to be displayed.  This will modify the box shape around the text for collision detection. \n\n## Contributors\n\nSee CONTRIBUTING.md\n\n## License\n\nSee LICENSE.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjshaffstall%2FPyPhysicsSandbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjshaffstall%2FPyPhysicsSandbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjshaffstall%2FPyPhysicsSandbox/lists"}