ogre-glslwright 0.1.1__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.
glslwright/__init__.py ADDED
@@ -0,0 +1,165 @@
1
+ """Pure-python shader-graph core: data model and codegen."""
2
+
3
+ from pathlib import Path
4
+ from ._version import __version__
5
+
6
+ import Ogre
7
+
8
+ from .grapheditor import GraphEditor
9
+
10
+ # Generated files default to a local output dir next to the package;
11
+ # callers (e.g. the editor app) are expected to pass an explicit outDir.
12
+ DEFAULT_OUTPUT_DIR = Path.cwd() / "shadergen"
13
+
14
+ # The resource group actually used by the last initResourceDir() call -
15
+ # may differ from the requested *groupname* (see rename below), so
16
+ # callers that parse/open generated resources must use this, not a
17
+ # hardcoded name, or #include lookups scoped to that group will fail.
18
+ RESOURCE_GROUP = None
19
+
20
+ # Other resource groups whose textures/materials should also be visible
21
+ # to generated shader materials (e.g. a host app's own asset group) -
22
+ # set via initResourceDir()'s extraGroups, kept generic so this library
23
+ # stays agnostic of any particular host application.
24
+ EXTRA_RESOURCE_GROUPS = ()
25
+
26
+
27
+ def initResourceDir(output='./glslwright', groupname='glslwright',
28
+ extraGroups=()):
29
+ """Initialize the resource directory for GLSL WRIGHT.
30
+
31
+ Arguments
32
+ ---------
33
+ output (str):
34
+ The output directory for generated material and graph files.
35
+ groupname (str):
36
+ The resource group name to use.
37
+ extraGroups (tuple[str]):
38
+ Other already-registered resource groups (e.g. the host app's
39
+ own asset group) whose locations are mirrored into *groupname*,
40
+ so generated materials can reference their textures by filename.
41
+
42
+ Returns the actual resource group name used (see RESOURCE_GROUP).
43
+ """
44
+
45
+ rgMgr = Ogre.ResourceGroupManager.getSingleton()
46
+ if rgMgr.resourceGroupExists(groupname) and rgMgr.isResourceGroupInitialised(
47
+ groupname
48
+ ):
49
+ # Can't force a re-parse of new locations on an already-initialised
50
+ # group (unload/reinit doesn't reset Ogre's internal parsed flag, and
51
+ # the bindings don't expose a script-only reparse call) - use our own
52
+ # group instead. findMaterial() falls back to an autodetect lookup
53
+ # across groups, so this is still discoverable under *groupname*.
54
+ groupname = f"{groupname}_glslwright"
55
+
56
+ if not rgMgr.resourceGroupExists(groupname):
57
+ rgMgr.createResourceGroup(groupname)
58
+
59
+ outputPath = Path(output).resolve()
60
+ outputPath.mkdir(parents=True, exist_ok=True)
61
+
62
+ print(f"Initializing resource directory at: {outputPath}")
63
+ global DEFAULT_OUTPUT_DIR, RESOURCE_GROUP, EXTRA_RESOURCE_GROUPS
64
+ DEFAULT_OUTPUT_DIR = outputPath
65
+ RESOURCE_GROUP = groupname
66
+ EXTRA_RESOURCE_GROUPS = tuple(extraGroups)
67
+
68
+ # Track locations already added to *groupname* in this call so
69
+ # mirrored extraGroup locations that coincide with ones added above
70
+ # (or with each other, e.g. two groups sharing Ogre's media dir)
71
+ # aren't parsed twice - that triggers a "Skipping ... already exists"
72
+ # warning per script file for every duplicate.
73
+ addedLocations = set()
74
+
75
+ def _addLocation(location):
76
+ location = str(location)
77
+ # Normalize so a relative string and Ogre's own (resolved) path
78
+ # for the same directory are recognized as the same location -
79
+ # otherwise it gets parsed twice into *groupname*, which Ogre
80
+ # reports as a "Skipping ... already exists" warning per file.
81
+ key = str(Path(location).resolve())
82
+ if key in addedLocations:
83
+ return
84
+ addedLocations.add(key)
85
+ rgMgr.addResourceLocation(location, "FileSystem", groupname)
86
+
87
+ _addLocation(outputPath)
88
+ _addLocation(Path(__file__).parent / "material")
89
+ # shaderNodes.glsl #includes Ogre's RTShaderSystem lib (RTSLib_*/
90
+ # FFPLib_*/SGXLib_*) - #include lookups are scoped to the program's
91
+ # own resource group, so that media dir must be added here too
92
+ _addLocation(
93
+ Ogre.Bites.ApplicationContextBase.getDefaultMediaDir() + "/RTShaderLib"
94
+ )
95
+
96
+ # Some runtimes place RTShaderLib next to the app/resources.cfg
97
+ # instead of under Ogre's default media dir.
98
+ for candidate in (
99
+ Path.cwd() / "RTShaderLib",
100
+ Path(__file__).resolve().parents[4] / "RTShaderLib",
101
+ ):
102
+ if (candidate / "RTSLib_Colour.glsl").exists():
103
+ _addLocation(candidate)
104
+ # OgreUnifiedShader.h, included by every generated program, usually
105
+ # lives in Ogre's shared media dir.
106
+ _addLocation(
107
+ Ogre.Bites.ApplicationContextBase.getDefaultMediaDir() + "/Main"
108
+ )
109
+
110
+ # Some packagings (including OpenGate's assets) ship this header in
111
+ # project-local materials dirs instead of Ogre's default media path.
112
+ # Mirror any known local location into *groupname* so #include lookup
113
+ # in generated programs still resolves.
114
+ for candidate in (
115
+ Path.cwd() / "assets" / "materials",
116
+ Path(__file__).resolve().parents[4] / "assets" / "materials",
117
+ ):
118
+ if (candidate / "OgreUnifiedShader.h").exists():
119
+ _addLocation(candidate)
120
+
121
+ # generated materials reference project textures (e.g. via the
122
+ # shader graph's Texture node) by filename only - mirror each extra
123
+ # group's locations here too, or lookups fail with "Cannot locate
124
+ # resource" since a material's own resource group is searched, not
125
+ # RGN_AUTODETECT
126
+ # Mirror Ogre's internal group by default so any RTShaderLib
127
+ # location discovered by host app bootstrap is visible here too.
128
+ allExtraGroups = list(EXTRA_RESOURCE_GROUPS)
129
+ internalGroup = Ogre.RGN_INTERNAL
130
+ if internalGroup not in allExtraGroups:
131
+ allExtraGroups.append(internalGroup)
132
+
133
+ for extraGroup in allExtraGroups:
134
+ if not rgMgr.resourceGroupExists(extraGroup):
135
+ continue
136
+ locations = rgMgr.listResourceLocations(extraGroup)
137
+ for i in range(locations.size()):
138
+ _addLocation(locations.__getitem__(i))
139
+
140
+ if not rgMgr.isResourceGroupInitialised(groupname):
141
+ try:
142
+ rgMgr.initialiseResourceGroup(groupname)
143
+ except RuntimeError as e:
144
+ # Samplers/global objects from RTShaderLib's own scripts are
145
+ # shared across groups and already exist once the main
146
+ # "Opengate" group has initialised them - harmless here.
147
+ if "already exists" not in str(e):
148
+ raise
149
+
150
+ return groupname
151
+
152
+ def _r(*message):
153
+ print(f"[ERROR glslwright] {' '.join(str(m) for m in message)}")
154
+
155
+ def _y(*message):
156
+ print(f"[WARNING glslwright] {' '.join(str(m) for m in message)}")
157
+
158
+ def _b(*message):
159
+ print(f"[DEBUG glslwright] {' '.join(str(m) for m in message)}")
160
+
161
+ def _g(*message):
162
+ print(f"[INFO glslwright] {' '.join(str(m) for m in message)}")
163
+
164
+
165
+ __all__ = ["__version__", "GraphEditor", "_r", "_y", "_b", "_g"]
glslwright/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.1'
22
+ __version_tuple__ = version_tuple = (0, 1, 1)
23
+
24
+ __commit_id__ = commit_id = None
glslwright/app.py ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python
2
+ """Standalone glslwright main window, built on ogre-meshviewer.
3
+
4
+ Reuses MeshViewer's Ogre/ApplicationContext/ImGui boilerplate (render
5
+ window, camera, resource groups, ...) as the host render environment,
6
+ without modifying the ogre-meshviewer checkout, and overlays the
7
+ shader node-graph editor on top of it.
8
+ """
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from .meshviewerloader import loadMeshViewerModule
13
+ from .grapheditor import GraphEditor
14
+
15
+
16
+ def _set_windows_dpi_awareness():
17
+ """Enable best-available Windows DPI-awareness before SDL/Ogre windowing."""
18
+ if os.name != "nt":
19
+ return
20
+
21
+ # Let SDL opt into per-monitor-v2 awareness where supported.
22
+ os.environ.setdefault("SDL_WINDOWS_DPI_AWARENESS", "permonitorv2")
23
+
24
+ try:
25
+ import ctypes
26
+
27
+ # Windows 10/11: Per-Monitor v2 (best quality on mixed-DPI setups).
28
+ user32 = ctypes.windll.user32
29
+ if user32.SetProcessDpiAwarenessContext(ctypes.c_void_p(-4)):
30
+ return
31
+ except Exception:
32
+ pass
33
+
34
+ try:
35
+ import ctypes
36
+
37
+ # Windows 8.1 fallback: Per-Monitor.
38
+ ctypes.windll.shcore.SetProcessDpiAwareness(2)
39
+ return
40
+ except Exception:
41
+ pass
42
+
43
+ try:
44
+ import ctypes
45
+
46
+ # Legacy fallback.
47
+ ctypes.windll.user32.SetProcessDPIAware()
48
+ except Exception:
49
+ pass
50
+
51
+
52
+ _set_windows_dpi_awareness()
53
+
54
+ _mv = loadMeshViewerModule()
55
+
56
+
57
+ class GlslWrightGui(_mv.MeshViewerGui):
58
+ """MeshViewerGui plus the glslwright shader node-graph panel."""
59
+
60
+ def __init__(self, app, graphEditor):
61
+ super().__init__(app)
62
+ self.graphEditor = graphEditor
63
+
64
+ def preRenderTargetUpdate(self, evt):
65
+ super().preRenderTargetUpdate(evt)
66
+ if self.graphEditor.isVisible():
67
+ self.graphEditor.renderImGui()
68
+
69
+
70
+ class _PropOverlayStub:
71
+ """Minimal stand-in for Opengate's propOverlay: exposes `.item` only."""
72
+
73
+ def __init__(self, item=None):
74
+ self.item = item
75
+
76
+
77
+ class GlslWrightApp(_mv.MeshViewer):
78
+ """MeshViewer subclass hosting the glslwright node editor."""
79
+
80
+ def __init__(
81
+ self,
82
+ infile=None,
83
+ rescfg=None,
84
+ outputPath="./examples",
85
+ materialName=None,
86
+ ):
87
+ self.outputPath = outputPath
88
+ if materialName is not None and materialName.endswith(".toml"):
89
+ materialName = Path(materialName).stem
90
+
91
+ self.materialName = materialName
92
+ super().__init__(infile, rescfg)
93
+
94
+ def setup(self):
95
+ super().setup()
96
+
97
+ self.graphEditor = GraphEditor(outputPath=self.outputPath)
98
+ self.graphEditor.app = self
99
+ self.graphEditor.registerInput(self)
100
+ self.sm = self.scn_mgr
101
+ if self.materialName:
102
+ self.graphEditor.selectMaterial(self.materialName)
103
+
104
+ # swap in our GUI subclass, reusing MeshViewer's public
105
+ # listener API instead of touching its internals
106
+ self.getRenderWindow().removeListener(self.gui)
107
+ self.gui = GlslWrightGui(self, self.graphEditor)
108
+ self.getRenderWindow().addListener(self.gui)
109
+
110
+ # Without a tracked MaterialListener, live-reload falls back to
111
+ # reloadMaterial(sm=None), which unloads/reparses the material
112
+ # but can't repoint the previewed entity's SubEntity at the new
113
+ # instance -- it's left referencing a freed Material/GpuProgram
114
+ # and segfaults on the next render. Assign to the meshviewer
115
+ # preview's own SubEntity up front, so it's always the live
116
+ # assign/supervision target, matching ogre-meshviewer's SubMesh
117
+ # preview.
118
+ materialListener = self._previewMaterialListener()
119
+ if materialListener is not None:
120
+ self.propOverlay = _PropOverlayStub(materialListener)
121
+ self.graphEditor._assignToMaterialListener(materialListener)
122
+
123
+ def _previewMaterialListener(self):
124
+ """Return the loaded preview mesh's first SubEntity, if any."""
125
+ if self.entity is None:
126
+ return None
127
+ subEntities = self.entity.getSubEntities()
128
+ return subEntities[0] if subEntities else None
129
+
130
+
131
+ def main():
132
+ import argparse
133
+
134
+ parser = argparse.ArgumentParser(description="GLSL Wright")
135
+ parser.add_argument(
136
+ "infile",
137
+ nargs="?",
138
+ help="preview mesh/scene for the shader (opens a file picker if omitted)",
139
+ )
140
+ parser.add_argument(
141
+ "materialName",
142
+ nargs="?",
143
+ help="materialName to load into the graph editor on startup",
144
+ )
145
+ parser.add_argument("-c", "--rescfg", help="path to the resources.cfg")
146
+ args = parser.parse_args()
147
+
148
+ app = GlslWrightApp(
149
+ args.infile, args.rescfg, materialName=args.materialName
150
+ )
151
+ # ogre_mesh_viewer.py reads/writes a module-level "app" global
152
+ # instead of self.app in several places; mirror its own __main__
153
+ # guard here since we don't run that guard.
154
+ _mv.app = app
155
+
156
+ while True: # allow auto restart (e.g. render system change)
157
+ try:
158
+ app.initApp()
159
+ app.getRoot().startRendering()
160
+ app.closeApp()
161
+ except RuntimeError as e:
162
+ raise SystemExit(e) from e
163
+
164
+ if not app.restart:
165
+ break
166
+
167
+
168
+ if __name__ == "__main__":
169
+ main()