2@file materialxgltf/plugin.py
3@namespace materialxgtf/plugin
13 from pygments
import highlight
14 from pygments.lexers
import JsonLexer
15 from pygments.formatters
import HtmlFormatter
17 have_highliting =
False
19from typing
import TYPE_CHECKING
22 global QtCore, QAction, QTextEdit, QDockWidget, QVBoxLayout, QWidget
24 from qtpy
import QtCore
25 from qtpy.QtWidgets
import QAction, QDockWidget, QVBoxLayout, QTextEdit, QWidget
26 logger.info(
"qtpy modules loaded successfully")
28 except ImportError
as e:
29 logger.error(f
"Failed to import qtpy modules: {e}")
32from QuiltiX
import constants, qx_plugin
33from QuiltiX.constants
import ROOT
36from qtpy.QtWebEngineWidgets
import QWebEngineView, QWebEnginePage
37from qtpy.QtQuick
import QQuickWindow
38from qtpy.QtQuick
import QSGRendererInterface
40QQuickWindow.setGraphicsApi(QSGRendererInterface.GraphicsApi.OpenGL)
42logger = logging.getLogger(__name__)
49 import materialxgltf.core
as core
53 logger.error(
"materialxgltf module is not installed.")
58 from pygments
import highlight
59 from pygments.lexers
import JsonLexer
60 from pygments.formatters
import HtmlFormatter
62 have_highliting =
False
70 @brief Highlighter for glTF text
74 @brief Initialize the highlighter
79 self.
formatter = HtmlFormatter(linenos=
False, style=
'github-dark')
83 @brief Highlight the text
84 @param text: The text to highlight
85 @return: The highlighted text
90 f
"{self.formatter.get_style_defs('.highlight')}"
91 f
"pre {{ line-height: 1.0; margin: 0; }}"
94 full_html = f
"<html><head>{styles}</head><body>{highlighted_html}</body></html>"
103 @brief Custom web engine page for the glTF viewer
107 @brief Initialize the custom web engine page
108 @param parent: The parent widget
115 @brief Set the debug flag
116 @param debug: The debug flag
122 @brief Handle JavaScript console messages
125 print(f
"JS: {message} (line: {lineNumber}, source: {sourceID})")
129 Add interestCohort function to the document to disable FLoC
132 document.addEventListener('DOMContentLoaded', (event) => {
133 document.interestCohort = function() { return false; };
136 self.runJavaScript(script)
141 if (
not os.path.exists(glb_file_path)):
142 print(f
"Error: glTF file not found: {glb_file_path}")
145 with open(glb_file_path,
"rb")
as f:
146 binary_data = f.read()
149 base64_data = base64.b64encode(binary_data).decode(
'utf-8')
153 script = f
"localStorage.setItem('glbData', '{base64_data}');"
154 self.runJavaScript(script)
156 print(
"GLB data injected into localStorage.")
158 self.runJavaScript(
'loadFromLocalStorage()')
159 print(
"Update viewer from localStorage.")
164 @brief glTF serializer for MaterialX
169 Initialize the plugin. Adds in:
170 - Menu items for loading and saving glTF files
171 - Menu items for setting options for glTF export
172 - Menu item for showing the current graph as glTF text
174 @param editor: The QuiltiX editor
175 @param root: The root path of QuitiX
184 editor.file_menu.addSeparator()
185 gltfMenu1 = self.
editor.file_menu.addMenu(
"glTF")
188 import_gltf_item = QAction(
"Load glTF...", editor)
190 gltfMenu1.addAction(import_gltf_item)
193 export_gltf_item = QAction(
"Save glTF...", editor)
195 gltfMenu1.addAction(export_gltf_item)
198 export_view_gltf_item = QAction(
"Export to Viewer...", editor)
200 gltfMenu1.addAction(export_view_gltf_item)
203 show_gltf_text = QAction(
"Show glTF as text...", editor)
205 gltfMenu1.addAction(show_gltf_text)
209 editor.options_menu.addSeparator()
210 gltfMenu2 = editor.options_menu.addMenu(
"glTF Options")
231 editor.view_menu.addSeparator()
244 logger.info(f
"Watch file loaded: {data}")
249 Custom about to show event for the view menu. Updates the glTF viewer toggle.
251 self.
editor.on_view_menu_showing()
256 Toggle the glTF viewer dock widget.
262 Set up the glTF viewer dock widget.
264 class glTFWidget(QDockWidget):
266 @brief glTF Viewer widget
270 @brief Sets up a web view and loads in a sample glTF viewer page.
272 super(glTFWidget, self).
__init__(parent)
274 self.setWindowTitle(
"glTF Viewer")
276 self.setFloating(
False)
281 self.
viewer_options +=
'&env=https://kwokcb.github.io/MaterialXLab/documents/resources/Lights/rural_crossroads_1k.hdr'
282 self.
viewer_address =
'https://kwokcb.github.io/MaterialXLab/documents/gltfViewer_simple.html'
305 layout = QVBoxLayout()
309 central_widget = QWidget()
310 central_widget.setLayout(layout)
316 self.setWidget(central_widget)
324 Show a text box with the given text.
325 @param text: The text to show
326 @param title: The title of the text box. Default is empty string.
328 te_text = QTextEdit()
332 highlighted_html = jsonHighlighter.highlight(text)
333 te_text.setHtml(highlighted_html)
335 te_text.setText(text)
336 te_text.setReadOnly(
True)
337 te_text.setParent(self.
editor, QtCore.Qt.Window)
338 te_text.setWindowTitle(title)
339 te_text.resize(1000, 800)
344 Import a glTF file into the current graph.
346 start_path = self.
editor.mx_selection_path
348 start_path = self.
editor.geometry_selection_path
351 start_path = os.path.join(ROOT,
"resources",
"materials")
353 path = self.
editor.request_filepath(
354 title=
"Load glTF file", start_path=start_path, file_filter=
"glTF files (*.gltf)", mode=
"open",
357 if not os.path.exists(path):
358 logger.error(
'Cannot find input file: ' + path)
365 options = core.GLTF2MtlxOptions()
366 options[
'createAssignments'] =
False
367 options[
'addAllInputs'] =
False
368 options[
'addExtractNodes'] =
True
369 gltf2MtlxReader = core.GLTF2MtlxReader()
370 gltf2MtlxReader.setOptions(options)
371 doc = gltf2MtlxReader.convert(path)
376 logger.error(
'Error converting glTF file to MaterialX file')
378 success , err = doc.validate()
383 docString = core.Util.writeMaterialXDocString(doc)
384 doc = mx.createDocument()
385 mx.readFromXmlString(doc, docString)
391 self.
editor.mx_selection_path = path
392 self.
editor.qx_node_graph.load_graph_from_mx_doc(doc)
394 self.
editor.qx_node_graph.mx_file_loaded.emit(
"")
400 Set up the default export options for gltf output.
401 @param path (str): path to the gltf file
402 @param bakeFileName (str): path to the baked file
403 @param bakeResolution (int): resolution of the baked textures. Default is 1024.
404 @param embed_geometry (bool): whether to embed the geometry in the gltf file. Default is False.
405 @return options (dict): Dictionary of options for the conversion
407 options = core.MTLX2GLTFOptions()
409 options[
'debugOutput'] =
False
410 options[
'bakeFileName'] = bakeFileName
412 bakeResolution = max(bakeResolution, 16)
413 options[
'bakeResolution'] = bakeResolution
422 gltfGeometryFile = pkg_resources.resource_filename(
'materialxgltf',
'data/shaderBall.gltf')
423 msg =
'> Load default geometry: %s' % mx.FilePath(gltfGeometryFile).getBaseName()
425 options[
'geometryFile'] = gltfGeometryFile
426 options[
'primsPerMaterial'] =
True
427 options[
'writeDefaultInputs'] =
False
428 options[
'translateShaders'] =
True
429 options[
'bakeTextures'] =
True
431 options[
'addExtractNodes'] =
True
433 searchPath = mx.getDefaultDataSearchPath()
434 if not mx.FilePath(path).isAbsolute():
435 path = os.path.abspath(path)
436 searchPath.append(mx.FilePath(path).getParentPath())
437 searchPath.append(mx.FilePath.getCurrentPath())
440 searchPath.append(mx.FilePath(gltfGeometryFile).getParentPath())
441 options[
'searchPath'] = searchPath
442 logger.info(f
'Export Search path: {searchPath.asString()}')
448 Create a baked path name from an original path
449 @param path (str): The original path
450 @return: The baked path
453 if os.path.isdir(path):
454 path = os.path.join(path,
'temp_baked.mtlx')
456 path = path.replace(
'.mtlx',
'_baked.mtlx')
461 Export the current graph to a glTF file in binary format (glb)
462 - Will perform shader translation if needed to glTF
463 - Will perform baking if needed
464 - Will package to a binary file
465 @param writeToTemp (bool): Whether to write to a temporary file
469 start_path = os.environ[
"TEMP"]
471 start_path = os.path.join(ROOT,
"resources",
"materials")
472 path = os.path.join(start_path, f
"_tmp_quiltix{self.temp_file_counter}.gltf")
475 start_path = self.
editor.mx_selection_path
477 start_path = self.
editor.geometry_selection_path
480 start_path = os.path.join(ROOT,
"resources",
"materials")
482 path = self.
editor.request_filepath(
483 title=
"Save glTF file", start_path=start_path, file_filter=
"glTF files (*.gltf)", mode=
"save",
491 if gltf_string ==
'{}':
497 options[
'packageBinary'] =
True
499 with open(path,
"w")
as f:
501 logger.info(f
"Wrote .gltf file to {path}")
504 binaryFileName = str(path)
505 binaryFileName = binaryFileName.replace(
'.gltf',
'.glb')
506 logger.debug(
'- Packaging GLB file...')
507 mtlx2glTFWriter = core.MTLX2GLTFWriter()
508 mtlx2glTFWriter.setOptions(options)
509 saved, images, buffers = mtlx2glTFWriter.packageGLTF(path, binaryFileName)
510 logger.info(
'- Save GLB file:' + binaryFileName +
'. Status:' + str(saved))
512 logger.debug(
' - Embedded image: ' + image)
513 for buffer
in buffers:
514 logger.debug(
' - Embedded buffer: ' + buffer)
515 logger.debug(
'- Packaging GLB file... finished.')
518 logger.debug(f
'Loading GLB file into glTF viewer: {binaryFileName}')
522 except Exception
as e:
527 Convert the current graph to a glTF document string.
529 - Shader translation if needed (not that only standard surface is supported)
530 - Baking if needed. Note that this writes local files.
531 - Uses the materialgltf package to perform conversion
533 @param options (dict): Dictionary of options for the conversion
534 @return The glTF string.
540 doc = self.
editor.qx_node_graph.get_current_mx_graph_doc()
544 mtlx2glTFWriter = core.MTLX2GLTFWriter()
545 mtlx2glTFWriter.setOptions(options)
548 stdlib = mx.createDocument()
549 searchPath = mx.getDefaultDataSearchPath()
551 libraryFolders.extend(mx.getDefaultDataLibraryFolders())
552 mx.loadLibraries(libraryFolders, searchPath, stdlib)
553 doc.importLibrary(stdlib)
557 if options[
'translateShaders']:
558 translatedCount = mtlx2glTFWriter.translateShaders(doc)
559 logger.debug(
'- Translated shaders: ' + str(translatedCount))
563 logger.debug(
'--- Forcing baking of textures')
567 if forceBake
or (translatedCount > 0
and options[
'bakeTextures']):
568 bakedFileName = options[
'bakeFileName']
569 bakeResolution = 1024
570 if options[
'bakeResolution']:
571 bakeResolution = options[
'bakeResolution']
572 logger.debug(f
'- START baking to {bakedFileName}. Resolution: {bakeResolution} ...')
573 mtlx2glTFWriter.bakeTextures(doc,
False, bakeResolution, bakeResolution,
True,
574 False,
False, bakedFileName)
575 if os.path.exists(bakedFileName):
576 logger.debug(
' - Baked textures to: ' + bakedFileName)
577 doc, libFiles = core.Util.createMaterialXDoc()
578 mx.readFromXmlFile(doc, bakedFileName, options[
'searchPath'])
579 remappedUris = core.Util.makeFilePathsRelative(doc, bakedFileName)
580 for uri
in remappedUris:
581 logger.debug(
' - Remapped URI: ' + uri[0] +
' to ' + uri[1])
582 core.Util.writeMaterialXDoc(doc, bakedFileName)
583 logger.debug(
'- ... END baking.')
585 gltfString = mtlx2glTFWriter.convert(doc)
590 Show the current graph as glTF text popup.
592 path = self.
editor.mx_selection_path
594 path = self.
editor.geometry_selection_path
596 path = os.path.join(ROOT,
"resources",
"materials")
602 logger.debug(
'Show glTF text triggered. Path:' + path +
'. bakeFileName: ' + bakeFileName)
611 @brief After UI initialization, add the MaterialX glTF serializer to the editor.
613 logger.info(
"Adding MaterialX glTF serializer")
620 @brief Get the name of the plugin.
621 @return The name of the plugin.'''
623 return "MaterialX glTF Serializer"
628 @brief Check if the plugin is valid. That is the glTF serializer module is installed.
629 @return True if the plugin is valid), False otherwise.
Highlighter for glTF text.
__init__(self)
Initialize the highlighter.
highlight(self, text)
Highlight the text.
glTF serializer for MaterialX
None export_gltf_triggered(self, writeToTemp=False)
Export the current graph to a glTF file in binary format (glb)
dict setup_default_export_options(self, path, bakeFileName, bakeResolution=1024, embed_geometry=False)
Set up the default export options for gltf output.
create_baked_path(self, path)
Create a baked path name from an original path.
act_gltf_viewer
Add viewer toggle.
str convert_graph_to_gltf(self, options)
Convert the current graph to a glTF document string.
None on_gltf_viewer_toggled(self, checked)
Toggle the glTF viewer dock widget.
None import_gltf_triggered(self)
Import a glTF file into the current graph.
None __init__(self, editor, root)
Initialize the plugin.
None setup_gltf_viewer_doc(self)
Set up the glTF viewer dock widget.
handle_file_loaded(self, data)
show_gltf_text_triggered(self)
Show the current graph as glTF text popup.
None show_text_box(self, text, title="")
Core utilities.
custom_on_view_menu_about_to_show(self)
Custom about to show event for the view menu.
custom_on_view_menu_about_to_show
Custom web engine page for the glTF viewer.
setDebug(self, debug)
Set the debug flag.
__init__(self, parent=None)
Initialize the custom web engine page.
injectJavaScript(self)
Add interestCohort function to the document to disable FLoC.
javaScriptConsoleMessage(self, level, message, lineNumber, sourceID)
Handle JavaScript console messages.
load_glb(self, glb_file_path)
after_ui_init("quiltix.QuiltiXWindow" editor)
After UI initialization, add the MaterialX glTF serializer to the editor.
bool is_valid()
Check if the plugin is valid.
str plugin_name()
Get the name of the plugin.
load_qt_modules()
Function to delay loading of Qt modules until after the QuiltiX UI is initialized.