pyforge3d 1.0.0__py3-none-any.whl
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.
- forge3d/__init__.py +105 -0
- forge3d/app.py +219 -0
- forge3d/backend.py +118 -0
- forge3d/camera.py +235 -0
- forge3d/collision/__init__.py +20 -0
- forge3d/collision/detection.py +739 -0
- forge3d/collision/epa.py +217 -0
- forge3d/collision/gjk.py +266 -0
- forge3d/collision/heightfield.py +196 -0
- forge3d/collision/layers.py +38 -0
- forge3d/constraints/__init__.py +35 -0
- forge3d/constraints/base.py +132 -0
- forge3d/constraints/joints.py +635 -0
- forge3d/contact/__init__.py +1 -0
- forge3d/contact/solver.py +341 -0
- forge3d/dynamics/__init__.py +24 -0
- forge3d/dynamics/aba.py +108 -0
- forge3d/dynamics/crba.py +73 -0
- forge3d/dynamics/model.py +97 -0
- forge3d/dynamics/rnea.py +260 -0
- forge3d/errors.py +98 -0
- forge3d/events.py +267 -0
- forge3d/facade.py +1032 -0
- forge3d/input.py +243 -0
- forge3d/io/__init__.py +10 -0
- forge3d/io/mesh_data.py +206 -0
- forge3d/io/obj_loader.py +203 -0
- forge3d/io/world_snapshot.py +284 -0
- forge3d/logging.py +35 -0
- forge3d/math/__init__.py +59 -0
- forge3d/math/inertia.py +60 -0
- forge3d/math/quaternion.py +141 -0
- forge3d/math/se3.py +162 -0
- forge3d/math/spatial.py +139 -0
- forge3d/model/__init__.py +14 -0
- forge3d/model/kinematics.py +134 -0
- forge3d/model/robot_config.py +138 -0
- forge3d/model/urdf_loader.py +194 -0
- forge3d/py.typed +0 -0
- forge3d/recorder.py +204 -0
- forge3d/render/__init__.py +1 -0
- forge3d/render/base.py +36 -0
- forge3d/render/hq/__init__.py +1 -0
- forge3d/render/hq/raytracer.py +297 -0
- forge3d/render/hq/renderer.py +72 -0
- forge3d/render/hq/scene.py +138 -0
- forge3d/render/realtime/__init__.py +5 -0
- forge3d/render/realtime/context.py +119 -0
- forge3d/render/realtime/meshes.py +254 -0
- forge3d/render/realtime/renderer.py +565 -0
- forge3d/render/realtime/shaders.py +193 -0
- forge3d/render/snapshot.py +91 -0
- forge3d/robot/__init__.py +38 -0
- forge3d/robot/presets.py +82 -0
- forge3d/robot/robot.py +162 -0
- forge3d/sim/__init__.py +1 -0
- forge3d/sim/domain_rand.py +113 -0
- forge3d/sim/jax_batch.py +191 -0
- forge3d/sim/world.py +626 -0
- forge3d/viewer.py +235 -0
- pyforge3d-1.0.0.dist-info/METADATA +566 -0
- pyforge3d-1.0.0.dist-info/RECORD +64 -0
- pyforge3d-1.0.0.dist-info/WHEEL +4 -0
- pyforge3d-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Collision layer and mask bit-field constants.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
box.collision_layer = forge3d.CollisionLayer.DEFAULT
|
|
6
|
+
box.collision_mask = forge3d.CollisionLayer.DEFAULT | forge3d.CollisionLayer.PLAYER
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CollisionLayer:
|
|
13
|
+
"""Named bit-field constants for collision layers.
|
|
14
|
+
|
|
15
|
+
Collision rule: bodies A and B collide if and only if:
|
|
16
|
+
``(A.collision_layer & B.collision_mask) != 0``
|
|
17
|
+
and
|
|
18
|
+
``(B.collision_layer & A.collision_mask) != 0``
|
|
19
|
+
|
|
20
|
+
The default is layer 0x0001 with mask 0xFFFF (collide with everything).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
DEFAULT = 1 << 0 # 0x0001 — default layer for all bodies
|
|
24
|
+
PLAYER = 1 << 1 # 0x0002
|
|
25
|
+
ENEMY = 1 << 2 # 0x0004
|
|
26
|
+
BULLET = 1 << 3 # 0x0008
|
|
27
|
+
TRIGGER = 1 << 4 # 0x0010
|
|
28
|
+
TERRAIN = 1 << 5 # 0x0020
|
|
29
|
+
DEBRIS = 1 << 6 # 0x0040
|
|
30
|
+
SENSOR = 1 << 7 # 0x0080
|
|
31
|
+
|
|
32
|
+
NONE = 0
|
|
33
|
+
ALL = 0xFFFF # collide with all 16 standard layers
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def layers_collide(layer_a: int, mask_a: int, layer_b: int, mask_b: int) -> bool:
|
|
37
|
+
"""Return True if body a and body b should collide."""
|
|
38
|
+
return bool((layer_a & mask_b) != 0 and (layer_b & mask_a) != 0)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""forge3d constraint / joint system.
|
|
2
|
+
|
|
3
|
+
All constraints use velocity-level Sequential Impulse with
|
|
4
|
+
Baumgarte position-error stabilization.
|
|
5
|
+
|
|
6
|
+
Supported joint types
|
|
7
|
+
---------------------
|
|
8
|
+
FixedJoint — rigid weld (0 DOF)
|
|
9
|
+
BallJoint — ball-and-socket (3 rotational DOF)
|
|
10
|
+
HingeJoint — revolute (1 rotational DOF) with optional limits + motor
|
|
11
|
+
PrismaticJoint — slider (1 translational DOF) with optional limits + motor
|
|
12
|
+
DistanceJoint — maintain distance between two anchor points
|
|
13
|
+
SpringJoint — spring-damper force element (not a hard constraint)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from forge3d.constraints.base import Constraint, JointHandle
|
|
17
|
+
from forge3d.constraints.joints import (
|
|
18
|
+
BallJoint,
|
|
19
|
+
DistanceJoint,
|
|
20
|
+
FixedJoint,
|
|
21
|
+
HingeJoint,
|
|
22
|
+
PrismaticJoint,
|
|
23
|
+
SpringJoint,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"Constraint",
|
|
28
|
+
"JointHandle",
|
|
29
|
+
"FixedJoint",
|
|
30
|
+
"BallJoint",
|
|
31
|
+
"HingeJoint",
|
|
32
|
+
"PrismaticJoint",
|
|
33
|
+
"DistanceJoint",
|
|
34
|
+
"SpringJoint",
|
|
35
|
+
]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Base classes for the constraint / joint system."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from forge3d.sim.world import _Body
|
|
13
|
+
|
|
14
|
+
# Baumgarte stabilization — position error fed back as velocity bias
|
|
15
|
+
BAUMGARTE_BETA = 0.05 # conservative: avoids instability while still correcting drift
|
|
16
|
+
BAUMGARTE_SLOP = 1e-3 # 1 mm dead-zone
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class JointHandle:
|
|
21
|
+
"""Opaque handle returned by World.add_joint().
|
|
22
|
+
|
|
23
|
+
Keep it to later call world.remove_joint(handle).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
joint_id: int
|
|
27
|
+
joint_type: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Constraint(ABC):
|
|
31
|
+
"""Abstract base for all velocity-level constraints.
|
|
32
|
+
|
|
33
|
+
Sub-classes must implement ``apply(bodies, dt)``, which modifies
|
|
34
|
+
the body list in-place (replace immutable _Body with updated copies).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
joint_id: int = field(default=-1)
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def apply(self, bodies: list[Any], id_to_idx: dict[int, int], dt: float) -> None:
|
|
41
|
+
"""Apply constraint impulses to the body list.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
bodies: Flat list of ``_Body`` objects (may be mutated via replace).
|
|
45
|
+
id_to_idx: Map from body_id to list index.
|
|
46
|
+
dt: Physics time-step in seconds.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# ── Helper: compute world-frame inertia inverse ───────────────────────────
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _I_world_inv(b: Any) -> np.ndarray:
|
|
53
|
+
"""World-frame inverse inertia (3×3).
|
|
54
|
+
|
|
55
|
+
For point masses or static bodies returns zero matrix.
|
|
56
|
+
"""
|
|
57
|
+
if b.static or b.inertia_inv_local is None:
|
|
58
|
+
return np.zeros((3, 3))
|
|
59
|
+
R = _quat_to_rot(b.quat)
|
|
60
|
+
I_inv_local = b.inertia_inv_local # diagonal (3×3)
|
|
61
|
+
return R @ I_inv_local @ R.T
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _effective_mass_ball(
|
|
65
|
+
b_a: Any, r_a: np.ndarray, b_b: Any, r_b: np.ndarray
|
|
66
|
+
) -> np.ndarray:
|
|
67
|
+
"""Effective mass matrix K (3×3) for a point-to-point constraint.
|
|
68
|
+
|
|
69
|
+
K = K_a + K_b where K_x = 1/m_x * I + [r_x×] * I_x_world_inv * [r_x×]ᵀ
|
|
70
|
+
"""
|
|
71
|
+
def _k(b: Any, r: np.ndarray) -> np.ndarray:
|
|
72
|
+
if b.static:
|
|
73
|
+
return np.zeros((3, 3))
|
|
74
|
+
m_inv = 1.0 / b.mass if b.mass > 0 else 0.0
|
|
75
|
+
I_inv = Constraint._I_world_inv(b)
|
|
76
|
+
rx = _skew(r)
|
|
77
|
+
return m_inv * np.eye(3) + rx @ I_inv @ rx.T
|
|
78
|
+
|
|
79
|
+
return _k(b_a, r_a) + _k(b_b, r_b)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _apply_impulse_pair(
|
|
83
|
+
bodies: list[Any],
|
|
84
|
+
id_to_idx: dict[int, int],
|
|
85
|
+
id_a: int,
|
|
86
|
+
id_b: int,
|
|
87
|
+
impulse: np.ndarray,
|
|
88
|
+
r_a: np.ndarray,
|
|
89
|
+
r_b: np.ndarray,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Apply linear impulse J and angular reactions to body pair."""
|
|
92
|
+
from dataclasses import replace
|
|
93
|
+
|
|
94
|
+
if id_a >= 0:
|
|
95
|
+
idx_a = id_to_idx[id_a]
|
|
96
|
+
ba = bodies[idx_a]
|
|
97
|
+
if not ba.static and ba.mass > 0:
|
|
98
|
+
dv = impulse / ba.mass
|
|
99
|
+
I_inv = Constraint._I_world_inv(ba)
|
|
100
|
+
dw = I_inv @ np.cross(r_a, impulse)
|
|
101
|
+
bodies[idx_a] = replace(ba, vel=ba.vel + dv, omega=ba.omega + dw)
|
|
102
|
+
|
|
103
|
+
if id_b >= 0:
|
|
104
|
+
idx_b = id_to_idx[id_b]
|
|
105
|
+
bb = bodies[idx_b]
|
|
106
|
+
if not bb.static and bb.mass > 0:
|
|
107
|
+
dv = -impulse / bb.mass
|
|
108
|
+
I_inv = Constraint._I_world_inv(bb)
|
|
109
|
+
dw = I_inv @ np.cross(r_b, -impulse)
|
|
110
|
+
bodies[idx_b] = replace(bb, vel=bb.vel + dv, omega=bb.omega + dw)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ── Utility functions ─────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _quat_to_rot(q: np.ndarray) -> np.ndarray:
|
|
117
|
+
"""Quaternion [w,x,y,z] → 3×3 rotation matrix."""
|
|
118
|
+
w, x, y, z = q
|
|
119
|
+
return np.array([
|
|
120
|
+
[1 - 2*(y*y + z*z), 2*(x*y - w*z), 2*(x*z + w*y)],
|
|
121
|
+
[2*(x*y + w*z), 1 - 2*(x*x + z*z), 2*(y*z - w*x)],
|
|
122
|
+
[2*(x*z - w*y), 2*(y*z + w*x), 1 - 2*(x*x + y*y)],
|
|
123
|
+
])
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _skew(v: np.ndarray) -> np.ndarray:
|
|
127
|
+
"""Skew-symmetric cross-product matrix."""
|
|
128
|
+
return np.array([
|
|
129
|
+
[ 0.0, -v[2], v[1]],
|
|
130
|
+
[ v[2], 0.0, -v[0]],
|
|
131
|
+
[-v[1], v[0], 0.0],
|
|
132
|
+
])
|