{"id":37063049,"url":"https://github.com/timabilov/python-play3d","last_synced_at":"2026-01-14T07:01:58.991Z","repository":{"id":45090176,"uuid":"343871520","full_name":"timabilov/python-play3d","owner":"timabilov","description":"Recreated 3D engine \u0026 playground with animations based on 2D.","archived":false,"fork":false,"pushed_at":"2023-11-26T09:22:59.000Z","size":32896,"stargazers_count":20,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-20T17:31:21.530Z","etag":null,"topics":["3d-graphics","3d-models","3d-plot","3d-world","projection-matrix","python-3d"],"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/timabilov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-02T18:23:13.000Z","updated_at":"2025-08-03T07:09:45.000Z","dependencies_parsed_at":"2022-09-04T17:10:18.348Z","dependency_job_id":null,"html_url":"https://github.com/timabilov/python-play3d","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/timabilov/python-play3d","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timabilov%2Fpython-play3d","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timabilov%2Fpython-play3d/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timabilov%2Fpython-play3d/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timabilov%2Fpython-play3d/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timabilov","download_url":"https://codeload.github.com/timabilov/python-play3d/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timabilov%2Fpython-play3d/sbom","scorecard":{"id":885523,"data":{"date":"2025-08-11","repo":{"name":"github.com/timabilov/python-play3d","commit":"6226e246139113ecc57471ed0aa3effcb64d762e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.1,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/python-package.yml:1","Warn: no topLevel permission defined: .github/workflows/python-publish.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/13 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-package.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/timabilov/python-play3d/python-package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-package.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/timabilov/python-play3d/python-package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-publish.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/timabilov/python-play3d/python-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-publish.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/timabilov/python-play3d/python-publish.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/python-package.yml:28","Warn: pipCommand not pinned by hash: .github/workflows/python-package.yml:29","Warn: pipCommand not pinned by hash: .github/workflows/python-package.yml:30","Warn: pipCommand not pinned by hash: .github/workflows/python-publish.yml:20","Warn: pipCommand not pinned by hash: .github/workflows/python-publish.yml:21","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   5 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2018-34 / GHSA-2fc2-6r4j-p65h","Warn: Project is vulnerable to: PYSEC-2021-856 / GHSA-5545-2q6w-2gh6","Warn: Project is vulnerable to: PYSEC-2019-108 / GHSA-9fq2-x9r6-wfmf","Warn: Project is vulnerable to: PYSEC-2018-33 / GHSA-cw6w-4rcx-xphc","Warn: Project is vulnerable to: PYSEC-2021-857 / GHSA-f7c7-j99h-c22f","Warn: Project is vulnerable to: GHSA-fpfv-jqm9-f5jm","Warn: Project is vulnerable to: PYSEC-2017-1 / GHSA-frgw-fgh6-9g52"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T09:53:00.995Z","repository_id":45090176,"created_at":"2025-08-24T09:53:00.995Z","updated_at":"2025-08-24T09:53:00.995Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28412482,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["3d-graphics","3d-models","3d-plot","3d-world","projection-matrix","python-3d"],"created_at":"2026-01-14T07:01:58.342Z","updated_at":"2026-01-14T07:01:58.985Z","avatar_url":"https://github.com/timabilov.png","language":"Python","readme":"3D Playground - Python 3D engine.\n=====================================\n\n![Pygame integration example](example/sm-example.gif)\n\n[![Tests](https://github.com/timabilov/python-play3d/actions/workflows/python-package.yml/badge.svg?event=push)](https://github.com/timabilov/python-play3d/actions/workflows/python-package.yml)\n[![Latest PyPI version](https://img.shields.io/pypi/v/play3d)](https://pypi.python.org/pypi/play3d/)\n#### TL;DR: Basic 3D world playground with animations and [camera](#camera-keys-example) completely from scratch using 2D surface/canvas. \nThis implementation / API only for demonstration and *playground* purposes based on [Perspective projection](https://en.wikipedia.org/wiki/3D_projection#Perspective_projection).     \nCan be used on top of **any** 2d graphics engine/lib(frame buffers, sdl and etc.)\n\nNot implemented features due to low performance:\n* Face clipping not implemented, vertices clipping ignored too    \n* Flat shading and Gouraud shading not implemented.\n* Z-buffering \n\n`models.Model` API is open demonstration of [MVP](https://stackoverflow.com/questions/5550620/the-purpose-of-model-view-projection-matrix) model and is definitely a good starting point/topic for 3D graphics.\n\nAlso you can plot any function on 3D scene.\n\n\n* [Install](#install)\n* [How to use](#how-to-use)\n* [Model View Projection](#model-view-projection)\n  * [Projection](#projection)\n  * [Camera](#world-camera)\n    * [Camera scene example](#camera-keys-example)\n* [Mesh and Wireframe](#mesh-and-wireframe)\n* [Rasterization](#rasterization)\n* [3D Plotting](#3d-plotting) \n* [Basic Wavefront .obj format support](#obj-format)\n* [Model API](#models-api)\n* [Trajectory API](#trajectory-api)\n* [Pygame Example](#pygame-example)\n\n## Install\n\n```\npip install play3d\n```\n\n## How to use\n\nThere is only one requirement - to provide 2D pixel and line renderer(drawer)\n\nAs current example uses `pygame`,\n\n```\npip install pygame==2.0.1 # recommended version\n# You have to install sdl lib separately\n# for Mac OS:\nbrew install sdl2 sdl2_gfx sdl2_image sdl2_mixer sdl2_net sdl2_ttf\n```\n\n```python\nfrom play3d.three_d import Device\nimport pygame\n\n# our adapter will rely on pygame renderer\nput_pixel = lambda x, y, color: pygame.draw.circle(screen, color, (x, y), 1)\n# we certainly can draw lines ourselves using put_pixel three_d.drawline\n# but implementation below - much faster\nline_adapter = lambda p1, p2, color: pygame.draw.line(screen, color, (p1[x], p1[y]), (p2[x], p2[y]), 1)\n\nwidth, height = 1024, 768 # should be same as 2D provider \nDevice.viewport(width, height)\nDevice.set_renderer(put_pixel, line_adapter)\nscreen = pygame.display.set_mode(Device.get_resolution())\n\n```\n\nThat's all we need for setting up environment. \nNow we can create and render model objects by calling `Model.draw()` at each frame update (See [Example](#pygame-example))\\\nTo create model you can simply pass 3D world vertices as 2-d list `Model(data=data)`\n\nIt is possible to provide faces as 2d array  `Model(data=data, faces=faces)`. Face index starts from 1.  Only triangles supported. For more information see below.\n\nSimply by providing 3D (or 4D homogeneous where w=1) `data` vertices list - Model transforms this coordinates from 3D world space to projected screen space\n```python\nfrom play3d.models import Model\n\n# our 2D library renderer setup.. See above.\n\n# Cube model. Already built-in `models.Cube`  \ncube = Model(position=(0, 0, 0),\n                 data=[\n                    [-1, 1, 1, 1],\n                    [1, 1, 1, 1],\n                    [-1, -1, 1, 1],\n                    [1, -1, 1, 1],\n                    [-1, 1, -1, 1],\n                    [1, 1, -1, 1],\n                    [1, -1, -1, 1],\n                    [-1, -1, -1, 1]\n                ])\nwhile True: # your render lib/method\n    cube.draw()\n```\n## Model View Projection\n\n`models.Model` and `three_d.Camera` implements all MVP(See `Model.draw`).\n\n### Projection\n\nHere we use perspective projection matrix\\\nZ axis of clipped cube(from frustum) mapped to [-1, 1] and our camera directed to -z axis (OpenGL convention)\\\nProjection Matrix can be tuned there (aspect ratio, FOV and etc.) \\\n```python\nCamera.near = 1\nCamera.far = 10\nCamera.fov = 60\nCamera.aspect_ratio = 3/4\n```\n\n\n### World camera\n\nBy OpenGL standard we basically move our scene.\nFacing direction considered when we move our camera in case of rotations(direction vector will be transformed too)\\\nCamera can be moved through `three_d.Camera` API:\n```python\nfrom play3d.three_d import Camera\ncamera = Camera.get_instance()\n\n# move camera to x, y, z with 0.5 step considering facing direction\ncamera['x'] += 0.5\ncamera['y'] += 0.5\ncamera['z'] += 0.5\n\ncamera.move(0.5, 0.5, 0.5) # identical above\n\n# rotate camera to our left on XZ plane\ncamera.rotate('y', 2) # \n```\n\n#### Camera keys example\n![Pygame integration example](example/move-around.gif)\n\n## Mesh and Wireframe\n\nTo exploit mesh one should provide both `data` and `faces`. Face represents triple group of vertices index referenced from `data`. Face index starts from 1.\\\nBy default object rendered as wireframe\n```python\n\nfrom play3d.models import Model\ntriangle = Model(position=(-5, 3, -4),\n                 data=[\n                     [-3, 1, -7, 1],\n                     [-2, 2, -7, 1],\n                     [-1, 0, -7, 1],\n                 ], faces=[[1, 2, 3]])\n```\n\n![Triangle wireframe](https://i.imgur.com/A7ktUd7.png)\n\n\n## Rasterization\n\nBy default if data and faces provided, rasterization will be enabled.\\\nFor rasterization we use - standard slope algorithm with horizontal filling lines. \n```python\nfrom play3d.models import Model\n\nwhite = (230, 230, 230)\nsuzanne = Model.load_OBJ('suzanne.obj.txt', position=(-4, 2, -6), color=white, rasterize=True)\nsuzanne_wireframe = Model.load_OBJ('suzanne.obj.txt', position=(-4, 2, -6), color=white)\nsuzanne.rotate(0, -14)\nsuzanne_wireframe.rotate(0, 14)\n```\n\n![Suzanne wireframe and rasterized](https://i.imgur.com/1vVlLt9.png)\n\n## 3D plotting\n\nYou can plot any function you want by providing parametric equation as `func(*parameters) -\u003e [x, y, z]`.\nFor example, sphere and some awesome wave both polar and parametric equations(Sphere built-in as `Models.Sphere`):\n```python\nimport math\nfrom play3d.models import Plot\n\ndef fn(phi, theta):\n\n    return [\n        math.sin(phi * math.pi / 180) * math.cos(theta * math.pi / 180),\n        math.sin(theta * math.pi / 180) * math.sin(phi * math.pi / 180),\n        math.cos(phi * math.pi / 180)\n    ]\n\nsphere_model = Plot(func=fn, allrange=[0, 360], position=(-4, 2, 1), color=(0, 64, 255))\n\nblow_your_head = Plot(\n    position=(-4, 2, 1), color=(0, 64, 255),\n    func=lambda x, t: [x, math.cos(x) * math.cos(t), math.cos(t)], allrange=[0, 2*math.pi], interpolate=75\n)\n\n```\n\n![Plots](https://i.imgur.com/utZexJ5.png)\n\n\n## OBJ format\n\nWawefront format is widely used as a standard in 3D graphics\n\nYou can import your model here. Only vertices and faces supported.\\\n`Model.load_OBJ(cls, path_or_url, wireframe=False, **all_model_kwargs)`\n\nYou can find examples here [github.com/alecjacobson/common-3d-test-models](https://github.com/alecjacobson/common-3d-test-models)\n\nYou might have to normalize(scale and etc.)each `.obj` sample differently \\\nOnly vertices and faces are supported.\n\n```python\nModel.load_OBJ('beetle.obj.txt', wireframe=True, color=white, position=(-2, 2, -4), scale=3)\n```\n\n\n![Beetle object](https://i.imgur.com/79fy4HK.png)\n\n## Models API\n\n`Models.Model`\n\n| Fields | Description    |\n| ------------- | ------------- |\n| `position`  | `tuple=(0, 0, 0)` with x, y, z world coordinates  |\n| `scale`  | `integer(=1)`  |\n| `color`  | `tuple`  `(255, 255, 255)`   |\n| `data`  | `list[[x, y, z, [w=1]]]` - Model vertices(points)  |\n| `faces`  | `list[[A, B, C]]` - Defines triangles See: [Mesh and Wireframe](#mesh-and-wireframe)  |\n| `rasterize`  | `bool(=True)` - Rasterize - \"fill\" an object  |\n| `shimmering`  | `bool(=False)` - color flickering/dancing  |\n\n\n\n```python\n# Initial Model Matrix\nmodel.matrix = Matrix([\n                          [1 * scale, 0, 0, 0],\n                          [0, 1 * scale, 0, 0],\n                          [0, 0, 1 * scale, 0],\n                          [*position, 1]\n                      ])\n\n```\n\n### Methods\n* `model_obj @ translate(x, y, z)`\n  \n  translates object's model matrix (in world space)\n  \n* `rotate(self, angle_x, angle_y=0, angle_z=0)`\n  \n  Rotates object relative to particular axis plane. First object translated from the world space back to local origin, then we rotate the object\n\n* `route(self, trajectory: 'Trajectory', enable_trace=False)` \n  \n  Set the function-based  trajectory routing for the object.\n\n  - trajectory `Trajectory` - trajectory state\n  - enable_trace `bool` - Keep track of i.e. draw trajectory path (breadcrumbs)\n\n## Trajectory API\n\n`Models.Trajectory`\n\n| Fields | Description    |\n| ------------- | ------------- |\n| `func`  | `func` Parametrized math function which takes `*args` and returns world respective coordinates `tuple=(x, y, z)`    |\n\nTo move our object through defined path we can build Trajectory for our object. \nYou can provide any parametric equation with args.\\\nWorld coordinates defined by `func(*args)` tuple output.  \n\n\n#### Example\n```python\nimport math\n\nfrom play3d.models import Sphere, Trajectory\nwhite = (230, 230, 230)\nmoving_sphere = Sphere(position=(1, 3, -5), color=white, interpolate=50)\nmoving_sphere.route(Trajectory.ToAxis.Z(speed=0.02).backwards())\n\nwhirling_sphere = Sphere(position=(1, 3, -5), color=white, interpolate=50)\n# Already built-in as Trajectory.SineXY(speed=0.1)\nwhirling_sphere.route(Trajectory(lambda x: [x, math.sin(x)], speed=0.1))\n\n\nwhile True: # inside your \"render()\"\n    moving_sphere.draw()\n    whirling_sphere.draw()\n```\n## Pygame example\n\n```python\nimport logging\nimport sys\n\nimport pygame\n\nfrom play3d.models import Model, Grid\nfrom pygame_utils import handle_camera_with_keys # custom keyboard handling - moving camera \nfrom play3d.three_d import Device, Camera\nfrom play3d.utils import capture_fps\n\nlogging.basicConfig(stream=sys.stdout, level=logging.INFO)\n\nblack, white = (20, 20, 20), (230, 230, 230)\n\n\nDevice.viewport(1024, 768)\npygame.init()\nscreen = pygame.display.set_mode(Device.get_resolution())\n\n# just for simplicity - array access, we should avoid that\nx, y, z = 0, 1, 2\n\n# pygame sdl line is faster than default one\nline_adapter = lambda p1, p2, color: pygame.draw.line(screen, color, (p1[x], p1[y]), (p2[x], p2[y]), 1)\nput_pixel = lambda x, y, color: pygame.draw.circle(screen, color, (x, y), 1)\n\nDevice.set_renderer(put_pixel, line_renderer=line_adapter)\n\ngrid = Grid(color=(30, 140, 200), dimensions=(30, 30))\n\n# be aware of different scaling of .obj samples. Only vertices and faces supported! \nsuzanne = Model.load_OBJ(\n  'https://raw.githubusercontent.com/OpenGLInsights/OpenGLInsightsCode/master/Chapter%2026%20Indexing%20Multiple%20Vertex%20Arrays/article/suzanne.obj',\n  position=(3, 2, -7), color=white, rasterize=True)\nbeetle = Model.load_OBJ(\n  'https://raw.githubusercontent.com/alecjacobson/common-3d-test-models/master/data/beetle.obj',\n   wireframe=False, color=white, position=(0, 2, -11), scale=3)\nbeetle.rotate(0, 45, 50)\n\ncamera = Camera.get_instance()\n# move our camera up and back a bit, from origin\ncamera.move(y=1, z=2)\n\n\n@capture_fps\ndef frame():\n    if pygame.event.get(pygame.QUIT):\n        sys.exit(0)\n\n    screen.fill(black)\n    handle_camera_with_keys()  # to move our camera - walk, can be ignored\n    grid.draw()\n    beetle.draw()\n    suzanne.rotate(0, 1, 0).draw()\n    pygame.display.flip()\n\n\nwhile True:\n\n    frame()\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimabilov%2Fpython-play3d","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimabilov%2Fpython-play3d","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimabilov%2Fpython-play3d/lists"}