pyforge3d 1.0.0__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 (88) hide show
  1. pyforge3d-1.0.0/.claude/settings.json +66 -0
  2. pyforge3d-1.0.0/.claude/settings.local.json +9 -0
  3. pyforge3d-1.0.0/.gitignore +27 -0
  4. pyforge3d-1.0.0/.readthedocs.yaml +16 -0
  5. pyforge3d-1.0.0/CHANGELOG.md +164 -0
  6. pyforge3d-1.0.0/CLAUDE.md +77 -0
  7. pyforge3d-1.0.0/CONTRIBUTING.md +69 -0
  8. pyforge3d-1.0.0/LICENSE +21 -0
  9. pyforge3d-1.0.0/PKG-INFO +566 -0
  10. pyforge3d-1.0.0/README.md +479 -0
  11. pyforge3d-1.0.0/assets/.gitkeep +0 -0
  12. pyforge3d-1.0.0/assets/arm_6dof.py +82 -0
  13. pyforge3d-1.0.0/assets/models/cube.obj +50 -0
  14. pyforge3d-1.0.0/assets/models/tetrahedron.obj +20 -0
  15. pyforge3d-1.0.0/demos/README.md +117 -0
  16. pyforge3d-1.0.0/demos/interactive_robot.py +428 -0
  17. pyforge3d-1.0.0/demos/physics_showcase.py +365 -0
  18. pyforge3d-1.0.0/demos/training_watch.py +901 -0
  19. pyforge3d-1.0.0/examples/01_falling_box_realtime.py +17 -0
  20. pyforge3d-1.0.0/examples/02_bounce_hq_video.py +22 -0
  21. pyforge3d-1.0.0/examples/02_bouncing_ball.py +23 -0
  22. pyforge3d-1.0.0/examples/03_robot_interactive.py +51 -0
  23. pyforge3d-1.0.0/examples/04_friction_grasp.py +76 -0
  24. pyforge3d-1.0.0/mkdocs.yml +107 -0
  25. pyforge3d-1.0.0/pyproject.toml +140 -0
  26. pyforge3d-1.0.0/src/forge3d/__init__.py +105 -0
  27. pyforge3d-1.0.0/src/forge3d/app.py +219 -0
  28. pyforge3d-1.0.0/src/forge3d/backend.py +118 -0
  29. pyforge3d-1.0.0/src/forge3d/camera.py +235 -0
  30. pyforge3d-1.0.0/src/forge3d/collision/__init__.py +20 -0
  31. pyforge3d-1.0.0/src/forge3d/collision/detection.py +739 -0
  32. pyforge3d-1.0.0/src/forge3d/collision/epa.py +217 -0
  33. pyforge3d-1.0.0/src/forge3d/collision/gjk.py +266 -0
  34. pyforge3d-1.0.0/src/forge3d/collision/heightfield.py +196 -0
  35. pyforge3d-1.0.0/src/forge3d/collision/layers.py +38 -0
  36. pyforge3d-1.0.0/src/forge3d/constraints/__init__.py +35 -0
  37. pyforge3d-1.0.0/src/forge3d/constraints/base.py +132 -0
  38. pyforge3d-1.0.0/src/forge3d/constraints/joints.py +635 -0
  39. pyforge3d-1.0.0/src/forge3d/contact/__init__.py +1 -0
  40. pyforge3d-1.0.0/src/forge3d/contact/solver.py +341 -0
  41. pyforge3d-1.0.0/src/forge3d/dynamics/__init__.py +24 -0
  42. pyforge3d-1.0.0/src/forge3d/dynamics/aba.py +108 -0
  43. pyforge3d-1.0.0/src/forge3d/dynamics/crba.py +73 -0
  44. pyforge3d-1.0.0/src/forge3d/dynamics/model.py +97 -0
  45. pyforge3d-1.0.0/src/forge3d/dynamics/rnea.py +260 -0
  46. pyforge3d-1.0.0/src/forge3d/errors.py +98 -0
  47. pyforge3d-1.0.0/src/forge3d/events.py +267 -0
  48. pyforge3d-1.0.0/src/forge3d/facade.py +1032 -0
  49. pyforge3d-1.0.0/src/forge3d/input.py +243 -0
  50. pyforge3d-1.0.0/src/forge3d/io/__init__.py +10 -0
  51. pyforge3d-1.0.0/src/forge3d/io/mesh_data.py +206 -0
  52. pyforge3d-1.0.0/src/forge3d/io/obj_loader.py +203 -0
  53. pyforge3d-1.0.0/src/forge3d/io/world_snapshot.py +284 -0
  54. pyforge3d-1.0.0/src/forge3d/logging.py +35 -0
  55. pyforge3d-1.0.0/src/forge3d/math/__init__.py +59 -0
  56. pyforge3d-1.0.0/src/forge3d/math/inertia.py +60 -0
  57. pyforge3d-1.0.0/src/forge3d/math/quaternion.py +141 -0
  58. pyforge3d-1.0.0/src/forge3d/math/se3.py +162 -0
  59. pyforge3d-1.0.0/src/forge3d/math/spatial.py +139 -0
  60. pyforge3d-1.0.0/src/forge3d/model/__init__.py +14 -0
  61. pyforge3d-1.0.0/src/forge3d/model/kinematics.py +134 -0
  62. pyforge3d-1.0.0/src/forge3d/model/robot_config.py +138 -0
  63. pyforge3d-1.0.0/src/forge3d/model/urdf_loader.py +194 -0
  64. pyforge3d-1.0.0/src/forge3d/py.typed +0 -0
  65. pyforge3d-1.0.0/src/forge3d/recorder.py +204 -0
  66. pyforge3d-1.0.0/src/forge3d/render/__init__.py +1 -0
  67. pyforge3d-1.0.0/src/forge3d/render/base.py +36 -0
  68. pyforge3d-1.0.0/src/forge3d/render/hq/__init__.py +1 -0
  69. pyforge3d-1.0.0/src/forge3d/render/hq/raytracer.py +297 -0
  70. pyforge3d-1.0.0/src/forge3d/render/hq/renderer.py +72 -0
  71. pyforge3d-1.0.0/src/forge3d/render/hq/scene.py +138 -0
  72. pyforge3d-1.0.0/src/forge3d/render/realtime/__init__.py +5 -0
  73. pyforge3d-1.0.0/src/forge3d/render/realtime/context.py +119 -0
  74. pyforge3d-1.0.0/src/forge3d/render/realtime/meshes.py +254 -0
  75. pyforge3d-1.0.0/src/forge3d/render/realtime/renderer.py +565 -0
  76. pyforge3d-1.0.0/src/forge3d/render/realtime/shaders.py +193 -0
  77. pyforge3d-1.0.0/src/forge3d/render/snapshot.py +91 -0
  78. pyforge3d-1.0.0/src/forge3d/robot/__init__.py +38 -0
  79. pyforge3d-1.0.0/src/forge3d/robot/presets.py +82 -0
  80. pyforge3d-1.0.0/src/forge3d/robot/robot.py +162 -0
  81. pyforge3d-1.0.0/src/forge3d/sim/__init__.py +1 -0
  82. pyforge3d-1.0.0/src/forge3d/sim/domain_rand.py +113 -0
  83. pyforge3d-1.0.0/src/forge3d/sim/jax_batch.py +191 -0
  84. pyforge3d-1.0.0/src/forge3d/sim/world.py +626 -0
  85. pyforge3d-1.0.0/src/forge3d/viewer.py +235 -0
  86. pyforge3d-1.0.0/training_output/demo_run/final_model.zip +0 -0
  87. pyforge3d-1.0.0/training_output/demo_run/progress.csv +92 -0
  88. pyforge3d-1.0.0/training_output/reach_ppo/final_model.zip +0 -0
@@ -0,0 +1,66 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(mkdir -p /workspaces/2026_python_toy_project_1/src/forge3d/io /workspaces/2026_python_toy_project_1/assets/models)",
5
+ "Bash(python -c \"import forge3d; print\\('forge3d import OK, version:', forge3d.__version__\\)\")",
6
+ "Bash(python -c ' *)",
7
+ "Bash(python -m pytest tests/test_import.py tests/test_backend.py tests/test_math.py tests/test_collision.py tests/test_snapshot.py tests/test_realtime_smoke.py tests/test_contact_physics.py tests/test_api_usability.py -q --tb=short)",
8
+ "Bash(python -m pytest tests/test_import.py tests/test_backend.py tests/test_math.py tests/test_collision.py tests/test_snapshot.py tests/test_contact_physics.py tests/test_api_usability.py -q --tb=short)",
9
+ "Bash(python -m pytest tests/test_realtime_smoke.py tests/test_hq_renderer.py -q --tb=short)",
10
+ "Bash(python -m pytest tests/test_rnea_2dof.py tests/test_crba.py tests/test_aba.py tests/test_conservation.py tests/test_p12_friction.py tests/test_p13_rigid_body.py tests/test_robot.py tests/test_collision.py -q --tb=short)",
11
+ "Bash(python -m pytest tests/test_mesh_and_io.py -q --tb=short)",
12
+ "Bash(python -m ruff check src/forge3d/io/ src/forge3d/collision/epa.py src/forge3d/collision/gjk.py src/forge3d/collision/detection.py src/forge3d/sim/world.py src/forge3d/render/realtime/ src/forge3d/facade.py src/forge3d/__init__.py)",
13
+ "Bash(python -m ruff check --fix src/forge3d/io/ src/forge3d/collision/epa.py src/forge3d/collision/gjk.py src/forge3d/collision/detection.py src/forge3d/sim/world.py src/forge3d/render/realtime/ src/forge3d/facade.py src/forge3d/__init__.py)",
14
+ "Bash(python -m ruff format --check src/forge3d/io/ src/forge3d/collision/epa.py src/forge3d/collision/detection.py src/forge3d/sim/world.py src/forge3d/render/realtime/ src/forge3d/facade.py)",
15
+ "Bash(python -m ruff format src/forge3d/io/ src/forge3d/collision/epa.py src/forge3d/collision/detection.py src/forge3d/sim/world.py src/forge3d/render/realtime/ src/forge3d/facade.py)",
16
+ "Bash(python -m ruff check src/forge3d/io/ src/forge3d/collision/epa.py src/forge3d/collision/detection.py src/forge3d/sim/world.py src/forge3d/render/realtime/ src/forge3d/facade.py)",
17
+ "Bash(python -m pytest tests/test_import.py tests/test_backend.py tests/test_math.py tests/test_collision.py tests/test_snapshot.py tests/test_contact_physics.py tests/test_api_usability.py tests/test_realtime_smoke.py tests/test_hq_renderer.py tests/test_conservation.py tests/test_rnea_2dof.py tests/test_crba.py tests/test_aba.py tests/test_p12_friction.py tests/test_p13_rigid_body.py tests/test_robot.py tests/test_mesh_and_io.py -q --tb=short)",
18
+ "Bash(python -m pytest tests/test_import.py tests/test_snapshot.py tests/test_realtime_smoke.py tests/test_mesh_and_io.py tests/test_collision.py -q --tb=short)",
19
+ "Bash(python -c \"import forge3d as f3d; print\\('import OK'\\); print\\(f3d.__version__\\); print\\(f3d.__all__\\)\")",
20
+ "Bash(python -m pytest tests/test_input.py tests/test_camera.py tests/test_app.py -v)",
21
+ "Bash(python -m pytest tests/ --ignore=tests/test_p9_training.py --ignore=tests/test_p11_jax.py -q)",
22
+ "Bash(python -m pytest tests/ --ignore=tests/test_p9_training.py --ignore=tests/test_p11_jax.py)",
23
+ "Bash(ruff check *)",
24
+ "Bash(ruff format *)",
25
+ "Bash(mypy src/forge3d/app.py src/forge3d/input.py src/forge3d/camera.py src/forge3d/facade.py src/forge3d/viewer.py src/forge3d/__init__.py --ignore-missing-imports)",
26
+ "Bash(python3 -)",
27
+ "Bash(pytest /workspaces/2026_python_toy_project_1/tests/ --ignore=/workspaces/2026_python_toy_project_1/tests/test_p9_training.py -q)",
28
+ "Bash(git init *)",
29
+ "Bash(git config *)",
30
+ "Bash(apt-get install *)",
31
+ "Bash(git branch *)",
32
+ "Bash(git -C /workspaces/2026_python_toy_project_1 config user.email \"me@iruki.dev\")",
33
+ "Bash(git -C /workspaces/2026_python_toy_project_1 config user.name \"forge3d\")",
34
+ "Bash(git -C /workspaces/2026_python_toy_project_1 branch -m main)",
35
+ "Bash(GIT_DIR=/workspaces/2026_python_toy_project_1/.git GIT_WORK_TREE=/workspaces/2026_python_toy_project_1 git status)",
36
+ "Bash(cat)",
37
+ "Bash(GIT_DIR=/workspaces/2026_python_toy_project_1/.git GIT_WORK_TREE=/workspaces/2026_python_toy_project_1 git branch -m main)",
38
+ "Bash(pip install *)",
39
+ "Bash(python -m build)",
40
+ "Bash(twine check *)",
41
+ "Bash(python -c \"import forge3d; print\\('forge3d', forge3d.__version__, '✅'\\)\")",
42
+ "Bash(GIT_DIR=.git GIT_WORK_TREE=. git add .gitignore .readthedocs.yaml .github/ pyproject.toml CHANGELOG.md src/forge3d/__init__.py docs/ README.md CONTRIBUTING.md LICENSE)",
43
+ "Bash(GIT_DIR=.git GIT_WORK_TREE=. git status --short)",
44
+ "Bash(GIT_DIR=.git GIT_WORK_TREE=. git add src/ tests/ apps/ assets/ examples/ demos/ validation/ CLAUDE.md)",
45
+ "Bash(GIT_DIR=.git GIT_WORK_TREE=. git commit -m ' *)",
46
+ "Bash(mkdocs build *)",
47
+ "Bash(pytest tests/test_p16_joints.py -v)",
48
+ "Bash(python -c \"from forge3d.constraints import BallJoint; print\\('OK'\\)\")",
49
+ "Bash(pytest tests/ --ignore=tests/test_p9_training.py -q)",
50
+ "Bash(pytest tests/test_import.py -v)",
51
+ "Bash(pytest tests/ --ignore=tests/test_p9_training.py)",
52
+ "Bash(mypy src/)",
53
+ "Bash(pytest tests/test_p17_events.py -v)",
54
+ "Bash(pytest tests/test_p18_serialization.py -v)",
55
+ "Bash(pytest tests/test_p19_layers.py -v)",
56
+ "Bash(pytest tests/test_p20_api.py -v)",
57
+ "Bash(pytest tests/test_p21_terrain.py -v)",
58
+ "Bash(pytest tests/test_p23_performance.py -v)",
59
+ "Bash(python -c \"import forge3d; print\\('forge3d', forge3d.__version__, '✅'\\); print\\('joints:', forge3d.JointHandle.__name__\\); print\\('events:', forge3d.CollisionEvent.__name__\\); print\\('terrain:', forge3d.World.add_terrain.__name__\\)\")",
60
+ "Bash(GIT_DIR=.git GIT_WORK_TREE=. git add -A)"
61
+ ],
62
+ "additionalDirectories": [
63
+ "/workspaces/2026_python_toy_project_1/src/forge3d"
64
+ ]
65
+ }
66
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(pip --version)",
5
+ "Bash(python3 -c \"import jax; print\\('jax', jax.__version__\\)\")",
6
+ "Bash(python3 -c \"import numpy; print\\('numpy', numpy.__version__\\)\")"
7
+ ]
8
+ }
9
+ }
@@ -0,0 +1,27 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.so
4
+ .eggs/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .venv/
9
+ venv/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .pytest_cache/
13
+ logs/
14
+ checkpoints/
15
+ *.mp4
16
+ *.png
17
+ *.states
18
+ *.npz
19
+ site/
20
+ .env
21
+ *.env.local
22
+ *.db
23
+ *.sqlite
24
+ .DS_Store
25
+ Thumbs.db
26
+ *.log
27
+ *.ppm
@@ -0,0 +1,16 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.12"
7
+
8
+ mkdocs:
9
+ configuration: mkdocs.yml
10
+
11
+ python:
12
+ install:
13
+ - method: pip
14
+ path: .
15
+ extra_requirements:
16
+ - docs
@@ -0,0 +1,164 @@
1
+ # Changelog
2
+
3
+ All notable changes to forge3d are documented here.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ versioning follows [Semantic Versioning](https://semver.org/).
6
+
7
+ ---
8
+
9
+ ## [Unreleased]
10
+
11
+ ---
12
+
13
+ ## [1.0.0] — 2026-06-03 ★ STABLE RELEASE
14
+
15
+ ### Added (P15 — MkDocs 문서 사이트)
16
+ - MkDocs Material 테마 기반 문서 사이트 (`mkdocs.yml`, `docs_src/`)
17
+ - API 레퍼런스, 튜토리얼 4종, 아키텍처 개요 페이지
18
+ - ReadTheDocs 설정 (`.readthedocs.yaml`), GitHub Pages 워크플로우
19
+
20
+ ### Added (P16 — 조인트 & 구속 시스템)
21
+ - `forge3d.constraints` 패키지: Sequential Impulse 구속 솔버
22
+ - `FixedJoint`, `BallJoint`, `HingeJoint` (모터·한계 포함), `PrismaticJoint`, `DistanceJoint`, `SpringJoint`
23
+ - `World.add_joint(type, body_a, body_b, ...)` 통합 API; `World.remove_joint(handle)`
24
+ - `forge3d.JointHandle` 핸들 클래스
25
+
26
+ ### Added (P17 — 충돌 이벤트 콜백)
27
+ - `forge3d.CollisionEvent`, `forge3d.CollisionHandler`
28
+ - `World.on_collision_begin`, `on_collision_stay`, `on_collision_end` 데코레이터
29
+ - `World.add_collision_handler(body_a, body_b)` 쌍별 핸들러
30
+ - `World.add_trigger_zone(position, size)` 순수 데이터 존 (물리 충돌 없음)
31
+ - `World.ignore_collision(body_a, body_b)` 쌍별 충돌 무시
32
+
33
+ ### Added (P18 — 씬 직렬화)
34
+ - `World.save(path)` → JSON 저장
35
+ - `World.load(path)` 클래스 메서드 → World 복원
36
+ - `forge3d.StateRecorder` — 프레임별 상태 기록 + npz 저장/재현
37
+
38
+ ### Added (P19 — 충돌 레이어·마스크)
39
+ - `forge3d.CollisionLayer` 비트필드 상수 (`DEFAULT`, `PLAYER`, `ENEMY`, `BULLET` 등)
40
+ - `Body.collision_layer`, `Body.collision_mask` 프로퍼티
41
+ - `World.ignore_collision()` 쌍 기반 물리 충돌 무시 (physics-level)
42
+
43
+ ### Added (P20 — API 강화)
44
+ - `forge3d.errors` 모듈: `Forge3dError`, `ValidationError`, `PhysicsError`, `RenderError`
45
+ - `World()`, `add_box()`, `add_sphere()` 인자 검증 (mass > 0, size > 0, restitution ∈ [0,1])
46
+ - 친절한 에러 메시지 (`ClassName.method() — param must be ... got param=...`)
47
+
48
+ ### Added (P21 — Heightfield 지형)
49
+ - `World.add_terrain(heights, cell_size, origin)` → Heightfield
50
+ - 구 vs 높이맵, 박스 vs 높이맵 충돌 감지 (쌍선형 보간)
51
+
52
+ ### Added (P23 — 아일랜드 슬리핑)
53
+ - 바디 슬리핑 카운터 (`_sleep_counters`)
54
+ - `Body.is_sleeping` 프로퍼티, `PhysicsWorld.wake_body()`
55
+
56
+ ### Changed
57
+ - Version bump: `0.4.0` → `1.0.0` (첫 안정 릴리즈)
58
+ - pyproject.toml `Development Status :: 5 - Production/Stable`
59
+ - Baumgarte 바이어스 부호 수정 (구속 솔버 안정성 대폭 향상)
60
+
61
+ ---
62
+
63
+ ## [0.4.0] — 2026-06-03
64
+
65
+ ### Added (P14 — PyPI 배포 인프라)
66
+ - GitHub Actions CI 워크플로우 (Python 3.12/3.13, push/PR 자동 테스트)
67
+ - GitHub Actions Release 워크플로우 (tag → TestPyPI → PyPI OIDC Trusted Publisher)
68
+ - GitHub Actions Docs 워크플로우 (main 브랜치 → GitHub Pages)
69
+ - `pyproject.toml` 저자·URL 정비 (`authors`, `project.urls`)
70
+ - `[project.optional-dependencies.docs]` — `mkdocs-material`, `mkdocstrings`
71
+ - `[tool.hatch.build.targets.sdist]` exclude 패턴 (테스트·앱·문서 제외)
72
+ - `.readthedocs.yaml` ReadTheDocs 설정
73
+ - `.github/ISSUE_TEMPLATE/` — bug report / feature request 템플릿
74
+
75
+ ### Changed
76
+ - Version bump: `0.3.0` → `0.4.0`
77
+ - `.gitignore` 확장 (`.states`, `.npz`, `site/`, `*.ppm` 등)
78
+
79
+ ### Added
80
+ - `App` class — high-level game-loop abstraction with `@on_start` / `@on_update` / `@on_render` decorators
81
+ - `Input` class — per-frame keyboard/mouse state snapshot (`key_held`, `key_pressed`, `key_released`, `mouse_pos`, `mouse_delta`, `scroll_delta`)
82
+ - `Key` — named key constant class (`Key.SPACE`, `Key.W`, `Key.ESCAPE`, …)
83
+ - `OrbitCamera` — orbit-around-target camera with `rotate()`, `zoom()`, `pan()`, `to_snapshot()`
84
+ - `FollowCamera` — smooth camera that tracks a `Body` from a fixed offset
85
+ - `world.bodies` property — list of all `Body` handles currently in the world
86
+ - `world.remove(body)` — remove a body from the world mid-simulation
87
+ - `world.clear()` — remove all dynamic bodies (keep statics if desired)
88
+ - `world.get_body(name)` — look up a body by name
89
+ - `body.name` — readable name set at creation
90
+ - `body.is_static` — True if the body does not move under physics
91
+ - `body.mass` — body mass in kg
92
+ - `body.apply_force(force)` — accumulate a world-frame force applied during the next `step()`
93
+ - `body.apply_torque(torque)` — accumulate a world-frame torque applied during the next `step()`
94
+ - `body.set_position(pos)` / `body.set_orientation(quat)` — teleport shortcuts
95
+ - `body.set_velocity(vel)` / `body.set_angular_velocity(omega)` — velocity override
96
+ - AABB broad-phase pre-filter in `collision.detection` — O(n²) → O(n log n) contact detection
97
+ - `src/forge3d/py.typed` — PEP 561 type marker (inline types exported)
98
+ - MIT `LICENSE` file
99
+ - `CONTRIBUTING.md` — contribution guide
100
+
101
+ ### Changed
102
+ - `World.__repr__` now includes body count and gravity vector
103
+ - `Body.__repr__` now includes name and velocity magnitude
104
+ - `Viewer` now integrates `Input` state; pass `viewer.input` to access current frame
105
+ - `pyproject.toml` upgraded to full PyPI metadata: classifiers, keywords, URLs, readme, license
106
+
107
+ ### Improved
108
+ - Broad-phase AABB filter reduces GJK calls from O(n²) to only overlapping pairs
109
+ - `world.update_body_pose` uses body-id index cache (O(1) lookup instead of O(n) scan)
110
+ - `world.apply_impulse` likewise O(1) lookup
111
+ - `Viewer.draw()` returns the rendered frame as `ndarray` consistently
112
+
113
+ ---
114
+
115
+ ## [0.2.0] — 2026-05
116
+
117
+ ### Added
118
+ - `Shape.capsule(radius, half_length)` — capsule rigid body
119
+ - `Shape.convex_mesh(mesh_data)` — convex-hull body from OBJ file
120
+ - `world.add_capsule()` / `world.add_mesh()` facade helpers
121
+ - `forge3d.io.load_obj(path)` → `MeshData` — pure-Python OBJ parser
122
+ - `MeshData.hull_vertices` / `.hull_faces` — precomputed convex hull
123
+ - `convex_hull_inertia()` — exact inertia via signed-tetrahedra method
124
+ - GJK extended: support functions for "mesh" and "capsule" shapes
125
+ - EPA (`collision/epa.py`) — penetration depth + contact normal for intersecting convex bodies
126
+ - `gjk_contact()` — public GJK+EPA interface returning `(depth, normal)`
127
+ - PBR shader (Cook-Torrance BRDF): GGX NDF, Smith geometry, Fresnel-Schlick
128
+ - PCF shadow map upgraded to 2 K resolution
129
+ - Reinhard tone mapping + gamma correction
130
+ - Albedo texture support: PNG/JPEG via `imageio`
131
+ - `Material.texture_path` / `.normal_map_path` fields
132
+ - Sample models: `assets/models/cube.obj`, `assets/models/tetrahedron.obj`
133
+ - Capsule VAO cached by `(radius, half_length)` key
134
+ - Mesh VAO cached by `mesh_id`
135
+
136
+ ### Changed
137
+ - Vertex layout: 6 floats → 8 floats `[px, py, pz, nx, ny, nz, u, v]`
138
+
139
+ ---
140
+
141
+ ## [0.1.0] — 2026-03
142
+
143
+ ### Added
144
+ - `World`, `Body`, `Shape`, `Material`, `Viewer`, `Recorder` — public API
145
+ - Rigid-body physics: RNEA, CRBA, ABA; semi-implicit Euler integrator
146
+ - Collision detection: SAT OBB-OBB (15-axis), sphere-sphere, sphere-box,
147
+ capsule-sphere, capsule-box, capsule-capsule
148
+ - Impulse-based contact solver with Coulomb friction and Baumgarte correction
149
+ - Weld constraints (`world.weld` / `world.release`) for kinematic grasping
150
+ - `world.teleport`, `world.apply_impulse`
151
+ - UR5 6-DOF robot model with forward kinematics and Jacobian
152
+ - `RealtimeRenderer` — OpenGL 3.3 rasteriser via `moderngl` + Xvfb headless
153
+ - `HQRenderer` — software ray-tracer (Blinn-Phong, AO, MSAA)
154
+ - `SceneSnapshot` — pure-data physics↔renderer contract
155
+ - Gymnasium-compatible `ReachEnv` and `PickPlaceEnv`
156
+ - JAX JIT+vmap batch physics (`sim/jax_batch.py`) — 2000× throughput
157
+ - SHAC: analytic policy gradients via FK auto-diff
158
+ - Domain randomisation (`sim/domain_rand.py`)
159
+ - NumPy ↔ JAX backend switch via `ENGINE_BACKEND` env-var
160
+ - 215 automated tests
161
+
162
+ [Unreleased]: https://github.com/your-org/forge3d/compare/v0.2.0...HEAD
163
+ [0.2.0]: https://github.com/your-org/forge3d/compare/v0.1.0...v0.2.0
164
+ [0.1.0]: https://github.com/your-org/forge3d/releases/tag/v0.1.0
@@ -0,0 +1,77 @@
1
+ # CLAUDE.md — forge3d
2
+
3
+ > 매 세션 자동 로딩되는 파일이다. **항상 적용되는 규칙만** 둔다.
4
+ > 가끔만 필요한 절차·도메인 지식은 `docs/`(ROADMAP, SPEC)나 `.claude/skills/`로 옮긴다.
5
+ > 초안은 `/init`로 생성한 뒤 이 형식으로 다듬는다. git에 커밋해 공유한다.
6
+ > `[대괄호]`는 실제 값으로 채운다.
7
+
8
+ ---
9
+
10
+ ## 0. 절대 제약 — YOU MUST (이 프로젝트의 존재 이유)
11
+
12
+ - **IMPORTANT: 외부 물리엔진(MuJoCo, PyBullet, Bullet, ODE, DART, Isaac, Brax 등)을 엔진 코어에서 사용하지 않는다.** 동역학·접촉·충돌·구속·기구학은 **항상 직접 구현한 코드**가 푼다.
13
+ - 판단 기준 한 줄: **"동역학과 접촉을 누가 푸는가?" → 답은 언제나 우리 코드.** 막혔다고 외부 엔진 호출로 우회하지 않는다. 막히면 멈추고 보고한다.
14
+ - 외부 엔진(`pybullet`, `mujoco`)의 import는 **`validation/` 디렉터리에서 기준값 대조 전용으로만** 허용한다. `src/forge3d/` 안에서는 절대 금지.
15
+ - 써도 되는 패키지: NumPy, JAX, PyTorch, SciPy, SymPy, Gymnasium, optax/flax/equinox, stable-baselines3. (이들은 동역학을 *대신 풀지 않는다*.) **그래픽 출력용** `moderngl`/`pyglet`/`glfw`/`imgui`/`imageio`/`ffmpeg`도 허용 — 단, **물리·접촉 연산에는 절대 쓰지 않는다.**
16
+ - **GPU 제약 해석: "GPU 불가"는 물리·학습 연산 한정이다. 화면에 삼각형을 그리는 그래픽용 OpenGL은 허용**한다(없으면 실시간 렌더링 불가). 단 헤드리스에서 OpenGL 가속 여부가 불확실하면 가정하지 말고 EGL/Xvfb 가능성부터 확인하고 보고한다.
17
+
18
+ ## 0b. 라이브러리 ↔ 응용 분리 — YOU MUST
19
+
20
+ - 이 프로젝트는 **라이브러리(`src/forge3d/`, 1급 산출물)** 와 **응용(`apps/robot_rl/`)** 의 2층이다. 응용은 라이브러리를 **외부인처럼 import해서만** 쓴다. 응용을 짜려고 라이브러리 내부를 고쳐야 한다면 추상화가 실패한 것이니 멈추고 보고한다.
21
+ - **물리 코어(`math/ dynamics/ collision/ contact/ model/ sim/`)는 렌더러(`render/`)를 import하지 않는다.** 물리↔렌더의 유일한 연결은 `SceneSnapshot`(순수 데이터) 계약뿐이다. 물리 코드가 렌더러나 OpenGL을 알게 하지 않는다.
22
+ - **공개 API는 작게 유지**한다: 처음 만나는 개념 5~6개(`World/Body/Joint/Shape/Viewer/Recorder`) 이내, 진입 예제 15줄 이내, 똑똑한 기본값, z-up·SI 단위. 새 공개 개념을 늘리기 전에 헬퍼·기본값으로 해결되는지 먼저 본다.
23
+
24
+ ## 1. 작업 방식 — 기획안(SPEC) 기반
25
+
26
+ - **전체 순서의 기준은 `docs/ROADMAP.md`**, 개별 작업의 기준은 `docs/specs/`의 해당 Phase SPEC이다. 이 둘이 source of truth다.
27
+ - 작업 전 해당 SPEC을 읽고, **거기 정의된 범위·파일·완료 조건만** 다룬다. SPEC에 없는 변경은 하지 않는다.
28
+ - **Phase 게이트: 이전 Phase의 검증 기준을 통과하기 전에는 다음 Phase로 넘어가지 않는다.** (ROADMAP의 검증 기준 표 참조)
29
+ - 여러 파일을 건드리거나 접근이 불확실한 작업은 **먼저 plan mode**로 탐색·계획하고, SPEC과 대조해 승인받은 뒤 구현한다.
30
+ - SPEC의 task를 **순서대로, 한 번에 하나씩** 구현한다. task가 끝나면 SPEC 체크박스를 갱신하고 `docs/PROGRESS.md`에 기록한다.
31
+ - **SPEC과 현실이 어긋나면 임의로 우회하지 말고 멈추고 보고한다. 깨진 계획을 땜질하지 않는다.** 필요하면 실패 지점부터 다시 계획한다.
32
+
33
+ ## 2. 엔진 작성 규칙 — 함수형 코어 (np ↔ jnp 호환의 전제)
34
+
35
+ - **in-place 변형 금지.** 상태는 입력 → 출력으로 전달한다. 배열은 불변으로 다룬다(JAX `arr.at[i].set(v)` 패턴, NumPy도 동일 추상화로 래핑).
36
+ - **난수는 명시적 키로 관리**한다(JAX PRNG 호환). 전역 난수 상태 사용 금지.
37
+ - 분기·루프는 가능하면 벡터화한다. JIT 대상 함수 안에서는 `jax.lax` 제어흐름을 쓴다.
38
+ - **모든 엔진 코드는 `ENGINE_BACKEND=numpy`와 `=jax`에서 동일하게 동작해야 한다.** 한쪽에서만 되는 코드를 작성하지 않는다.
39
+
40
+ ## 3. 백엔드 경계
41
+
42
+ - **성능 경로(권장)는 all-JAX로 통일**: jnp 엔진 + flax/equinox 정책 + optax + 전체 루프 JIT + `vmap` 다중환경.
43
+ - **torch는 실험·프로토타입(SB3) 전용.**
44
+ - **학습 핫루프에서 JAX 환경 + torch 정책 혼합 금지** (매 스텝 host↔device 동기화로 JIT 이점 소멸). 경계 변환이 꼭 필요하면 DLPack, 단 프로토타이핑에 한함.
45
+
46
+ ## 4. 검증 — YOU MUST
47
+
48
+ - **"완료/통과/동작함"이라고 말하기 전에 반드시 실제로 실행하고 증거(명령어 + 출력)를 보여준다.** 실행하지 않은 채 통과를 주장하지 않는다.
49
+ - 변경 후 순서: `[pytest]` → `[ruff check]` → `[mypy]`. 셋 다 통과해야 완료.
50
+ - 물리 코드의 정확성 게이트(해당 시 반드시 추가):
51
+ - **보존 법칙**: 무토크·무감쇠에서 에너지 보존, 외력 없을 때 운동량 보존.
52
+ - **해석해 대조**: 단진자/이중진자 닫힌 해, RNEA vs 손유도(SymPy).
53
+ - **기준 엔진 대조**: `validation/`에서 동일 입력을 PyBullet/MuJoCo와 허용오차 내 비교.
54
+ - **백엔드 일치**: 같은 입력에 대해 np 결과와 jnp 결과가 수치 허용오차 내 일치.
55
+ - 렌더링·API 게이트(해당 시):
56
+ - **렌더러 계약**: 동일 `SceneSnapshot`이 두 렌더러에서 일관된 장면(실시간=빠른 미리보기, HQ=골든 이미지 비교) — 회귀 테스트.
57
+ - **API 사용성**: `examples/`의 예제가 *라이브러리 내부를 안 건드리고* 동작(15줄 이내). 이게 추상화가 살아있다는 증거다.
58
+
59
+ ## 5. 명령어 (Claude가 추측할 수 없는 것)
60
+
61
+ - 백엔드 스위치: `ENGINE_BACKEND=numpy` 또는 `ENGINE_BACKEND=jax` (기본 numpy).
62
+ - 공개 API 스모크: `python -c "import forge3d"` 가 동작해야 한다.
63
+ - 테스트(단일 우선, 성능 위해 전체 스위트 지양): `[pytest tests/test_xxx.py -q]`
64
+ - 린트/포맷: `[ruff check . && ruff format --check .]`
65
+ - 타입: `[mypy src/]`
66
+ - 컨테이너: `[docker compose run --rm dev pytest -q]`
67
+ - CPU 멀티코어: `XLA_FLAGS`, `OMP_NUM_THREADS` 등 스레드 환경변수를 명시적으로 설정.
68
+
69
+ ## 6. 금지 / 사용자 확인 필수
70
+
71
+ - 확인 없이 금지: 파일·데이터 삭제, `git push --force`, 시크릿/`.env` 커밋, Docker 베이스 이미지 교체.
72
+ - 코드·체크포인트·로그는 **볼륨 마운트**로 컨테이너 밖에 보존한다. 컨테이너 내부에만 남기지 않는다.
73
+ - **물리·학습 연산에 GPU/CUDA 의존성 추가 금지**(이 환경은 CPU 전용). JAX·PyTorch는 CPU 휠만 설치. (그래픽용 OpenGL은 §0의 해석에 따라 허용 — 단 헤드리스 가용성은 확인 후 사용.)
74
+
75
+ ## 7. 저장소 규칙
76
+
77
+ - 브랜치: `[phase-N/<요약>]` / 커밋: `[type(scope): 요약]` / 한 PR = 한 Phase 또는 그 하위 task 묶음.
@@ -0,0 +1,69 @@
1
+ # Contributing to forge3d
2
+
3
+ Thank you for your interest in contributing!
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ git clone https://github.com/your-org/forge3d
9
+ cd forge3d
10
+ pip install -e ".[dev]"
11
+ ```
12
+
13
+ ## Workflow
14
+
15
+ 1. Fork the repo and create a feature branch: `git checkout -b feat/my-feature`
16
+ 2. Write your changes and corresponding tests
17
+ 3. Make sure the checks below pass
18
+ 4. Open a PR — one PR per logical change
19
+
20
+ ## Checks (must all pass)
21
+
22
+ ```bash
23
+ ruff check . && ruff format --check . # lint + format
24
+ mypy src/ # type checking
25
+ pytest tests/ -q # test suite
26
+ ```
27
+
28
+ ## Rules
29
+
30
+ ### Physics code
31
+ - Every new formula needs a unit test that checks against an analytical solution,
32
+ a conservation law (energy / momentum), or a PyBullet/MuJoCo baseline
33
+ (`validation/` directory).
34
+ - No in-place mutation of arrays. Use `dataclasses.replace(body, vel=new_vel)`.
35
+ - Must run correctly under both `ENGINE_BACKEND=numpy` and `ENGINE_BACKEND=jax`.
36
+
37
+ ### Renderer
38
+ - Physics core (`math/`, `dynamics/`, `collision/`, `contact/`, `model/`, `sim/`)
39
+ must **never** import renderer code.
40
+ - The only allowed bridge is `SceneSnapshot` (pure data).
41
+
42
+ ### Public API
43
+ - New public concepts require an entry in `__all__` in `src/forge3d/__init__.py`.
44
+ - New public concepts need a usage example in `examples/` (≤ 15 lines).
45
+
46
+ ### Forbidden
47
+ - External physics engines in `src/forge3d/` (MuJoCo, PyBullet, Bullet, etc.).
48
+ They are allowed only in `validation/` for baseline comparison.
49
+ - GPU/CUDA dependencies for physics or learning code.
50
+
51
+ ## Code style
52
+
53
+ - [ruff](https://docs.astral.sh/ruff/) for lint and formatting (line length 100).
54
+ - Type annotations on all public functions.
55
+ - Comments only when the *why* is non-obvious.
56
+ - No docstrings for trivial getters / setters.
57
+
58
+ ## Commit style
59
+
60
+ ```
61
+ feat(collision): add sweep-and-prune broad-phase
62
+ fix(contact): clamp Baumgarte correction to avoid tunnelling
63
+ docs(readme): add App-style game loop example
64
+ test(physics): add conservation test for capsule-capsule
65
+ ```
66
+
67
+ ## Questions?
68
+
69
+ Open an issue or start a Discussion on GitHub.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 forge3d contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.