hyperstellar 1.5.7__cp315-cp315-win_amd64.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.
- hyperstellar/__init__.py +312 -0
- hyperstellar/__init__.pyi +1019 -0
- hyperstellar/_native/windows-x64/shaders/axis.frag +8 -0
- hyperstellar/_native/windows-x64/shaders/axis.vert +13 -0
- hyperstellar/_native/windows-x64/shaders/fragShader.frag +27 -0
- hyperstellar/_native/windows-x64/shaders/geoShader.geom +69 -0
- hyperstellar/_native/windows-x64/shaders/math.comp +2177 -0
- hyperstellar/_native/windows-x64/shaders/paint.comp +1214 -0
- hyperstellar/_native/windows-x64/shaders/quad.frag +26 -0
- hyperstellar/_native/windows-x64/shaders/quad.geom +149 -0
- hyperstellar/_native/windows-x64/shaders/quad.vert +39 -0
- hyperstellar/_native/windows-x64/shaders/vertexShader.vert +13 -0
- hyperstellar/_native/windows-x64/stellar.pyd +0 -0
- hyperstellar/debug.py +169 -0
- hyperstellar/jit.py +2010 -0
- hyperstellar/jit_linter.py +3 -0
- hyperstellar/py.typed +0 -0
- hyperstellar/shader.py +1356 -0
- hyperstellar/typed.py +152 -0
- hyperstellar-1.5.7.dist-info/DELVEWHEEL +2 -0
- hyperstellar-1.5.7.dist-info/METADATA +450 -0
- hyperstellar-1.5.7.dist-info/RECORD +26 -0
- hyperstellar-1.5.7.dist-info/WHEEL +5 -0
- hyperstellar-1.5.7.dist-info/licenses/LICENSE +24 -0
- hyperstellar-1.5.7.dist-info/top_level.txt +1 -0
- hyperstellar.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
hyperstellar/__init__.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# hyperstellar/src/hyperstellar/__init__.py
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# start delvewheel patch
|
|
5
|
+
def _delvewheel_patch_1_13_0():
|
|
6
|
+
import os
|
|
7
|
+
if os.path.isdir(libs_dir := os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'hyperstellar.libs'))):
|
|
8
|
+
os.add_dll_directory(libs_dir)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_delvewheel_patch_1_13_0()
|
|
12
|
+
del _delvewheel_patch_1_13_0
|
|
13
|
+
# end delvewheel patch
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
import platform
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import List, Dict, Any, Iterator, Optional, Union
|
|
21
|
+
|
|
22
|
+
# Import the decorator function from jit.py (module-level)
|
|
23
|
+
from .jit import script as _script_func
|
|
24
|
+
|
|
25
|
+
__version__ = "1.5.7"
|
|
26
|
+
|
|
27
|
+
# Platform detection
|
|
28
|
+
system = platform.system().lower()
|
|
29
|
+
arch = platform.machine().lower()
|
|
30
|
+
|
|
31
|
+
if system == "windows" and ("amd64" in arch or "x86_64" in arch):
|
|
32
|
+
platform_dir = "windows-x64"
|
|
33
|
+
lib_name = "stellar.pyd"
|
|
34
|
+
elif system == "linux" and ("x86_64" in arch or "amd64" in arch):
|
|
35
|
+
platform_dir = "linux-x64"
|
|
36
|
+
lib_name = "stellar.so"
|
|
37
|
+
elif system == "darwin":
|
|
38
|
+
platform_dir = "macos-x64"
|
|
39
|
+
lib_name = "stellar.so"
|
|
40
|
+
else:
|
|
41
|
+
raise ImportError(f"Unsupported platform: {system} {arch}")
|
|
42
|
+
|
|
43
|
+
# Native module path
|
|
44
|
+
module_dir = Path(__file__).parent / "_native" / platform_dir
|
|
45
|
+
module_path = module_dir / lib_name
|
|
46
|
+
|
|
47
|
+
if not module_path.exists():
|
|
48
|
+
raise ImportError(
|
|
49
|
+
f"Native module not found: {module_path}\n"
|
|
50
|
+
f"Platform detected: {system} {arch}\n"
|
|
51
|
+
f"Expected: {platform_dir}/{lib_name}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Windows: help loader find DLL dependencies
|
|
55
|
+
if system == "windows":
|
|
56
|
+
dll_dir = str(module_dir)
|
|
57
|
+
os.environ['PATH'] = dll_dir + ';' + os.environ.get('PATH', '')
|
|
58
|
+
try:
|
|
59
|
+
os.add_dll_directory(dll_dir)
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# ----------------------------------------------------------------------
|
|
64
|
+
# Determine if shader cache already exists
|
|
65
|
+
# ----------------------------------------------------------------------
|
|
66
|
+
cache_dir = None
|
|
67
|
+
if system == "windows":
|
|
68
|
+
local_appdata = os.environ.get("LOCALAPPDATA")
|
|
69
|
+
if local_appdata:
|
|
70
|
+
cache_dir = Path(local_appdata) / "hyperstellar" / "cache"
|
|
71
|
+
else: # Linux/macOS
|
|
72
|
+
home = os.environ.get("HOME")
|
|
73
|
+
if home:
|
|
74
|
+
cache_dir = Path(home) / ".cache" / "hyperstellar"
|
|
75
|
+
|
|
76
|
+
has_cache = False
|
|
77
|
+
if cache_dir and cache_dir.exists():
|
|
78
|
+
has_cache = any(cache_dir.glob("*.bin"))
|
|
79
|
+
|
|
80
|
+
if not has_cache:
|
|
81
|
+
sys.stderr.write(
|
|
82
|
+
f"hyperstellar {__version__}: First-time shader compilation may take 30-60 seconds on older GPUs.\n"
|
|
83
|
+
f" Subsequent runs will be instant (binary cache).\n"
|
|
84
|
+
f"Compiling... "
|
|
85
|
+
)
|
|
86
|
+
sys.stderr.flush()
|
|
87
|
+
else:
|
|
88
|
+
sys.stderr.write(f"hyperstellar {__version__}: Loading cached shaders... ")
|
|
89
|
+
sys.stderr.flush()
|
|
90
|
+
|
|
91
|
+
start_time = time.perf_counter()
|
|
92
|
+
|
|
93
|
+
# Import native module
|
|
94
|
+
try:
|
|
95
|
+
sys.modules.pop('stellar', None)
|
|
96
|
+
sys.path.insert(0, str(module_dir))
|
|
97
|
+
import stellar as _stellar_module
|
|
98
|
+
sys.path.pop(0)
|
|
99
|
+
|
|
100
|
+
# Expose everything from the native module
|
|
101
|
+
for attr_name in dir(_stellar_module):
|
|
102
|
+
if not attr_name.startswith('__'):
|
|
103
|
+
globals()[attr_name] = getattr(_stellar_module, attr_name)
|
|
104
|
+
|
|
105
|
+
# Define __all__
|
|
106
|
+
if hasattr(_stellar_module, '__all__'):
|
|
107
|
+
__all__ = _stellar_module.__all__ + ['script', 'EquationPresets']
|
|
108
|
+
else:
|
|
109
|
+
__all__ = ['script', 'EquationPresets']
|
|
110
|
+
|
|
111
|
+
# Attach @sim.script decorator to the Simulation class
|
|
112
|
+
def _script_method(self, mode='object', debug=False):
|
|
113
|
+
def decorator(func):
|
|
114
|
+
return _script_func(self, debug=debug, mode=mode)(func)
|
|
115
|
+
return decorator
|
|
116
|
+
|
|
117
|
+
Simulation.script = _script_method
|
|
118
|
+
|
|
119
|
+
# =====================================================================
|
|
120
|
+
# ITERATION PROTOCOL
|
|
121
|
+
# =====================================================================
|
|
122
|
+
def _sim_iter(self):
|
|
123
|
+
for i in range(self.object_count()):
|
|
124
|
+
yield self.get_object(i)
|
|
125
|
+
|
|
126
|
+
Simulation.__iter__ = _sim_iter
|
|
127
|
+
Simulation.__len__ = lambda self: self.object_count()
|
|
128
|
+
|
|
129
|
+
# =====================================================================
|
|
130
|
+
# CONTEXT MANAGER
|
|
131
|
+
# =====================================================================
|
|
132
|
+
def _sim_enter(self):
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def _sim_exit(self, exc_type, exc_val, exc_tb):
|
|
136
|
+
self.cleanup()
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
Simulation.__enter__ = _sim_enter
|
|
140
|
+
Simulation.__exit__ = _sim_exit
|
|
141
|
+
|
|
142
|
+
# =====================================================================
|
|
143
|
+
# DUPLICATE_OBJECT (uses native get_all_objects, not overwritten)
|
|
144
|
+
# =====================================================================
|
|
145
|
+
def _duplicate_object(self, obj: Union[int, 'ObjectHandle'], **overrides):
|
|
146
|
+
"""
|
|
147
|
+
Duplicate an existing object with optional property overrides.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
obj: Object ID (raw index or ObjectHandle)
|
|
151
|
+
**overrides: Any property to override (x, y, vx, vy, mass, etc.)
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
ObjectHandle: Handle to the new object
|
|
155
|
+
"""
|
|
156
|
+
state = self.get_object(obj) # accepts both raw int and handle
|
|
157
|
+
kwargs = {
|
|
158
|
+
'x': state.x, 'y': state.y,
|
|
159
|
+
'vx': state.vx, 'vy': state.vy,
|
|
160
|
+
'mass': state.mass, 'charge': state.charge,
|
|
161
|
+
'rotation': state.rotation,
|
|
162
|
+
'angular_velocity': state.angular_velocity,
|
|
163
|
+
'skin': state.skin_type,
|
|
164
|
+
'size': state.radius,
|
|
165
|
+
'width': state.width, 'height': state.height,
|
|
166
|
+
'r': state.r, 'g': state.g, 'b': state.b, 'a': state.a,
|
|
167
|
+
'polygon_sides': state.polygon_sides
|
|
168
|
+
}
|
|
169
|
+
kwargs.update(overrides)
|
|
170
|
+
return self.add_object(**kwargs)
|
|
171
|
+
|
|
172
|
+
Simulation.duplicate_object = _duplicate_object
|
|
173
|
+
|
|
174
|
+
# =====================================================================
|
|
175
|
+
# EQUATION PRESETS
|
|
176
|
+
# =====================================================================
|
|
177
|
+
class EquationPresets:
|
|
178
|
+
@staticmethod
|
|
179
|
+
def gravity(G=1.0, eps=0.001):
|
|
180
|
+
return f"ax = -{G} * x / (x*x + y*y + {eps})**1.5; ay = -{G} * y / (x*x + y*y + {eps})**1.5"
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def spring(k=1.0):
|
|
184
|
+
return f"ax = -{k} * x; ay = -{k} * y"
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def damped_spring(k=1.0, damping=0.1):
|
|
188
|
+
return f"ax = -{k} * x - {damping} * vx; ay = -{k} * y - {damping} * vy"
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def orbit(G=1.0, eps=0.001):
|
|
192
|
+
# Note: this function does NOT accept a 'target' argument; target is handled in set_equation_named.
|
|
193
|
+
return (f"dx = target.x - x; dy = target.y - y; r2 = dx*dx + dy*dy + {eps}; "
|
|
194
|
+
f"r = sqrt(r2); ax = {G} * target.mass * dx / (r * r2); ay = {G} * target.mass * dy / (r * r2)")
|
|
195
|
+
|
|
196
|
+
globals()['EquationPresets'] = EquationPresets
|
|
197
|
+
|
|
198
|
+
# =====================================================================
|
|
199
|
+
# OBJECT TAGS/GROUPS (handle‑only, no raw indices)
|
|
200
|
+
# =====================================================================
|
|
201
|
+
class _ObjectTags:
|
|
202
|
+
"""
|
|
203
|
+
Lightweight Python-side object tagging system.
|
|
204
|
+
Only ObjectHandles should be stored – raw indices are not accepted.
|
|
205
|
+
"""
|
|
206
|
+
def __init__(self, sim):
|
|
207
|
+
self.sim = sim
|
|
208
|
+
self._tags: Dict[str, List['ObjectHandle']] = {}
|
|
209
|
+
|
|
210
|
+
def add(self, tag: str, obj: 'ObjectHandle'):
|
|
211
|
+
if not isinstance(obj, ObjectHandle):
|
|
212
|
+
raise TypeError("Only ObjectHandle can be tagged; use add_object() to get a handle.")
|
|
213
|
+
if tag not in self._tags:
|
|
214
|
+
self._tags[tag] = []
|
|
215
|
+
if obj not in self._tags[tag]:
|
|
216
|
+
self._tags[tag].append(obj)
|
|
217
|
+
|
|
218
|
+
def remove(self, tag: str, obj: 'ObjectHandle'):
|
|
219
|
+
if tag in self._tags and obj in self._tags[tag]:
|
|
220
|
+
self._tags[tag].remove(obj)
|
|
221
|
+
if not self._tags[tag]:
|
|
222
|
+
del self._tags[tag]
|
|
223
|
+
|
|
224
|
+
def get(self, tag: str) -> List['ObjectHandle']:
|
|
225
|
+
return self._tags.get(tag, []).copy()
|
|
226
|
+
|
|
227
|
+
def clear(self, tag: str = None):
|
|
228
|
+
if tag is None:
|
|
229
|
+
self._tags.clear()
|
|
230
|
+
elif tag in self._tags:
|
|
231
|
+
del self._tags[tag]
|
|
232
|
+
|
|
233
|
+
def __contains__(self, tag: str) -> bool:
|
|
234
|
+
return tag in self._tags
|
|
235
|
+
|
|
236
|
+
def __iter__(self):
|
|
237
|
+
return iter(self._tags.items())
|
|
238
|
+
|
|
239
|
+
def __repr__(self):
|
|
240
|
+
return f"<ObjectTags tags={list(self._tags.keys())}>"
|
|
241
|
+
|
|
242
|
+
def _get_tags(self):
|
|
243
|
+
if not hasattr(self, '_tags_storage'):
|
|
244
|
+
self._tags_storage = _ObjectTags(self)
|
|
245
|
+
return self._tags_storage
|
|
246
|
+
|
|
247
|
+
Simulation.tags = property(_get_tags)
|
|
248
|
+
|
|
249
|
+
# =====================================================================
|
|
250
|
+
# REMOVE TAGGED
|
|
251
|
+
# =====================================================================
|
|
252
|
+
def _remove_tagged(self, tag: str):
|
|
253
|
+
if tag in self.tags:
|
|
254
|
+
handles = self.tags.get(tag)
|
|
255
|
+
if handles:
|
|
256
|
+
self.remove_objects(handles)
|
|
257
|
+
self.tags.clear(tag)
|
|
258
|
+
|
|
259
|
+
Simulation.remove_tagged = _remove_tagged
|
|
260
|
+
|
|
261
|
+
# =====================================================================
|
|
262
|
+
# NAMED EQUATION SETTER (with proper target handling for orbit)
|
|
263
|
+
# =====================================================================
|
|
264
|
+
def _set_equation_named(self, obj, preset_name: str, **kwargs):
|
|
265
|
+
"""
|
|
266
|
+
Set a physics equation using a named preset.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
obj: Object ID or ObjectHandle
|
|
270
|
+
preset_name (str): "gravity", "spring", "damped_spring", or "orbit"
|
|
271
|
+
**kwargs: Parameters to substitute. For orbit, pass `target=other_handle`.
|
|
272
|
+
"""
|
|
273
|
+
# Extract target early for orbit, since orbit's function signature doesn't accept it.
|
|
274
|
+
target = kwargs.pop('target', None) if preset_name == 'orbit' else None
|
|
275
|
+
|
|
276
|
+
if preset_name == "gravity":
|
|
277
|
+
base = EquationPresets.gravity(**kwargs)
|
|
278
|
+
elif preset_name == "spring":
|
|
279
|
+
base = EquationPresets.spring(**kwargs)
|
|
280
|
+
elif preset_name == "damped_spring":
|
|
281
|
+
base = EquationPresets.damped_spring(**kwargs)
|
|
282
|
+
elif preset_name == "orbit":
|
|
283
|
+
base = EquationPresets.orbit(**kwargs)
|
|
284
|
+
if target is not None:
|
|
285
|
+
# target can be a raw int or a handle; convert to p[index] syntax.
|
|
286
|
+
if hasattr(target, 'slot'): # ObjectHandle
|
|
287
|
+
idx = target.slot
|
|
288
|
+
else:
|
|
289
|
+
idx = int(target) # raw index
|
|
290
|
+
base = base.replace('target', f'p[{idx}]')
|
|
291
|
+
else:
|
|
292
|
+
raise ValueError("orbit preset requires a 'target' object (pass target=handle)")
|
|
293
|
+
else:
|
|
294
|
+
raise ValueError(f"Unknown preset: {preset_name}. Available: gravity, spring, damped_spring, orbit")
|
|
295
|
+
|
|
296
|
+
self.set_equation(obj, base)
|
|
297
|
+
|
|
298
|
+
Simulation.set_equation_named = _set_equation_named
|
|
299
|
+
|
|
300
|
+
except ImportError as e:
|
|
301
|
+
raise ImportError(
|
|
302
|
+
f"Failed to load native module: {e}\n"
|
|
303
|
+
f"Module path: {module_path}\n"
|
|
304
|
+
f"Platform: {system} {arch}"
|
|
305
|
+
)
|
|
306
|
+
except Exception as e:
|
|
307
|
+
raise ImportError(f"Unexpected error loading hyperstellar: {e}")
|
|
308
|
+
|
|
309
|
+
# Print completion time
|
|
310
|
+
elapsed = time.perf_counter() - start_time
|
|
311
|
+
sys.stderr.write(f" done in {elapsed:.1f}s.\n")
|
|
312
|
+
sys.stderr.flush()
|