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 +165 -0
- glslwright/_version.py +24 -0
- glslwright/app.py +169 -0
- glslwright/codegen.py +804 -0
- glslwright/editorMixin.py +202 -0
- glslwright/graph.py +1509 -0
- glslwright/grapheditor.py +1918 -0
- glslwright/imguiHelper.py +631 -0
- glslwright/meshviewerloader.py +43 -0
- glslwright/ogreHelper.py +843 -0
- ogre_glslwright-0.1.1.dist-info/METADATA +127 -0
- ogre_glslwright-0.1.1.dist-info/RECORD +15 -0
- ogre_glslwright-0.1.1.dist-info/WHEEL +5 -0
- ogre_glslwright-0.1.1.dist-info/licenses/LICENSE +21 -0
- ogre_glslwright-0.1.1.dist-info/top_level.txt +1 -0
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()
|