OpenSceneGraph 0.1.2__cp313-cp313-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.
Files changed (90) hide show
  1. OpenSceneGraph/OpenThreads.lib +0 -0
  2. OpenSceneGraph/_OpenSceneGraph.cp313-win_amd64.pyd +0 -0
  3. OpenSceneGraph/__init__.py +52 -0
  4. OpenSceneGraph/aipython/00-index.md +44 -0
  5. OpenSceneGraph/aipython/01-core.md +255 -0
  6. OpenSceneGraph/aipython/02-inspect.md +81 -0
  7. OpenSceneGraph/aipython/03-headless-frames.md +149 -0
  8. OpenSceneGraph/aipython/05-camera-manipulator.md +68 -0
  9. OpenSceneGraph/aipython/06-camera-effects.md +132 -0
  10. OpenSceneGraph/aipython/07-camera-manual.md +88 -0
  11. OpenSceneGraph/aipython/08-lighting.md +121 -0
  12. OpenSceneGraph/aipython/09-picking.md +90 -0
  13. OpenSceneGraph/aipython/10-rtt.md +199 -0
  14. OpenSceneGraph/aipython/11-mrt.md +104 -0
  15. OpenSceneGraph/aipython/12-gbuffer.md +48 -0
  16. OpenSceneGraph/aipython/15-shader-hotswap.md +72 -0
  17. OpenSceneGraph/aipython/17-particles.md +189 -0
  18. OpenSceneGraph/aipython/18-deterministic-captures.md +157 -0
  19. OpenSceneGraph/aipython/20-object-lifetime.md +77 -0
  20. OpenSceneGraph/aipython/25-async-osgpy.md +228 -0
  21. OpenSceneGraph/aipython/29-material.md +109 -0
  22. OpenSceneGraph/aipython/30-pbribl.md +162 -0
  23. OpenSceneGraph/aipython/40-typed-lights-gizmos.md +218 -0
  24. OpenSceneGraph/examples/__init__.py +24 -0
  25. OpenSceneGraph/examples/__main__.py +143 -0
  26. OpenSceneGraph/examples/blur.py +370 -0
  27. OpenSceneGraph/examples/info.py +126 -0
  28. OpenSceneGraph/examples/mrt.py +493 -0
  29. OpenSceneGraph/examples/pyosg_async.py +467 -0
  30. OpenSceneGraph/examples/pyosg_example.py +120 -0
  31. OpenSceneGraph/examples/pyosg_repl.py +842 -0
  32. OpenSceneGraph/examples/pyosg_visitor.py +138 -0
  33. OpenSceneGraph/ktx.dll +0 -0
  34. OpenSceneGraph/ktx.lib +0 -0
  35. OpenSceneGraph/osg.lib +0 -0
  36. OpenSceneGraph/osg161-osg.dll +0 -0
  37. OpenSceneGraph/osg161-osgAnimation.dll +0 -0
  38. OpenSceneGraph/osg161-osgDB.dll +0 -0
  39. OpenSceneGraph/osg161-osgFX.dll +0 -0
  40. OpenSceneGraph/osg161-osgGA.dll +0 -0
  41. OpenSceneGraph/osg161-osgText.dll +0 -0
  42. OpenSceneGraph/osg161-osgUtil.dll +0 -0
  43. OpenSceneGraph/osg161-osgViewer.dll +0 -0
  44. OpenSceneGraph/osg161-osgWidget.dll +0 -0
  45. OpenSceneGraph/osgAnimation.lib +0 -0
  46. OpenSceneGraph/osgDB.lib +0 -0
  47. OpenSceneGraph/osgFX.lib +0 -0
  48. OpenSceneGraph/osgGA.lib +0 -0
  49. OpenSceneGraph/osgPlugins-3.6.5/jpeg62-ebb2f26be87097e77bafd1e9095b4820.dll +0 -0
  50. OpenSceneGraph/osgPlugins-3.6.5/ktx.dll +0 -0
  51. OpenSceneGraph/osgPlugins-3.6.5/liblzma-b9cff3753c4848b9ff1350ba5a053c6e.dll +0 -0
  52. OpenSceneGraph/osgPlugins-3.6.5/libpng16-36b7e1e8d185cb21280e5688f99660c0.dll +0 -0
  53. OpenSceneGraph/osgPlugins-3.6.5/msvcp140.dll +0 -0
  54. OpenSceneGraph/osgPlugins-3.6.5/osgdb_bmp.dll +0 -0
  55. OpenSceneGraph/osgPlugins-3.6.5/osgdb_dds.dll +0 -0
  56. OpenSceneGraph/osgPlugins-3.6.5/osgdb_gltf.dll +0 -0
  57. OpenSceneGraph/osgPlugins-3.6.5/osgdb_hdr.dll +0 -0
  58. OpenSceneGraph/osgPlugins-3.6.5/osgdb_jpeg.dll +0 -0
  59. OpenSceneGraph/osgPlugins-3.6.5/osgdb_ktx2.dll +0 -0
  60. OpenSceneGraph/osgPlugins-3.6.5/osgdb_obj.dll +0 -0
  61. OpenSceneGraph/osgPlugins-3.6.5/osgdb_osg.dll +0 -0
  62. OpenSceneGraph/osgPlugins-3.6.5/osgdb_png.dll +0 -0
  63. OpenSceneGraph/osgPlugins-3.6.5/osgdb_pnm.dll +0 -0
  64. OpenSceneGraph/osgPlugins-3.6.5/osgdb_rgb.dll +0 -0
  65. OpenSceneGraph/osgPlugins-3.6.5/osgdb_serializers_osg.dll +0 -0
  66. OpenSceneGraph/osgPlugins-3.6.5/osgdb_stl.dll +0 -0
  67. OpenSceneGraph/osgPlugins-3.6.5/osgdb_tga.dll +0 -0
  68. OpenSceneGraph/osgPlugins-3.6.5/osgdb_tiff.dll +0 -0
  69. OpenSceneGraph/osgPlugins-3.6.5/tiff-1defa8059e5ba115a3ab4b120de6f4bb.dll +0 -0
  70. OpenSceneGraph/osgPlugins-3.6.5/z.dll +0 -0
  71. OpenSceneGraph/osgText.lib +0 -0
  72. OpenSceneGraph/osgUtil.lib +0 -0
  73. OpenSceneGraph/osgViewer.lib +0 -0
  74. OpenSceneGraph/osgWidget.lib +0 -0
  75. OpenSceneGraph/osgx.cp313-win_amd64.pyd +0 -0
  76. OpenSceneGraph/osgx_static.lib +0 -0
  77. OpenSceneGraph/ot21-OpenThreads.dll +0 -0
  78. openscenegraph-0.1.2.dist-info/DELVEWHEEL +2 -0
  79. openscenegraph-0.1.2.dist-info/METADATA +563 -0
  80. openscenegraph-0.1.2.dist-info/RECORD +90 -0
  81. openscenegraph-0.1.2.dist-info/WHEEL +5 -0
  82. openscenegraph-0.1.2.dist-info/entry_points.txt +6 -0
  83. openscenegraph-0.1.2.dist-info/licenses/LICENSE +21 -0
  84. openscenegraph.libs/jpeg62-ebb2f26be87097e77bafd1e9095b4820.dll +0 -0
  85. openscenegraph.libs/liblzma-b9cff3753c4848b9ff1350ba5a053c6e.dll +0 -0
  86. openscenegraph.libs/libpng16-36b7e1e8d185cb21280e5688f99660c0.dll +0 -0
  87. openscenegraph.libs/msvcp140.dll +0 -0
  88. openscenegraph.libs/tiff-1defa8059e5ba115a3ab4b120de6f4bb.dll +0 -0
  89. openscenegraph.libs/z.dll +0 -0
  90. osgx.py +7 -0
@@ -0,0 +1,218 @@
1
+ # osgx typed direct lights (`osgx.LightSet`) + light gizmos
2
+
3
+ Check `git log -1 -- src/osgx/PBR.hpp` in the configured osgx source dir (see
4
+ the repo's `CLAUDE.md` on `PYOSG_OSGX_SOURCE_DIR`) if anything here looks
5
+ stale — this describes an SSBO-backed contract that may still be uncommitted
6
+ in some checkouts.
7
+
8
+ ## What this is
9
+
10
+ `osgx::pbr::LightSet` (C++: `src/osgx/PBR.hpp`/`src/PBR.cpp`, Python:
11
+ `osgx.LightSet`) is a typed direct-light rig. Per-light data
12
+ (`osgx.MAX_LIGHTS` == 6 slots) lives in a single `std430` Shader Storage
13
+ Buffer Object (`osgx_LightBuffer`/`osgx_lights[]`, binding 3 — C++
14
+ `osgx::LIGHT_BINDING`, not currently exposed to Python) plus one
15
+ `osgx_lightCount` uniform — not parallel flat uniform arrays. Three real
16
+ types — Point, Directional, Spot — plus a "Sphere" light that is NOT a
17
+ fourth type: it's `setPoint(..., sourceRadius>0)`, see below.
18
+
19
+ Every consumer shader reaches the light array through one hook call,
20
+ **`osgx_DirectLighting(N, V, worldPos, mat)`** — declared by
21
+ `osgx.DIRECT_LIGHTING_DECL` (splice into your fragment shader's
22
+ `#pragma osgx::pbr ...` line, alongside `MATERIAL_STRUCT`) and defined by
23
+ `osgx.DIRECT_LIGHTING_HOOK_DEFAULT`, a second self-contained FRAGMENT
24
+ shader object added to the same `Program`.
25
+ `osgx.makeDirectLightingHookShader()` returns that shader object ready
26
+ to append:
27
+
28
+ ```python
29
+ program = osg.Program(name="my-shader", shaders=(
30
+ osg.Shader(osg.Shader.VERTEX, vertex_source),
31
+ osg.Shader(osg.Shader.FRAGMENT, osgx.resolveShaderLibs(fragment_source)),
32
+ osgx.makeDirectLightingHookShader(),
33
+ ))
34
+ ```
35
+
36
+ This is the same "hook" pattern `osgSlug` uses: a base shader owns `main()`
37
+ and only *declares* a fixed entry point; each consumer supplies — or accepts
38
+ the library default for — that entry point's definition as a separate
39
+ compiled shader object. It exists so the per-light dispatch loop lives in
40
+ exactly one place instead of being hand-copied into every consumer's
41
+ `main()`.
42
+
43
+ `osgx::LightGizmos`/`osgx::LightMarkers` (C++: `src/osgx/Gizmos.hpp`/
44
+ `src/Gizmos.cpp`, Python: `osgx.LightGizmos`/`osgx.LightMarkers` — flat under
45
+ `osgx`, not a `gizmo` submodule) are the matching debug visualization: real
46
+ depth-tested marker geometry for point/sphere/spot, plus a non-depth-tested
47
+ overlay for directional (which has no position to place a marker at). Both
48
+ take a live `osgx.LightSet` directly and read it through its typed
49
+ accessors (`getCount`/`getType`/`getPosIntensity`/`getColor`/`getDirection`/
50
+ `getSpotAngles`/`getSourceRadius`).
51
+
52
+ ## Minimal live REPL setup
53
+
54
+ ```python
55
+ lights = osgx.LightSet.create(root.stateSet) # allocates the SSBO buffer (size MAX_LIGHTS, zero-initialized) + osgx_lightCount on root.stateSet
56
+
57
+ lights.setCount(1) # how many of the 6 slots osgx_DirectLighting()'s loop actually reads this frame
58
+
59
+ # Point -- inverse-square falloff, ideal (zero-size) specular highlight
60
+ lights.setPoint(0, osg.Vec3(2.5, -2.5, 6.0), osg.Vec3(0.85, 0.55, 0.30), 12.0)
61
+
62
+ # Directional -- "travel direction" (KHR_lights_punctual convention), no position, no falloff
63
+ lights.setDirectional(0, osg.Vec3(0.2, 0.4, -1.0), osg.Vec3(0.75, 0.70, 0.60), 3.0)
64
+
65
+ # Spot -- cone-attenuated point light; angles in RADIANS (use math.radians(), no osg.DegreesToRadians)
66
+ import math
67
+ lights.setSpot(
68
+ 0, osg.Vec3(0.0, -4.0, 8.0), osg.Vec3(0.0, 1.0, -0.35), osg.Vec3(0.95, 0.85, 0.65),
69
+ 40.0, math.radians(15.0), math.radians(32.0),
70
+ )
71
+
72
+ # Sphere -- NOT a separate call: setPoint(..., sourceRadius>0). Widens the SPECULAR highlight only
73
+ # (Karis/UE4 representative-point trick) -- diffuse falloff is deliberately IDENTICAL to a plain
74
+ # point light at the same position/intensity. Has visibly ZERO effect on a fully rough/matte
75
+ # surface -- that's expected, not a bug.
76
+ lights.setPoint(0, osg.Vec3(2.5, -2.5, 6.0), osg.Vec3(0.85, 0.55, 0.30), 12.0, sourceRadius=0.7)
77
+
78
+ # Read back any field live:
79
+ lights.getType(0), lights.getPosIntensity(0), lights.getColor(0), lights.getSourceRadius(0)
80
+ ```
81
+
82
+ `LightSet.create()` REPLACES the SSBO buffer + `osgx_lightCount` on that
83
+ StateSet — don't call it twice on the same live StateSet if you've already
84
+ populated lights there, or you'll zero it out. To WRAP an already-populated
85
+ StateSet's LightSet instead, construct `osgx.LightSet()` and set `.ss`
86
+ directly — `LightSet` has exactly one field (`ss`); the setters/getters
87
+ mutate the SSBO/uniforms that `ss` already carries, there is no separate
88
+ `.lights` handle to assign:
89
+
90
+ ```python
91
+ lights = osgx.LightSet()
92
+ lights.ss = already_populated_stateset
93
+ ```
94
+
95
+ A rig-building helper (`pyosg-match4-dice.py`'s `add_torch_rig()`):
96
+
97
+ ```python
98
+ def add_torch_rig(stateset, torches):
99
+ """Build an osgx.LightSet on `stateset` from (position, intensity, color) tuples."""
100
+ lights = osgx.LightSet.create(stateset)
101
+
102
+ for i, (pos, intensity, color) in enumerate(torches):
103
+ lights.setPoint(i, osg.Vec3(*pos), osg.Vec3(*color), intensity)
104
+
105
+ lights.setCount(len(torches))
106
+
107
+ return lights
108
+ ```
109
+
110
+ ## Gizmos
111
+
112
+ `osgx.LightGizmos` is itself an `osg.Group` — construct it and add the one
113
+ instance to the scene graph; its constructor already parents both the marker
114
+ geometry and the directional overlay as its own children, so don't also
115
+ add `.markers`/`.overlay` separately:
116
+
117
+ ```python
118
+ gizmos = osgx.LightGizmos(lights, root, 0.4, 10.0) # (LightSet, scene node for directional's bound-sizing, minMarkerRadius, spotConeLength)
119
+
120
+ root.children.append(gizmos)
121
+ ```
122
+
123
+ `.markers` (an `osgx.LightMarkers`) and `.overlay` (an `osg.Camera`) are
124
+ read-only properties for reaching the two pieces individually — e.g. to
125
+ toggle visibility:
126
+
127
+ - `gizmos.markers`' point/spot markers are real scene geometry and DO get
128
+ depth-occluded by actual room geometry (no shadow-mapping in this system,
129
+ so occlusion only affects the debug marker's visibility, never the shading
130
+ math). Keep test-light Z below the real ceiling if you want to see the
131
+ marker.
132
+ - `gizmos.overlay` is unaffected (`GL_DEPTH_TEST` off, `POST_RENDER`).
133
+ - Toggle `gizmos.markers.nodeMask = 0` / `gizmos.overlay.nodeMask = 0` to
134
+ hide them without removing them.
135
+ - `LightGizmos::computeBound()` is overridden to always return an empty
136
+ bound — gizmo geometry deliberately never affects `TrackballManipulator`
137
+ auto-fit/`home()` or cull's near/far, even though the overlay's own
138
+ plane/arrow are sized larger than the scene it annotates.
139
+
140
+ ## Live ImGui control panel
141
+
142
+ `osgx.imgui`'s Python bindings are function-style wrappers (`slider_float`,
143
+ `color_edit3`, `checkbox`, `radio_group`, `button`, `text`, `separator`,
144
+ `input_text` — all return `(changed, *new_values)` tuples), not the raw C++
145
+ `ImGui::` namespace. There is no `SliderFloat3`, `PushID`/`PopID`, or
146
+ `SameLine`. Build a vec3 control from three `slider_float` calls, and give
147
+ every widget label a `##<section-scope>` suffix — `Panel::draw()` only
148
+ `PushID`s a section when `options.expand=True`, so plain sections share one
149
+ global Dear ImGui id namespace and identical labels across sections (every
150
+ light type has a "Position"/"Color"/"Intensity") fight over the same drag
151
+ state without the suffix.
152
+
153
+ Working shape — one collapsible section per light type, an
154
+ Activate/Deactivate button driving a single shared "live" `LightSet` slot,
155
+ sliders scoped per section:
156
+
157
+ ```python
158
+ import math
159
+
160
+ active_slot = 0 # the ONLY light -- lightCount stays 0 until a section is Activated
161
+
162
+ class LightsState:
163
+ def __init__(self):
164
+ self.active_demo = None
165
+ self.point_position = [2.5, -2.5, 6.0]
166
+ self.point_color = [0.85, 0.55, 0.30]
167
+ self.point_intensity = 12.0
168
+ # ... directional_*, sphere_* (+radius), spot_* (+direction, inner/outer deg, source_radius)
169
+
170
+ state = LightsState()
171
+
172
+ def apply_state():
173
+ if state.active_demo is None:
174
+ lights.setCount(0)
175
+ return
176
+ lights.setCount(1)
177
+ if state.active_demo == "Point":
178
+ lights.setPoint(active_slot, osg.Vec3(*state.point_position), osg.Vec3(*state.point_color), state.point_intensity)
179
+ # ... Directional -> setDirectional, Sphere -> setPoint(..., sourceRadius=...), Spot -> setSpot(...)
180
+
181
+ def point_section(ri): # addSection()'s callback always takes one osg.RenderInfo arg, even if unused
182
+ # activate_button(name): text("ACTIVE"/"(inactive)") + Activate/Deactivate osgx.imgui.button(),
183
+ # calling apply_state() on either. Every widget label carries a "##point"-style scope suffix.
184
+ # vec3_sliders(prefix, scope, values, lo, hi): three osgx.imgui.slider_float() calls, one per axis.
185
+ activate_button("Point")
186
+ changed = vec3_sliders("Position", "ptpos", state.point_position, -10.0, 10.0)
187
+ c, r, g, b = osgx.imgui.color_edit3("Color##pt", *state.point_color)
188
+ state.point_color[:] = [r, g, b]
189
+ changed = changed or c
190
+ c, v = osgx.imgui.slider_float("Intensity##pt", state.point_intensity, 0.0, 30.0)
191
+ state.point_intensity = v
192
+ changed = changed or c
193
+ if changed and state.active_demo == "Point": apply_state()
194
+
195
+ # directional_section / sphere_section / spot_section follow the same shape
196
+ # (Sphere adds a "Source Radius" slider 0..3; Spot adds Direction, Inner/Outer
197
+ # Cone (deg), Source Radius, and clamps inner < outer so GLSL's
198
+ # smoothstep(cos(outer), cos(inner), ...) stays well-formed).
199
+
200
+ lights = osgx.LightSet.create(root.stateSet)
201
+ gizmos = osgx.LightGizmos(lights, root, 0.4, 10.0)
202
+ root.children.append(gizmos)
203
+
204
+ gui = osgx.imgui.Widget(viewer)
205
+ gui.addSection("Directional", directional_section)
206
+ gui.addSection("Point", point_section)
207
+ gui.addSection("Sphere", sphere_section)
208
+ gui.addSection("Spot", spot_section)
209
+ ```
210
+
211
+ ## Known gaps
212
+
213
+ No `HookList`-style helper (mirroring osgSlug's `HookList`) yet for composing
214
+ multiple fragment-stage hooks on one Program — `osgx_DirectLighting()` is the
215
+ first hook, added by hand since there's only one so far. `PBRIBL.cpp`'s
216
+ `evaluateIBL()` and the final tonemap/composite step are candidates for the
217
+ same treatment once a real second consumer needs to override one
218
+ specifically.
@@ -0,0 +1,24 @@
1
+ """Curated, runnable examples installed with the OpenSceneGraph wheel.
2
+
3
+ Each module here exposes ``build_scene(width, height) -> osg.Node`` and, optionally,
4
+ ``configure_viewer(viewer, root)``; see ``OpenSceneGraph.examples.__main__`` (the
5
+ implementation behind the ``pyosg`` console script) for the shared runner that drives both
6
+ hooks. Every module is also directly runnable on its own, e.g.
7
+ ``python -m OpenSceneGraph.examples.mrt``.
8
+
9
+ This is a small, curated subset of the full example sandbox in the project's `examples/`
10
+ source directory -- not everything there implements the build_scene()/configure_viewer()
11
+ contract (yet), and most of that directory (the Lighting Series, GLSL experiments, data
12
+ files, etc.) isn't meant to ship in the wheel at all.
13
+ """
14
+
15
+ import sys as _sys
16
+ from pathlib import Path as _Path
17
+
18
+ # A couple of these examples do a bare `from pyosg_visitor import GatherVisitor` rather than
19
+ # a package-relative import, matching how they're written to also run standalone straight out
20
+ # of the project's examples/ directory (see pyosg-cli). Put this package's own directory on
21
+ # sys.path so that resolves the same way here.
22
+ _sys.path.insert(0, str(_Path(__file__).parent))
23
+
24
+ del _sys, _Path
@@ -0,0 +1,143 @@
1
+ """CLI backing the `pyosg` console script and `python -m OpenSceneGraph.examples <name>`.
2
+
3
+ Runs one of this package's example modules standalone (no Qt, no aipython) by creating a
4
+ bare osgViewer.Viewer, calling the example's build_scene(w, h), and its
5
+ configure_viewer(viewer, root) if it defines one. Adapted from the pyosg-cli
6
+ proof-of-concept in the project's repository root, which loads examples by file path out of
7
+ examples/ instead -- useful for the full example sandbox, most of which doesn't implement
8
+ this module's build_scene()/configure_viewer() contract (yet).
9
+ """
10
+
11
+ import argparse
12
+ import importlib
13
+ import pkgutil
14
+ import sys
15
+
16
+ from OpenSceneGraph import *
17
+
18
+
19
+ def _load_module(name):
20
+ try:
21
+ return importlib.import_module(f"OpenSceneGraph.examples.{name}")
22
+
23
+ except ModuleNotFoundError as error:
24
+ # Only turn failure to locate the requested example itself into the
25
+ # friendly availability list. A ModuleNotFoundError raised by one of the
26
+ # example's own imports is a real diagnostic and must not be disguised as
27
+ # an unknown runner name.
28
+ if error.name != f"OpenSceneGraph.examples.{name}":
29
+ raise
30
+
31
+ import OpenSceneGraph.examples as _examples
32
+
33
+ # pyosg_* are shared helper modules (pyosg_example, pyosg_visitor, pyosg_repl,
34
+ # pyosg_dice), not runnable examples -- they have no build_scene() and were never meant
35
+ # to appear here, same reason a leading "_" is already excluded.
36
+ available = sorted(
37
+ mod.name for mod in pkgutil.iter_modules(_examples.__path__)
38
+ if not mod.name.startswith("_") and not mod.name.startswith("pyosg_")
39
+ )
40
+
41
+ sys.exit(f"error: no such example: {name!r} (available: {', '.join(available)})")
42
+
43
+
44
+ def run_module(module, width=800, height=600, extra_argv=(), name=None):
45
+ """Shared runner core: viewer construction, window setup, build_scene()/configure_viewer(),
46
+ and the frame loop. Deliberately takes an already-loaded module object rather than a name --
47
+ `run()` below loads it from the installed OpenSceneGraph.examples package, while pyosg-cli
48
+ (repo root) loads an arbitrary examples/*.py file by path and calls this directly, so the
49
+ actual viewer-setup/frame-loop logic exists in exactly one place instead of two copies that
50
+ can silently drift (this bit pyosg-cli once already -- see PYOSG_BUILD_PACKAGE_OVERLAY/
51
+ setUpViewInWindow ordering history in project_pyosg_contract_conversion for the pyosg-
52
+ polyhaven.py --hdr bug that ordering mismatch caused)."""
53
+
54
+ if not hasattr(module, "build_scene"):
55
+ sys.exit(f"error: {name or module.__name__} has no build_scene(w, h)")
56
+
57
+ viewer = osgViewer.Viewer()
58
+
59
+ # Explicit window setup, driven by the same width/height build_scene() receives -- a single
60
+ # source of truth instead of every example hardcoding its own OSG_WINDOW env var string that
61
+ # has to be kept in sync by hand (and silently isn't, the moment --width/--height differs from
62
+ # an example's own hardcoded default). x/y match the "50 50 ..." every example's old OSG_WINDOW
63
+ # used.
64
+ viewer.setUpViewInWindow(50, 50, width, height)
65
+
66
+ viewer.cameraManipulator = osgGA.TrackballManipulator()
67
+
68
+ # Sandbox sys.argv for the duration of both hook calls below: some examples (e.g.
69
+ # blur's create_scene()) read sys.argv[1] lazily, from inside build_scene() itself, not
70
+ # just at module-import time. extra_argv forwards e.g. `pyosg blur -- foo.gltf` through
71
+ # to the example's own sys.argv[1:], matching `python -m OpenSceneGraph.examples.blur
72
+ # foo.gltf`.
73
+ saved_argv = sys.argv
74
+
75
+ sys.argv = [name or module.__name__, *extra_argv]
76
+
77
+ try:
78
+ root = module.build_scene(width, height)
79
+
80
+ viewer.sceneData = root
81
+
82
+ if hasattr(module, "configure_viewer"):
83
+ module.configure_viewer(viewer, root)
84
+
85
+ finally:
86
+ sys.argv = saved_argv
87
+
88
+ viewer.TODO()
89
+
90
+ # KNOWN ISSUE, deliberately not "fixed" with a sleep() here -- see feedback_runner_unthrottled_loop
91
+ # for the full writeup. Short version: a sleep() here treats the symptom (starves anything else
92
+ # on the process needing the GIL on a regular cadence, confirmed via audible clicking in
93
+ # pyosg-animusic-grid.py's sounddevice callback thread); the real fix under consideration is
94
+ # releasing the GIL inside viewer.frame()'s own pybind11 binding instead.
95
+ while not viewer.done:
96
+ viewer.frame()
97
+
98
+
99
+ def run(name, width=800, height=600, extra_argv=()):
100
+ module = _load_module(name)
101
+
102
+ run_module(module, width, height, extra_argv, name=name)
103
+
104
+
105
+ def main():
106
+ parser = argparse.ArgumentParser(
107
+ prog="pyosg",
108
+ description="Run an OpenSceneGraph.examples module standalone, no Qt required"
109
+ )
110
+
111
+ parser.add_argument("name", help="Example module name, e.g. 'mrt' for OpenSceneGraph.examples.mrt")
112
+ parser.add_argument("--width", type=int, default=800)
113
+ parser.add_argument("--height", type=int, default=600)
114
+
115
+ # Split on the FIRST literal "--" ourselves, before argparse ever sees it, and hand it
116
+ # only the part before that. argparse.add_argument("extra", nargs="*") looks like the
117
+ # obvious way to do this instead, but has a real, confirmed bug: "mrt -- foo.osgt" parses
118
+ # fine, but "mrt --width 640 -- foo.osgt" fails with "unrecognized arguments: -- foo.osgt"
119
+ # -- a known CPython argparse limitation around "--" interacting with a nargs="*"
120
+ # positional when other flags are interspersed, not something fixable by reordering our
121
+ # own argument definitions.
122
+ #
123
+ # KNOWN FRICTION (deferred, not fixed, same as pyosg-cli): every one of the target example's
124
+ # own arguments -- including its OWN required positionals/flags -- has to go after this "--",
125
+ # since this parser only knows about name/--width/--height. A `parse_known_args()`-based
126
+ # two-pass parse could remove the need for "--" in the common case; not attempted, needs real
127
+ # testing against the SAME argparse quirk documented above first.
128
+ argv = sys.argv[1:]
129
+
130
+ if "--" in argv:
131
+ i = argv.index("--")
132
+ own_argv, extra_argv = argv[:i], argv[i + 1:]
133
+
134
+ else:
135
+ own_argv, extra_argv = argv, []
136
+
137
+ args = parser.parse_args(own_argv)
138
+
139
+ run(args.name, args.width, args.height, extra_argv)
140
+
141
+
142
+ if __name__ == "__main__":
143
+ main()