MaterialXWeb 0.0.2
Utilities for using MaterialX Packages with Web clients
Loading...
Searching...
No Matches
MaterialXConversionApp.py
1import base64
2import os
3import argparse
4import datetime
5import platform
6
7from flask import Flask, render_template
8from flask_socketio import SocketIO, emit
9
10import MaterialX as mx
11
12# Try to import usdmtlx. If cannot, set flag to False
13# -- Not really required as part of Python package requirements
14try:
15 from usdmtlx import convertMtlxToUsd
16 have_usd_converter = True
17except ImportError:
18 print('Cannot import usdmtlx')
19 have_usd_converter = False
20
21# Try to import gltf_materialx_converter. If cannot, set flag to False
22try:
23 from gltf_materialx_converter import converter as MxGLTFPT
24 from gltf_materialx_converter import utilities as MxGLTFPTUtil
25 have_gltf_converter = True
26except ImportError:
27 print('Cannot import gltf_materialx_converter')
28 have_gltf_converter = False
29
30def get_os_details():
31 return {
32 'os': platform.system(),
33 'release': platform.release(),
34 'architecture': platform.machine()
35 }
36
38 def __init__(self, home):
39 self.home = home
40
41 # Initialize Flask and SocketIO
42 self.app = Flask(__name__)
43 self.socketio = SocketIO(self.app)
44
45 # Register routes and events
46 self._register_routes()
49
50 self.deployment_platform = 'Local'
51 print(f'* Initialized on deployment plaform: {self.deployment_platform}')
52 self.os_details = get_os_details()
53 print(f" * OS: {self.os_details['os']}")
54 print(f" * Release: {self.os_details['release']}")
55 print(f" * Architecture: {self.os_details['architecture']}")
56
58 '''
59 Register HTTP routes.
60 '''
61 @self.app.route('/')
62 def home():
63 '''
64 Render the home page.
65 '''
66 #status_message = f'Startup: Using MaterialX version: {mx.getVersionString()}'
67 #self._emit_status_message(status_message)
68 return render_template(self.home)
69
71 '''Pure virtual method: Must be implemented by subclasses.'''
72 raise NotImplementedError("Subclasses must implement _setup_event_handler_map")
73
75 '''
76 Register SocketIO events.
77 '''
78 # Dynamically register event handlers
79 for event_name, handler in self.event_handlers.items():
80 self.socketio.on_event(event_name, handler)
81
82 def run(self, host, port, deployment_platform, debug=True):
83 '''
84 Run the Flask server with SocketIO.
85 '''
86 self.deployment_platform = deployment_platform
87 self.socketio.run(self.app, host, port, debug=debug)
88
90 '''
91 '''
92 def __init__(self, homePage):
93 '''
94 Constructor
95 '''
96 super().__init__(homePage)
97
99 '''
100 Set up dictionary of mapping event names to their handlers
101 '''
102 event_map = {}
103 event_map['page_loaded'] = self.handle_page_loaded
104
105 self.event_handlers = {
106 'page_loaded': self.handle_page_loaded,
107 'load_materialx': self.handle_load_materialx,
108 'render_materialx': self.handle_render_materialx,
109 'convert_mtlx_to_usd': self.handle_convert_to_usd,
110 'convert_mtlx_to_gltf': self.handle_convert_to_glTF,
111 'query_have_gltf_converter': self.handle_have_gltf_converter,
112 }
113
114 def handle_page_loaded(self, data):
115 '''
116 Handle page load / startup feedback
117 '''
118 status = '> Using MaterialX version: ' + mx.getVersionString()
119 emit('materialx_version', {'status': status}, broadcast=True)
120
121 def handle_load_materialx(self, data):
122 '''
123 Handle loading in of MaterialX document
124 '''
125 materialx_file = data.get('materialxFile', 'Default MaterialX File')
126 print('> Server: load materialx event received. File:', materialx_file)
127 materialx_content = data.get('content', 'MaterialX content')
128 doc = mx.createDocument()
129 if len(materialx_content) == 0:
130 return
131 mx.readFromXmlString(doc, materialx_content)
132 print('>> MaterialX Document loaded')
133 doc_string = mx.writeToXmlString(doc)
134 emit('materialx_loaded', {'materialxDocument': doc_string}, broadcast=True)
135
136 def handle_render_materialx(self, data):
137 '''
138 Handle request to render MaterialX document
139 '''
140 materialx_string = data.get('materialxDocument', 'MaterialX content')
141 print('> Server: render_materialx event received')
142
143 # Get environment variable: MATERIALX_DEFAULT_VIEWER
144 ilm_viewer = os.getenv('MATERIALX_DEFAULT_VIEWER', '')
145 if len(ilm_viewer) == 0:
146 print('>> MATERIALX_DEFAULT_VIEWER environment variable not set')
147 return
148
149 doc = mx.createDocument()
150 if len(materialx_string) == 0:
151 return
152
153 stdlib = mx.createDocument()
154 mx.loadLibraries(mx.getDefaultDataLibraryFolders(), mx.getDefaultDataSearchPath(), stdlib)
155 doc.importLibrary(stdlib)
156 mx.readFromXmlString(doc, materialx_string)
157
158 # Get platform temporary folder location
159 temp_location = os.getenv('TEMP', '/tmp')
160 if not os.path.exists(temp_location):
161 temp_location = '/tmp'
162 # Generate a temp file name based on the current date and time
163 temp_name = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '.mtlx'
164 temp_file = f'{temp_location}/{temp_name}'
165 mx.writeToXmlFile(doc, temp_file)
166 capture_filename = temp_file.replace(".mtlx", ".png")
167 cmd = f'{ilm_viewer} --screenWidth 512 --screenHeight 512 '
168 cmd += f' --captureFilename {capture_filename} --material {temp_file}'
169 print('>> Rendering:', cmd)
170 os.system(cmd)
171 # Delete the temp files
172 os.remove(temp_file)
173
174 image_base64 = self.convert_png_to_base64(capture_filename)
175 os.remove(capture_filename)
176 print('>> Emit materialx_rendered event')
177 emit('materialx_rendered', {'image': image_base64}, broadcast=True)
178
179 def handle_convert_to_usd(self, data):
180 '''
181 Handle request to convert MaterialX to USD
182 '''
183 if not have_usd_converter:
184 emit('usd_converted', {'usdDocument': ''}, broadcast=True)
185 return
186
187 materialx_string = data.get('materialxDocument', '')
188 print('> Server: convert-to-usd event received')
189 doc = mx.createDocument()
190 if len(materialx_string) == 0:
191 return
192 try:
193 mx.readFromXmlString(doc, materialx_string)
194 print('>> MaterialX document loaded')
195 stage_string = convertMtlxToUsd(doc, True)
196 print('>> USD stage created.')
197 emit('usd_converted', {'usdDocument': stage_string}, broadcast=True)
198 except Exception as e:
199 print(f'>> Error during USD conversion: {e}')
200 emit('usd_converted', {'usdDocument': ''}, broadcast=True)
201
202 def handle_convert_to_glTF(self, data):
203 '''
204 Handle request to convert MaterialX to glTF Texture Procedural
205 graph
206 '''
207 if not have_gltf_converter:
208 emit('gltf_converted', {'document': '{}'}, broadcast=True)
209 return
210
211 materialx_string = data.get('materialxDocument', '')
212 print('> Server: convert_to_glTF event received')
213 stdlib, _ = MxGLTFPTUtil.load_standard_libraries()
214 doc = MxGLTFPTUtil.create_working_document([stdlib])
215 if len(materialx_string) == 0:
216 return
217 mx.readFromXmlString(doc, materialx_string)
218 print('>> MaterialX document loaded')
219 converter = MxGLTFPT.glTFMaterialXConverter()
220 json_string, status = converter.materialX_to_glTF(doc)
221 if not json_string:
222 print('>> Error converting to glTF:', status)
223 json_string = '{}'
224 print('>> glTF JSON created:')
225 emit('gltf_converted', {'document': json_string}, broadcast=True)
226
228 '''
229 Handle query to see if glTF converter is available
230 '''
231 emit('have_gltf_converter', {'have_gltf_converter': have_gltf_converter}, broadcast=True)
232
233 @staticmethod
234 def convert_png_to_base64(file_path):
235 '''
236 Utility to load in png image from disk and return Base64
237 representation
238 '''
239 with open(file_path, "rb") as image_file:
240 binary_data = image_file.read()
241 return base64.b64encode(binary_data).decode('utf-8')
242
243def deployment_platform():
244 # Small detection setup to determine the platform
245 # More can be added as needed
246 if os.environ.get('RENDER_SERVICE_ID'):
247 return "Render"
248 elif os.environ.get('HEROKU'):
249 return "Heroku"
250 elif os.environ.get('AWS_EXECUTION_ENV'):
251 return "AWS"
252 else:
253 # Assume running local
254 return "Local"
255
256def main():
257 '''
258 Main command line interface
259 '''
260 parser = argparse.ArgumentParser(description="GPUOpen MaterialX Application")
261 parser.add_argument('--host', type=str, default='127.0.0.1', help="Host address to run the server on (default: 127.0.0.1)")
262 parser.add_argument('--port', type=int, default=None, help="Port to run the server on (default: 8000)")
263 parser.add_argument('--home', type=str, default='MaterialXConversionApp.html', help="Home page.")
264
265 args = parser.parse_args()
266
267 app_host = args.host
268 deploy_platform = deployment_platform()
269 if deployment_platform() == "Render":
270 # Enforce this for Render deployments
271 app_host = '0.0.0.0'
272
273 # Get the port from the environment or fallback to args
274 app_port = int(os.environ.get('PORT', 8080))
275 if args.port is not None:
276 app_port = args.port
277
278 app = MaterialXConversionApp(args.home)
279 app.run(host=app_host, port=app_port, deployment_platform=deployment_platform)
280
281if __name__ == "__main__":
282 main()
handle_convert_to_glTF(self, data)
Handle request to convert MaterialX to glTF Texture Procedural graph.
_setup_event_handler_map(self)
Set up dictionary of mapping event names to their handlers.
handle_page_loaded(self, data)
Handle page load / startup feedback.
handle_load_materialx(self, data)
Handle loading in of MaterialX document.
handle_convert_to_usd(self, data)
Handle request to convert MaterialX to USD.
handle_render_materialx(self, data)
Handle request to render MaterialX document.
convert_png_to_base64(file_path)
Utility to load in png image from disk and return Base64 representation.
handle_have_gltf_converter(self)
Handle query to see if glTF converter is available.
_setup_event_handler_map(self)
Pure virtual method: Must be implemented by subclasses.
run(self, host, port, deployment_platform, debug=True)
Run the Flask server with SocketIO.
_register_socket_events(self)
Register SocketIO events.