pyforge3d 2.1.1__tar.gz → 2.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/PKG-INFO +100 -77
  2. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/README.md +84 -59
  3. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/pyproject.toml +21 -21
  4. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/__init__.py +3 -2
  5. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/app.py +43 -25
  6. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/camera.py +181 -0
  7. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/character.py +103 -4
  8. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/collision/detection.py +103 -7
  9. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/collision/gjk.py +2 -2
  10. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/collision/heightfield.py +91 -0
  11. pyforge3d-2.2.1/src/forge3d/ecs/component.py +102 -0
  12. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ecs/system.py +14 -2
  13. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/facade.py +531 -30
  14. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/input.py +123 -3
  15. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/realtime/meshes.py +190 -0
  16. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/realtime/window_renderer.py +47 -24
  17. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/sim/world.py +4 -0
  18. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/viewer.py +60 -9
  19. pyforge3d-2.1.1/src/forge3d/ecs/component.py +0 -56
  20. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/Cargo.lock +0 -0
  21. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/Cargo.toml +0 -0
  22. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/LICENSE +0 -0
  23. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/_core.pyi +0 -0
  24. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/animation/__init__.py +0 -0
  25. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/animation/clip.py +0 -0
  26. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/animation/ik_fabrik.py +0 -0
  27. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/animation/player.py +0 -0
  28. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/animation/skeleton.py +0 -0
  29. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/animation/system.py +0 -0
  30. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/audio/__init__.py +0 -0
  31. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/audio/clip.py +0 -0
  32. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/audio/null_driver.py +0 -0
  33. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/audio/openal_driver.py +0 -0
  34. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/audio/source.py +0 -0
  35. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/audio/system.py +0 -0
  36. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/backend.py +0 -0
  37. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/collision/__init__.py +0 -0
  38. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/collision/epa.py +0 -0
  39. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/collision/layers.py +0 -0
  40. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/collision/raycast.py +0 -0
  41. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/constraints/__init__.py +0 -0
  42. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/constraints/base.py +0 -0
  43. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/constraints/joint_type.py +0 -0
  44. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/constraints/joints.py +0 -0
  45. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/contact/__init__.py +0 -0
  46. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/contact/solver.py +0 -0
  47. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/dynamics/__init__.py +0 -0
  48. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/dynamics/aba.py +0 -0
  49. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/dynamics/crba.py +0 -0
  50. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/dynamics/model.py +0 -0
  51. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/dynamics/rnea.py +0 -0
  52. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ecs/__init__.py +0 -0
  53. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ecs/bridge.py +0 -0
  54. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ecs/entity.py +0 -0
  55. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ecs/serialization.py +0 -0
  56. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ecs/transform.py +0 -0
  57. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/editor/__init__.py +0 -0
  58. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/editor/editor_app.py +0 -0
  59. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/editor/gizmo.py +0 -0
  60. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/editor/layout.py +0 -0
  61. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/errors.py +0 -0
  62. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/events.py +0 -0
  63. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/io/__init__.py +0 -0
  64. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/io/mesh_data.py +0 -0
  65. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/io/obj_loader.py +0 -0
  66. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/io/world_snapshot.py +0 -0
  67. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/logging.py +0 -0
  68. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/math/__init__.py +0 -0
  69. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/math/inertia.py +0 -0
  70. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/math/quaternion.py +0 -0
  71. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/math/se3.py +0 -0
  72. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/math/spatial.py +0 -0
  73. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/model/__init__.py +0 -0
  74. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/model/kinematics.py +0 -0
  75. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/model/robot_config.py +0 -0
  76. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/model/urdf_loader.py +0 -0
  77. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/particle/__init__.py +0 -0
  78. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/particle/emitter.py +0 -0
  79. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/particle/presets.py +0 -0
  80. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/particle/system.py +0 -0
  81. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/profiler.py +0 -0
  82. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/py.typed +0 -0
  83. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/recorder.py +0 -0
  84. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/__init__.py +0 -0
  85. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/base.py +0 -0
  86. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/deferred/__init__.py +0 -0
  87. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/deferred/renderer.py +0 -0
  88. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/hq/__init__.py +0 -0
  89. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/hq/raytracer.py +0 -0
  90. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/hq/renderer.py +0 -0
  91. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/hq/scene.py +0 -0
  92. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/passes/__init__.py +0 -0
  93. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/passes/base.py +0 -0
  94. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/realtime/__init__.py +0 -0
  95. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/realtime/context.py +0 -0
  96. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/realtime/renderer.py +0 -0
  97. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/realtime/shaders.py +0 -0
  98. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/bloom_down.frag +0 -0
  99. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/bloom_up.frag +0 -0
  100. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/fullscreen.vert +0 -0
  101. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/gbuffer.frag +0 -0
  102. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/gbuffer.vert +0 -0
  103. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/lighting.frag +0 -0
  104. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/pbr.wgsl +0 -0
  105. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/shadow.frag +0 -0
  106. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/shadow.vert +0 -0
  107. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/ssao.frag +0 -0
  108. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/ssao_blur.frag +0 -0
  109. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/tonemap.frag +0 -0
  110. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/shaders/update_particles.comp +0 -0
  111. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/snapshot.py +0 -0
  112. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/wgpu_backend/__init__.py +0 -0
  113. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/wgpu_backend/pipeline.py +0 -0
  114. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/render/wgpu_backend/renderer.py +0 -0
  115. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/robot/__init__.py +0 -0
  116. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/robot/presets.py +0 -0
  117. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/robot/robot.py +0 -0
  118. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/scene/__init__.py +0 -0
  119. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/scene/manager.py +0 -0
  120. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/scene/node.py +0 -0
  121. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/scene/prefab.py +0 -0
  122. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/sim/__init__.py +0 -0
  123. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/sim/domain_rand.py +0 -0
  124. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/sim/jax_batch.py +0 -0
  125. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ui/__init__.py +0 -0
  126. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ui/backend.py +0 -0
  127. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ui/canvas.py +0 -0
  128. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ui/panels.py +0 -0
  129. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d/ui/system.py +0 -0
  130. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d_core/Cargo.toml +0 -0
  131. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d_core/benches/physics_bench.rs +0 -0
  132. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d_core/src/bvh.rs +0 -0
  133. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d_core/src/gjk_epa.rs +0 -0
  134. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d_core/src/lib.rs +0 -0
  135. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d_core/src/math_simd.rs +0 -0
  136. {pyforge3d-2.1.1 → pyforge3d-2.2.1}/src/forge3d_core/src/pgs_solver.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyforge3d
3
- Version: 2.1.1
3
+ Version: 2.2.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Intended Audience :: Developers
@@ -16,35 +16,33 @@ Classifier: Topic :: Scientific/Engineering :: Physics
16
16
  Classifier: Topic :: Scientific/Engineering :: Visualization
17
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Classifier: Typing :: Typed
19
- Requires-Dist: numpy>=1.26
19
+ Requires-Dist: numpy>=2.0
20
20
  Requires-Dist: scipy>=1.12
21
21
  Requires-Dist: sympy>=1.13
22
22
  Requires-Dist: jax[cpu]>=0.4.25
23
- Requires-Dist: pyforge3d[render,rl,dev] ; extra == 'all'
23
+ Requires-Dist: moderngl>=5.10
24
+ Requires-Dist: glfw>=2.5
25
+ Requires-Dist: pillow>=10.0
26
+ Requires-Dist: imageio>=2.34
27
+ Requires-Dist: imageio-ffmpeg>=0.5
28
+ Requires-Dist: soundfile>=0.12
29
+ Requires-Dist: gymnasium>=0.29
30
+ Requires-Dist: stable-baselines3>=2.2
31
+ Requires-Dist: optax>=0.2
32
+ Requires-Dist: matplotlib>=3.8
33
+ Requires-Dist: pyforge3d[experimental,dev,docs] ; extra == 'all'
24
34
  Requires-Dist: pytest>=8.0 ; extra == 'dev'
25
35
  Requires-Dist: pytest-cov>=5.0 ; extra == 'dev'
26
36
  Requires-Dist: ruff>=0.4 ; extra == 'dev'
27
37
  Requires-Dist: mypy>=1.9 ; extra == 'dev'
28
- Requires-Dist: moderngl>=5.10 ; extra == 'dev'
29
- Requires-Dist: glfw>=2.5 ; extra == 'dev'
30
- Requires-Dist: pillow>=10.0 ; extra == 'dev'
31
- Requires-Dist: imageio>=2.34 ; extra == 'dev'
32
- Requires-Dist: imageio-ffmpeg>=0.5 ; extra == 'dev'
33
38
  Requires-Dist: mkdocs-material>=9.5 ; extra == 'docs'
34
39
  Requires-Dist: mkdocstrings[python]>=0.25 ; extra == 'docs'
35
- Requires-Dist: moderngl>=5.10 ; extra == 'render'
36
- Requires-Dist: glfw>=2.5 ; extra == 'render'
37
- Requires-Dist: pillow>=10.0 ; extra == 'render'
38
- Requires-Dist: imageio>=2.34 ; extra == 'render'
39
- Requires-Dist: imageio-ffmpeg>=0.5 ; extra == 'render'
40
- Requires-Dist: gymnasium>=0.29 ; extra == 'rl'
41
- Requires-Dist: stable-baselines3>=2.2 ; extra == 'rl'
42
- Requires-Dist: optax>=0.2 ; extra == 'rl'
40
+ Requires-Dist: wgpu>=0.19 ; extra == 'experimental'
41
+ Requires-Dist: rendercanvas>=2.0 ; extra == 'experimental'
43
42
  Provides-Extra: all
44
43
  Provides-Extra: dev
45
44
  Provides-Extra: docs
46
- Provides-Extra: render
47
- Provides-Extra: rl
45
+ Provides-Extra: experimental
48
46
  License-File: LICENSE
49
47
  Summary: Pure-Python 3D physics game engine — own dynamics, own rules, no compromises.
50
48
  Keywords: game-engine,physics,simulation,3d,rigid-body,collision,rendering,robotics,reinforcement-learning,numpy,jax
@@ -92,17 +90,26 @@ all solved by forge3d's own code, backed by NumPy and optional JAX acceleration.
92
90
  | Category | Details |
93
91
  |---|---|
94
92
  | **Game loop** | `App` class — `@on_start` / `@on_update` / `@on_render` decorators |
95
- | **Input** | `Input` snapshot — `key_held`, `key_pressed`, `mouse_pos`, `scroll_delta` |
96
- | **Camera** | `OrbitCamera`, `FollowCamera` — orbit, zoom, pan, smooth follow |
93
+ | **Input** | `Input` snapshot — `key_held`, `key_pressed`, `axis()`, `mouse_pos`, `scroll_delta` |
94
+ | **Camera** | `OrbitCamera` (with `handle_input()`), `FollowCamera` — orbit, zoom, pan, smooth follow |
95
+ | **Character** | `CharacterController` — `move()`, `jump()`, `glide()`, `move_camera_relative()` |
97
96
  | **Physics** | Rigid-body dynamics (RNEA / CRBA / ABA), semi-implicit Euler integrator |
98
- | **Collision** | SAT OBB-OBB (15-axis), sphere, capsule, GJK/EPA convex-mesh; AABB broad-phase |
99
- | **Contact** | Impulse solver, Coulomb friction, Baumgarte correction, warm-starting |
100
- | **Grasping** | `weld` / `release` kinematic constraints; friction-based pinch grasp |
97
+ | **Collision** | SAT OBB-OBB (15-axis), sphere, capsule, GJK/EPA convex-mesh; AABB broad-phase; BVH (Rust) |
98
+ | **Contact** | Impulse-based PGS solver, Coulomb friction, Baumgarte stabilization |
99
+ | **Joints** | Hinge, Ball, Prismatic, Fixed, Distance, Spring; `weld` / `release` |
100
+ | **Terrain** | Heightfield collision + rendering (32×32 to 512×512) |
101
101
  | **Robots** | UR5 6-DOF FK/IK, DH parameters, Jacobian |
102
- | **Rendering** | Real-time PBR (Cook-Torrance, PCF shadows, albedo/normal textures); HQ software ray-tracer |
103
- | **RL** | Gymnasium-compatible `ReachEnv` / `PickPlaceEnv` |
104
- | **Performance** | JAX JIT+vmap: 2 000× throughput vs. NumPy; AABB broad-phase O(n log n) |
105
- | **Testing** | 215+ automated tests; PyBullet baseline comparison |
102
+ | **Rendering** | `RealtimeRenderer` (OpenGL 3.3 PBR), `DeferredRenderer` (OpenGL 4.3 + SSAO + CSM + Bloom), `HQRenderer` (software ray-tracer) |
103
+ | **ECS** | `EntityWorld`, `Transform`, `Rigidbody`, `MeshRenderer`, `Script`, `PhysicsSystem` |
104
+ | **Animation** | `Skeleton`, `AnimationClip`, `AnimationPlayer`, `BlendTree`, `FABRIKSolver` |
105
+ | **Audio** | `AudioSystem`, `AudioSource`, `AudioClip` OpenAL, headless null fallback |
106
+ | **Particles** | `ParticleEmitter`, `ParticleSystem` — `sparks`, `smoke`, `debris`, `rain` presets |
107
+ | **Scene** | `SceneManager`, `SceneNode`, `Prefab` — additive scene transitions |
108
+ | **UI** | `Canvas`, `DebugPanel`, `InspectorPanel`, `HierarchyPanel` |
109
+ | **Editor** | `EditorApp` — Play/Pause/Step, transform gizmos |
110
+ | **RL** | Gymnasium-compatible environments; JAX JIT+vmap batch rollouts |
111
+ | **Performance** | JAX JIT+vmap: 2 000× throughput; Rust PyO3 core (PGS, GJK/EPA, BVH) |
112
+ | **Testing** | 459+ automated tests; PyBullet baseline comparison |
106
113
  | **Typed** | Fully annotated with `py.typed` marker |
107
114
 
108
115
  ---
@@ -110,27 +117,21 @@ all solved by forge3d's own code, backed by NumPy and optional JAX acceleration.
110
117
  ## Installation
111
118
 
112
119
  ```bash
113
- # Core library (physics + rendering)
120
+ # Full install physics, rendering, and RL in one command
114
121
  pip install pyforge3d
115
122
 
116
- # With rendering dependencies
117
- pip install "pyforge3d[render]"
118
-
119
- # With RL dependencies
120
- pip install "pyforge3d[rl]"
121
-
122
- # Everything
123
- pip install "pyforge3d[all]"
124
-
125
123
  # Development install
126
124
  git clone https://github.com/iruki-dev/forge3d
127
125
  cd forge3d
128
126
  pip install -e ".[dev]"
127
+
128
+ # WebGPU renderer (experimental, P34 — falls back to OpenGL if omitted)
129
+ pip install "pyforge3d[experimental]"
129
130
  ```
130
131
 
131
132
  > **Install name vs import name:** `pip install pyforge3d` — then `import forge3d` in your code.
132
133
 
133
- **Requirements:** Python 3.12+, NumPy, SciPy, JAX (CPU wheel)
134
+ **Requirements:** Python 3.12+, NumPy, SciPy, JAX (CPU wheel), moderngl, glfw, gymnasium
134
135
 
135
136
  ---
136
137
 
@@ -257,11 +258,21 @@ print(f"End-effector: {ee_pos.round(4)}")
257
258
 
258
259
  ## Reinforcement learning
259
260
 
261
+ forge3d ships a Gymnasium-compatible physics world. Use it directly with any RL library:
262
+
260
263
  ```python
261
- from apps.robot_rl.envs.reach_env import ReachEnv
264
+ import gymnasium as gym
265
+ import forge3d as f3d
262
266
  from stable_baselines3 import PPO
263
267
 
264
- env = ReachEnv(render_mode=None) # headless, fast
268
+ # Build a custom Gymnasium env backed by forge3d physics
269
+ class ReachEnv(gym.Env):
270
+ def __init__(self):
271
+ super().__init__()
272
+ self.world = f3d.World(gravity=(0, 0, -9.81))
273
+ # ... define observation_space / action_space
274
+
275
+ env = ReachEnv()
265
276
  model = PPO("MlpPolicy", env, verbose=1)
266
277
  model.learn(total_timesteps=200_000)
267
278
  model.save("reach_policy")
@@ -436,43 +447,55 @@ Physics core → SceneSnapshot → RealtimeRenderer (OpenGL)
436
447
 
437
448
  ```
438
449
  forge3d/
439
- ├── src/forge3d/
440
- │ ├── __init__.py # Public API: World, App, Body, Shape, Material…
441
- │ ├── app.py # App game-loop abstraction
442
- │ ├── input.py # Input, Key, _InputBuilder
443
- │ ├── camera.py # OrbitCamera, FollowCamera
444
- │ ├── facade.py # World, Body, Shape, Material facades
445
- │ ├── viewer.py # Viewer (realtime render loop)
446
- │ ├── recorder.py # Recorder (video capture)
447
- │ ├── backend.py # NumPy JAX backend switch
448
- │ ├── math/ # SE3, quaternion, spatial algebra
449
- │ ├── dynamics/ # RNEA, CRBA, ABA
450
- │ ├── collision/
451
- │ │ ├── detection.py # SAT + AABB broad-phase
452
- │ │ ├── gjk.py # GJK distance / intersection
453
- │ │ └── epa.py # EPA penetration depth
454
- │ ├── contact/
455
- │ │ └── solver.py # Impulse solver (PGS + Coulomb friction)
456
- │ ├── model/ # RigidBodyModel, DH joints, kinematics
457
- │ ├── sim/
458
- │ │ ├── world.py # PhysicsWorld (step, snapshot, CRUD)
459
- │ │ ├── jax_batch.py # JAX JIT+vmap batch physics
460
- │ │ └── domain_rand.py # Domain randomisation
461
- │ ├── robot/ # Robot, UR5 preset, FK/IK
462
- │ ├── render/
463
- │ │ ├── snapshot.py # SceneSnapshot, Transform, Material…
464
- │ │ ├── base.py # Renderer ABC
465
- │ │ ├── realtime/ # OpenGL rasteriser (PBR, PCF shadow)
466
- │ │ └── hq/ # Software ray-tracer (Blinn-Phong, AO, AA)
467
- │ ├── io/
468
- │ │ ├── obj_loader.py # Pure-Python OBJ parser
469
- │ │ └── mesh_data.py # MeshData, convex_hull_inertia
470
- └── py.typed # PEP 561 type marker
471
- ├── apps/robot_rl/ # RL application (Gymnasium envs, training)
472
- ├── examples/ # Self-contained examples (≤15 lines each)
473
- ├── tests/ # 215+ automated tests
474
- ├── validation/ # PyBullet / MuJoCo baseline comparison
475
- ├── assets/ # 3D models, textures
450
+ ├── src/
451
+ │ ├── forge3d/
452
+ ├── __init__.py # Public API: World, App, Body, Shape, Material…
453
+ ├── app.py # App game-loop abstraction
454
+ ├── input.py # Input, Key, InputBuilder
455
+ ├── camera.py # OrbitCamera, FollowCamera
456
+ ├── character.py # CharacterController
457
+ ├── facade.py # World, Body, Shape, Material facades
458
+ ├── viewer.py # Viewer (realtime render loop)
459
+ ├── recorder.py # Recorder (video capture)
460
+ ├── backend.py # NumPy JAX backend switch
461
+ ├── math/ # SE3, quaternion, spatial algebra
462
+ │ │ ├── dynamics/ # RNEA, CRBA, ABA
463
+ │ │ ├── collision/ # SAT, GJK/EPA, heightfield, layers, raycast
464
+ │ │ ├── contact/ # Impulse solver (PGS + Coulomb friction)
465
+ ├── constraints/ # Joints: Hinge, Ball, Prismatic, Fixed, Distance, Spring
466
+ │ │ ├── model/ # RigidBodyModel, DH joints, kinematics
467
+ ├── sim/
468
+ │ │ ├── world.py # PhysicsWorld (step, snapshot, CRUD)
469
+ │ │ ├── jax_batch.py # JAX JIT+vmap batch physics
470
+ │ │ │ └── domain_rand.py # Domain randomisation
471
+ │ │ ├── robot/ # Robot, UR5 preset, FK/IK
472
+ ├── ecs/ # EntityWorld, Transform, Rigidbody, Systems…
473
+ ├── animation/ # Skeleton, AnimationClip, FABRIKSolver…
474
+ │ │ ├── audio/ # AudioSystem, AudioSource, AudioClip (OpenAL)
475
+ │ │ ├── particle/ # ParticleEmitter, ParticleSystem, presets
476
+ │ │ ├── scene/ # SceneManager, SceneNode, Prefab
477
+ │ │ ├── ui/ # Canvas, DebugPanel, InspectorPanel…
478
+ ├── editor/ # EditorApp, gizmos, layout
479
+ │ │ ├── render/
480
+ │ │ │ ├── snapshot.py # SceneSnapshot — pure-data physics↔render contract
481
+ │ │ ├── base.py # Renderer ABC
482
+ │ │ │ ├── realtime/ # OpenGL 3.3 PBR rasteriser
483
+ │ │ │ ├── deferred/ # OpenGL 4.3 deferred (SSAO, CSM, HDR, Bloom)
484
+ │ │ │ ├── hq/ # Software ray-tracer (Blinn-Phong, AO, AA)
485
+ │ │ │ ├── wgpu_backend/ # WebGPU renderer (experimental, P34)
486
+ │ │ │ └── shaders/ # GLSL + WGSL shader sources
487
+ │ │ ├── io/ # OBJ loader, MeshData, world snapshot
488
+ │ │ └── py.typed # PEP 561 type marker
489
+ │ └── forge3d_core/ # Rust crate (PyO3): PGS, GJK/EPA, BVH, SIMD math
490
+ ├── apps/
491
+ │ ├── game/ # FORGE RUNNER — 3D physics platformer
492
+ │ ├── fps_battleroyal/ # FPS battle-royale demo
493
+ │ └── showcase/ # Engine feature showcase
494
+ ├── examples/ # Self-contained examples (≤15 lines each)
495
+ ├── tests/ # 459+ automated tests
496
+ ├── validation/ # PyBullet / MuJoCo baseline comparison
497
+ ├── assets/ # 3D models, textures
498
+ ├── docs_src/ # MkDocs documentation source
476
499
  ├── CHANGELOG.md
477
500
  ├── CONTRIBUTING.md
478
501
  ├── LICENSE
@@ -33,17 +33,26 @@ all solved by forge3d's own code, backed by NumPy and optional JAX acceleration.
33
33
  | Category | Details |
34
34
  |---|---|
35
35
  | **Game loop** | `App` class — `@on_start` / `@on_update` / `@on_render` decorators |
36
- | **Input** | `Input` snapshot — `key_held`, `key_pressed`, `mouse_pos`, `scroll_delta` |
37
- | **Camera** | `OrbitCamera`, `FollowCamera` — orbit, zoom, pan, smooth follow |
36
+ | **Input** | `Input` snapshot — `key_held`, `key_pressed`, `axis()`, `mouse_pos`, `scroll_delta` |
37
+ | **Camera** | `OrbitCamera` (with `handle_input()`), `FollowCamera` — orbit, zoom, pan, smooth follow |
38
+ | **Character** | `CharacterController` — `move()`, `jump()`, `glide()`, `move_camera_relative()` |
38
39
  | **Physics** | Rigid-body dynamics (RNEA / CRBA / ABA), semi-implicit Euler integrator |
39
- | **Collision** | SAT OBB-OBB (15-axis), sphere, capsule, GJK/EPA convex-mesh; AABB broad-phase |
40
- | **Contact** | Impulse solver, Coulomb friction, Baumgarte correction, warm-starting |
41
- | **Grasping** | `weld` / `release` kinematic constraints; friction-based pinch grasp |
40
+ | **Collision** | SAT OBB-OBB (15-axis), sphere, capsule, GJK/EPA convex-mesh; AABB broad-phase; BVH (Rust) |
41
+ | **Contact** | Impulse-based PGS solver, Coulomb friction, Baumgarte stabilization |
42
+ | **Joints** | Hinge, Ball, Prismatic, Fixed, Distance, Spring; `weld` / `release` |
43
+ | **Terrain** | Heightfield collision + rendering (32×32 to 512×512) |
42
44
  | **Robots** | UR5 6-DOF FK/IK, DH parameters, Jacobian |
43
- | **Rendering** | Real-time PBR (Cook-Torrance, PCF shadows, albedo/normal textures); HQ software ray-tracer |
44
- | **RL** | Gymnasium-compatible `ReachEnv` / `PickPlaceEnv` |
45
- | **Performance** | JAX JIT+vmap: 2 000× throughput vs. NumPy; AABB broad-phase O(n log n) |
46
- | **Testing** | 215+ automated tests; PyBullet baseline comparison |
45
+ | **Rendering** | `RealtimeRenderer` (OpenGL 3.3 PBR), `DeferredRenderer` (OpenGL 4.3 + SSAO + CSM + Bloom), `HQRenderer` (software ray-tracer) |
46
+ | **ECS** | `EntityWorld`, `Transform`, `Rigidbody`, `MeshRenderer`, `Script`, `PhysicsSystem` |
47
+ | **Animation** | `Skeleton`, `AnimationClip`, `AnimationPlayer`, `BlendTree`, `FABRIKSolver` |
48
+ | **Audio** | `AudioSystem`, `AudioSource`, `AudioClip` OpenAL, headless null fallback |
49
+ | **Particles** | `ParticleEmitter`, `ParticleSystem` — `sparks`, `smoke`, `debris`, `rain` presets |
50
+ | **Scene** | `SceneManager`, `SceneNode`, `Prefab` — additive scene transitions |
51
+ | **UI** | `Canvas`, `DebugPanel`, `InspectorPanel`, `HierarchyPanel` |
52
+ | **Editor** | `EditorApp` — Play/Pause/Step, transform gizmos |
53
+ | **RL** | Gymnasium-compatible environments; JAX JIT+vmap batch rollouts |
54
+ | **Performance** | JAX JIT+vmap: 2 000× throughput; Rust PyO3 core (PGS, GJK/EPA, BVH) |
55
+ | **Testing** | 459+ automated tests; PyBullet baseline comparison |
47
56
  | **Typed** | Fully annotated with `py.typed` marker |
48
57
 
49
58
  ---
@@ -51,27 +60,21 @@ all solved by forge3d's own code, backed by NumPy and optional JAX acceleration.
51
60
  ## Installation
52
61
 
53
62
  ```bash
54
- # Core library (physics + rendering)
63
+ # Full install physics, rendering, and RL in one command
55
64
  pip install pyforge3d
56
65
 
57
- # With rendering dependencies
58
- pip install "pyforge3d[render]"
59
-
60
- # With RL dependencies
61
- pip install "pyforge3d[rl]"
62
-
63
- # Everything
64
- pip install "pyforge3d[all]"
65
-
66
66
  # Development install
67
67
  git clone https://github.com/iruki-dev/forge3d
68
68
  cd forge3d
69
69
  pip install -e ".[dev]"
70
+
71
+ # WebGPU renderer (experimental, P34 — falls back to OpenGL if omitted)
72
+ pip install "pyforge3d[experimental]"
70
73
  ```
71
74
 
72
75
  > **Install name vs import name:** `pip install pyforge3d` — then `import forge3d` in your code.
73
76
 
74
- **Requirements:** Python 3.12+, NumPy, SciPy, JAX (CPU wheel)
77
+ **Requirements:** Python 3.12+, NumPy, SciPy, JAX (CPU wheel), moderngl, glfw, gymnasium
75
78
 
76
79
  ---
77
80
 
@@ -198,11 +201,21 @@ print(f"End-effector: {ee_pos.round(4)}")
198
201
 
199
202
  ## Reinforcement learning
200
203
 
204
+ forge3d ships a Gymnasium-compatible physics world. Use it directly with any RL library:
205
+
201
206
  ```python
202
- from apps.robot_rl.envs.reach_env import ReachEnv
207
+ import gymnasium as gym
208
+ import forge3d as f3d
203
209
  from stable_baselines3 import PPO
204
210
 
205
- env = ReachEnv(render_mode=None) # headless, fast
211
+ # Build a custom Gymnasium env backed by forge3d physics
212
+ class ReachEnv(gym.Env):
213
+ def __init__(self):
214
+ super().__init__()
215
+ self.world = f3d.World(gravity=(0, 0, -9.81))
216
+ # ... define observation_space / action_space
217
+
218
+ env = ReachEnv()
206
219
  model = PPO("MlpPolicy", env, verbose=1)
207
220
  model.learn(total_timesteps=200_000)
208
221
  model.save("reach_policy")
@@ -377,43 +390,55 @@ Physics core → SceneSnapshot → RealtimeRenderer (OpenGL)
377
390
 
378
391
  ```
379
392
  forge3d/
380
- ├── src/forge3d/
381
- │ ├── __init__.py # Public API: World, App, Body, Shape, Material…
382
- │ ├── app.py # App game-loop abstraction
383
- │ ├── input.py # Input, Key, _InputBuilder
384
- │ ├── camera.py # OrbitCamera, FollowCamera
385
- │ ├── facade.py # World, Body, Shape, Material facades
386
- │ ├── viewer.py # Viewer (realtime render loop)
387
- │ ├── recorder.py # Recorder (video capture)
388
- │ ├── backend.py # NumPy JAX backend switch
389
- │ ├── math/ # SE3, quaternion, spatial algebra
390
- │ ├── dynamics/ # RNEA, CRBA, ABA
391
- │ ├── collision/
392
- │ │ ├── detection.py # SAT + AABB broad-phase
393
- │ │ ├── gjk.py # GJK distance / intersection
394
- │ │ └── epa.py # EPA penetration depth
395
- │ ├── contact/
396
- │ │ └── solver.py # Impulse solver (PGS + Coulomb friction)
397
- │ ├── model/ # RigidBodyModel, DH joints, kinematics
398
- │ ├── sim/
399
- │ │ ├── world.py # PhysicsWorld (step, snapshot, CRUD)
400
- │ │ ├── jax_batch.py # JAX JIT+vmap batch physics
401
- │ │ └── domain_rand.py # Domain randomisation
402
- │ ├── robot/ # Robot, UR5 preset, FK/IK
403
- │ ├── render/
404
- │ │ ├── snapshot.py # SceneSnapshot, Transform, Material…
405
- │ │ ├── base.py # Renderer ABC
406
- │ │ ├── realtime/ # OpenGL rasteriser (PBR, PCF shadow)
407
- │ │ └── hq/ # Software ray-tracer (Blinn-Phong, AO, AA)
408
- │ ├── io/
409
- │ │ ├── obj_loader.py # Pure-Python OBJ parser
410
- │ │ └── mesh_data.py # MeshData, convex_hull_inertia
411
- └── py.typed # PEP 561 type marker
412
- ├── apps/robot_rl/ # RL application (Gymnasium envs, training)
413
- ├── examples/ # Self-contained examples (≤15 lines each)
414
- ├── tests/ # 215+ automated tests
415
- ├── validation/ # PyBullet / MuJoCo baseline comparison
416
- ├── assets/ # 3D models, textures
393
+ ├── src/
394
+ │ ├── forge3d/
395
+ ├── __init__.py # Public API: World, App, Body, Shape, Material…
396
+ ├── app.py # App game-loop abstraction
397
+ ├── input.py # Input, Key, InputBuilder
398
+ ├── camera.py # OrbitCamera, FollowCamera
399
+ ├── character.py # CharacterController
400
+ ├── facade.py # World, Body, Shape, Material facades
401
+ ├── viewer.py # Viewer (realtime render loop)
402
+ ├── recorder.py # Recorder (video capture)
403
+ ├── backend.py # NumPy JAX backend switch
404
+ ├── math/ # SE3, quaternion, spatial algebra
405
+ │ │ ├── dynamics/ # RNEA, CRBA, ABA
406
+ │ │ ├── collision/ # SAT, GJK/EPA, heightfield, layers, raycast
407
+ │ │ ├── contact/ # Impulse solver (PGS + Coulomb friction)
408
+ ├── constraints/ # Joints: Hinge, Ball, Prismatic, Fixed, Distance, Spring
409
+ │ │ ├── model/ # RigidBodyModel, DH joints, kinematics
410
+ ├── sim/
411
+ │ │ ├── world.py # PhysicsWorld (step, snapshot, CRUD)
412
+ │ │ ├── jax_batch.py # JAX JIT+vmap batch physics
413
+ │ │ │ └── domain_rand.py # Domain randomisation
414
+ │ │ ├── robot/ # Robot, UR5 preset, FK/IK
415
+ ├── ecs/ # EntityWorld, Transform, Rigidbody, Systems…
416
+ ├── animation/ # Skeleton, AnimationClip, FABRIKSolver…
417
+ │ │ ├── audio/ # AudioSystem, AudioSource, AudioClip (OpenAL)
418
+ │ │ ├── particle/ # ParticleEmitter, ParticleSystem, presets
419
+ │ │ ├── scene/ # SceneManager, SceneNode, Prefab
420
+ │ │ ├── ui/ # Canvas, DebugPanel, InspectorPanel…
421
+ ├── editor/ # EditorApp, gizmos, layout
422
+ │ │ ├── render/
423
+ │ │ │ ├── snapshot.py # SceneSnapshot — pure-data physics↔render contract
424
+ │ │ ├── base.py # Renderer ABC
425
+ │ │ │ ├── realtime/ # OpenGL 3.3 PBR rasteriser
426
+ │ │ │ ├── deferred/ # OpenGL 4.3 deferred (SSAO, CSM, HDR, Bloom)
427
+ │ │ │ ├── hq/ # Software ray-tracer (Blinn-Phong, AO, AA)
428
+ │ │ │ ├── wgpu_backend/ # WebGPU renderer (experimental, P34)
429
+ │ │ │ └── shaders/ # GLSL + WGSL shader sources
430
+ │ │ ├── io/ # OBJ loader, MeshData, world snapshot
431
+ │ │ └── py.typed # PEP 561 type marker
432
+ │ └── forge3d_core/ # Rust crate (PyO3): PGS, GJK/EPA, BVH, SIMD math
433
+ ├── apps/
434
+ │ ├── game/ # FORGE RUNNER — 3D physics platformer
435
+ │ ├── fps_battleroyal/ # FPS battle-royale demo
436
+ │ └── showcase/ # Engine feature showcase
437
+ ├── examples/ # Self-contained examples (≤15 lines each)
438
+ ├── tests/ # 459+ automated tests
439
+ ├── validation/ # PyBullet / MuJoCo baseline comparison
440
+ ├── assets/ # 3D models, textures
441
+ ├── docs_src/ # MkDocs documentation source
417
442
  ├── CHANGELOG.md
418
443
  ├── CONTRIBUTING.md
419
444
  ├── LICENSE
@@ -6,7 +6,7 @@ build-backend = "maturin"
6
6
 
7
7
  [project]
8
8
  name = "pyforge3d"
9
- version = "2.1.1"
9
+ version = "2.2.1"
10
10
  description = "Pure-Python 3D physics game engine — own dynamics, own rules, no compromises."
11
11
  readme = "README.md"
12
12
  license = { file = "LICENSE" }
@@ -35,10 +35,22 @@ classifiers = [
35
35
  "Typing :: Typed",
36
36
  ]
37
37
  dependencies = [
38
- "numpy>=1.26",
38
+ "numpy>=2.0",
39
39
  "scipy>=1.12",
40
40
  "sympy>=1.13",
41
41
  "jax[cpu]>=0.4.25",
42
+ # rendering
43
+ "moderngl>=5.10",
44
+ "glfw>=2.5",
45
+ "Pillow>=10.0",
46
+ "imageio>=2.34",
47
+ "imageio-ffmpeg>=0.5",
48
+ "soundfile>=0.12",
49
+ # RL / training
50
+ "gymnasium>=0.29",
51
+ "stable-baselines3>=2.2",
52
+ "optax>=0.2",
53
+ "matplotlib>=3.8",
42
54
  ]
43
55
 
44
56
  [project.urls]
@@ -51,36 +63,22 @@ Changelog = "https://github.com/iruki-dev/forge3d/blob/main/CHANGELOG.md"
51
63
  # ── Optional dependency groups ────────────────────────────────────────────────
52
64
 
53
65
  [project.optional-dependencies]
54
- render = [
55
- "moderngl>=5.10",
56
- "glfw>=2.5",
57
- "Pillow>=10.0",
58
- "imageio>=2.34",
59
- "imageio-ffmpeg>=0.5",
60
- ]
61
- rl = [
62
- "gymnasium>=0.29",
63
- "stable-baselines3>=2.2",
64
- "optax>=0.2",
66
+ experimental = [
67
+ "wgpu>=0.19", # P34 WebGPU 렌더러 (없으면 GL 폴백)
68
+ "rendercanvas>=2.0", # wgpu 캔버스 백엔드
65
69
  ]
66
70
  dev = [
67
71
  "pytest>=8.0",
68
72
  "pytest-cov>=5.0",
69
73
  "ruff>=0.4",
70
74
  "mypy>=1.9",
71
- # render deps for tests
72
- "moderngl>=5.10",
73
- "glfw>=2.5",
74
- "Pillow>=10.0",
75
- "imageio>=2.34",
76
- "imageio-ffmpeg>=0.5",
77
75
  ]
78
76
  docs = [
79
77
  "mkdocs-material>=9.5",
80
78
  "mkdocstrings[python]>=0.25",
81
79
  ]
82
80
  all = [
83
- "pyforge3d[render,rl,dev]",
81
+ "pyforge3d[experimental,dev,docs]",
84
82
  ]
85
83
 
86
84
  # ── Build (maturin — Python + Rust 혼합) ──────────────────────────────────────
@@ -103,13 +101,15 @@ addopts = "-q --tb=short"
103
101
  line-length = 100
104
102
  target-version = "py312"
105
103
  src = ["src"]
104
+ exclude = ["archive", "demos"]
106
105
 
107
106
  [tool.ruff.lint]
108
107
  select = ["E", "F", "I", "UP", "B", "C4", "SIM"]
109
108
  ignore = ["B008", "SIM118"]
110
109
 
111
110
  [tool.ruff.lint.per-file-ignores]
112
- "tests/**" = ["E501"]
111
+ "tests/**" = ["E501"]
112
+ "apps/**" = ["E501", "E701", "E702", "B905", "E741", "SIM105", "SIM108"]
113
113
 
114
114
  # ── Type-check ────────────────────────────────────────────────────────────────
115
115
 
@@ -103,7 +103,7 @@ from forge3d.editor import EditorApp, PlayState
103
103
  from forge3d.errors import Forge3dError, PhysicsError, RenderError, ValidationError
104
104
  from forge3d.events import CollisionEvent, CollisionHandler
105
105
  from forge3d.facade import Body, Material, Shape, World
106
- from forge3d.input import Input, InputBuilder, Key
106
+ from forge3d.input import Input, InputBuilder, Key, ScriptedInput
107
107
  from forge3d.io.world_snapshot import StateRecorder
108
108
 
109
109
  # Particle system (P31)
@@ -121,7 +121,7 @@ from forge3d.scene import Prefab, SceneManager, SceneNode
121
121
  from forge3d.ui import Canvas, DebugPanel, HierarchyPanel, InspectorPanel, UISystem
122
122
  from forge3d.viewer import Viewer
123
123
 
124
- __version__ = "2.1.1"
124
+ __version__ = "2.2.1"
125
125
 
126
126
  # ── API 안정성 선언 ───────────────────────────────────────────────────────────
127
127
  # Stable (v3까지 Breaking change 없음):
@@ -158,6 +158,7 @@ __all__ = [
158
158
  "Input",
159
159
  "InputBuilder",
160
160
  "Key",
161
+ "ScriptedInput",
161
162
  # Camera
162
163
  "OrbitCamera",
163
164
  "FollowCamera",