MaterialXWeb 1.39.5
Utilities for using MaterialX Packages with Web clients
Loading...
Searching...
No Matches
MaterialXGPUOpenApp.py
1'''
2@file app.py
3@brief A Flask application that connects with the GPUOpen MaterialX server to allow downloading and extracting of materials by regular expression.
4'''
5import argparse
6import sys
7from flask import Flask, render_template
8import json
9from flask_socketio import SocketIO, emit
10from materialxMaterials import GPUOpenLoader as gpuo
11have_mx = False
12try:
13 import MaterialX as mx
14 have_mx = True
15except ImportError as e:
16 print("MaterialX module not found.")
17
18# Not required unless performing MaterialX operations on data
19#import MaterialX as mx
20
22 '''
23 @brief Base Flask application class for MaterialX GPUOpen server interactions.
24 '''
25 def __init__(self, home):
26 '''
27 Initialize the Flask application and SocketIO.
28 @param home The home page template to render.
29 '''
30 self.home = home
31
32 # Initialize Flask and SocketIO
33 self.app = Flask(__name__)
34 self.socketio = SocketIO(self.app)
35
36 # Register routes and events
37 self._register_routes()
40
42 '''
43 Register HTTP routes.
44 '''
45 @self.app.route('/')
46 def home():
47 """
48 Render the home page.
49 """
50 status_message = f'Startup: Using MaterialX version: {mx.getVersionString()}'
51 #self._emit_status_message(status_message)
52 print(status_message)
53 return render_template(self.home)
54
56 """Pure virtual method: Must be implemented by subclasses."""
57 raise NotImplementedError("Subclasses must implement _setup_event_handler_map")
58
60 '''
61 Register SocketIO events.
62 '''
63 # Dynamically register event handlers
64 for event_name, handler in self.event_handlers.items():
65 self.socketio.on_event(event_name, handler)
66
67 def run(self, host, port, debug=True):
68 '''
69 Run the Flask server with SocketIO.
70 @param host The host address to run the server on.
71 @param port The port to run the server on.
72 @param debug Whether to run the server in debug mode.
73 '''
74 self.socketio.run(self.app, host, port, debug=debug)
75
76
78 '''
79 A Flask application that connects with the GPUOpen MaterialX server to allow downloading
80 and extracting of materials by regular expression.
81 '''
82 def __init__(self, homePage):
83 '''
84 Initialize the Flask application and the MaterialX loader.
85 @param homePage The home page template to render.
86 '''
87 super().__init__(homePage)
88
89 # Material loader and associated attributes
90 self.loader = None
91 self.materials = None
92 self.material_names = None
93 self.material_count = 0
94
95 def _emit_status_message(self, message):
96 '''
97 @brief Emit a status message to the client. The message emitted is of the form:
98 { 'message': 'message string' }
99
100 @param message The status message to emit.
101 '''
102 emit('materialx_status', { 'message': message }, broadcast=True)
103 print('Python:', message)
104
106 '''
107 @brief Handle the 'download_materialx' event, initialize the loader, and send materials data to the client.
108 Data is of the form:
109 { 'materialCount': int,
110 'materialNames': list of strings,
111 'materialsList': list of material data in JSON format
112 }
113 @param data The data received from the client (not used in this handler).
114
115 '''
116 status_message = f'Downloaded materials...'
117 self._emit_status_message(status_message)
118
119 # Initialize the loader and fetch materials
120 self.loader = gpuo.GPUOpenMaterialLoader()
121 from_package = data.get('frompackage', False)
122 if from_package:
123 self.loader.readPackageFiles()
124 else:
125 # Download vs package...
126 self.materials = self.loader.getMaterials()
127 self.loader.getRenders()
128 self.material_names = self.loader.getMaterialNames()
129
130 # Convert materials to JSON and add preview URLs
131 materials_list = []
132 merged_results = { "results": [] }
133 for mat_json in self.loader.getMaterialsAsJsonString():
134 mat_obj = json.loads(mat_json)
135 # Add preview URL for each result
136 results = mat_obj.get('results', [])
137 for result in results:
138 title = result.get('title')
139 result['url'] = self.loader.getMaterialPreviewURL(title)
140 merged_results["results"].append(result)
141
142 # Sort list by title
143 merged_results["results"].sort(key=lambda x: x.get('title', ''))
144 materials_list.append(json.dumps(merged_results, indent=2))
145
146 self.material_count = len(self.material_names)
147
148 # Emit a status message
149 status_message = f'Downloaded {self.material_count} materials.'
150 self._emit_status_message(status_message)
151
152 # Emit the data back to the client
153 emit('materialx_downloaded', {
154 'materialCount': self.material_count,
155 'materialNames': self.material_names,
156 'materialsList': materials_list
157 }, broadcast=True)
158
159 def handle_extract_material(self, data):
160 '''
161 @brief Handle the 'extract_material' event, extract material data, and send it back to the client.
162 @param data The data received from the client, expected to contain:
163 { 'expression': string, 'update_materialx': bool }
164 '''
165 return_list = []
166
167 if self.loader is None:
168 self._emit_status_message('Loader is not initialized. Download materials first.')
169 emit('materialx_extracted', {'extractedData': return_list}, broadcast=True)
170 return
171
172 self._emit_status_message('Extracting materials...')
173 expression = data.get('expression', 'Default Expression')
174 update_mtlx = data.get('update_materialx', False)
175 if not have_mx:
176 update_mtlx = False
177 data_items = self.loader.downloadPackageByExpression(expression)
178
179 for data_item in data_items:
180 status_message = f'Extracting material: {data_item[1]}'
181 self._emit_status_message(status_message)
182 package = data_item[0]
183 title = data_item[1]
184 extracted_data = self.loader.extractPackageData(package, None)
185 return_data = {}
186
187 if extracted_data:
188 for item in extracted_data:
189 file_name = item['file_name']
190 if item['type'] == 'mtlx':
191 self._emit_status_message(f'- MaterialX file {file_name}')
192 mx_string = item['data']
193 if update_mtlx:
194 self._emit_status_message(f'Updating MaterialX data to version: {mx.getVersionString()}')
195 doc = mx.createDocument()
196 readOptions = mx.XmlReadOptions()
197 readOptions.readComments = True
198 readOptions.readNewlines = True
199 readOptions.upgradeVersion = True
200 mx.readFromXmlString(doc, mx_string, mx.FileSearchPath(), readOptions)
201 mx_string = mx.writeToXmlString(doc)
202
203 return_data[file_name] = mx_string
204 elif item["type"] == 'image':
205 self._emit_status_message(f'- Image file {file_name}')
206 image = item["data"]
207 image_base64 = self.loader.convertPilImageToBase64(image)
208 return_data[file_name] = image_base64
209
210 if len(return_data) > 0:
211 url = self.loader.getMaterialPreviewURL(title)
212 self._emit_status_message(f'Preview URL: {url}')
213 return_list.append({'title': title, 'data': return_data, 'url': url})
214
215 if len(return_list) == 0:
216 self._emit_status_message('No materials extracted')
217 emit('materialx_extracted', {'extractedData': return_list}, broadcast=True)
218 else:
219 status_message = f'Extracted {len(return_list)} materials'
220 self._emit_status_message(status_message)
221 emit('materialx_extracted', {'extractedData': return_list}, broadcast=True)
222
224 '''
225 Set up dictionary of mapping event names to their handlers
226 '''
227 self.event_handlers = {
228 'download_materialx': self.handle_download_materialx,
229 'extract_material': self.handle_extract_material,
230 }
231
232# Main entry point
233def main():
234 '''
235 @brief Main entry point for the application. Parses command-line arguments and starts the Flask server.
236 @detail Argument parameters are:
237 -h/--host: Host address to run the server on (default: 127.0.0.1)
238 -po/--port: Port to run the server on (default: 8080)
239 -ho/--home: Home page template (default: MaterialXGPUOpenApp.html)
240 '''
241 parser = argparse.ArgumentParser(description="GPUOpen MaterialX Application")
242 parser.add_argument('-hs', '--host', type=str, default='127.0.0.1', help="Host address to run the server on (default: 127.0.0.1)")
243 parser.add_argument('-p','--port', type=int, default=8080, help="Port to run the server on (default: 8080)")
244 parser.add_argument('-ho', '--home', type=str, default='MaterialXGPUOpenApp.html', help="Home page.")
245
246 args = parser.parse_args()
247
248 app = MaterialXGPUOpenApp(args.home)
249 app_host = args.host
250 app_port = args.port
251 app.run(host=app_host, port=app_port)
252
253if __name__ == '__main__':
254 main()
Base Flask application class for MaterialX GPUOpen server interactions.
run(self, host, port, debug=True)
Run the Flask server with SocketIO.
_register_socket_events(self)
Register SocketIO events.
_register_routes(self)
Register HTTP routes.
__init__(self, home)
Initialize the Flask application and SocketIO.
_setup_event_handler_map(self)
Pure virtual method: Must be implemented by subclasses.
A Flask application that connects with the GPUOpen MaterialX server to allow downloading and extracti...
__init__(self, homePage)
Initialize the Flask application and the MaterialX loader.
handle_extract_material(self, data)
Handle the 'extract_material' event, extract material data, and send it back to the client.
_emit_status_message(self, message)
Emit a status message to the client.
handle_download_materialx(self, data)
Handle the 'download_materialx' event, initialize the loader, and send materials data to the client.
_setup_event_handler_map(self)
Set up dictionary of mapping event names to their handlers.