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
21from qtpy
import QtCore
22from qtpy.QtWidgets
import (
29from QuiltiX
import constants, qx_plugin
30from QuiltiX.constants
import ROOT
33from qtpy.QtWebEngineWidgets
import QWebEngineView, QWebEnginePage
34from qtpy.QtQuick
import QQuickWindow
35from qtpy.QtQuick
import QSGRendererInterface
37QQuickWindow.setGraphicsApi(QSGRendererInterface.GraphicsApi.OpenGL)
39logger = logging.getLogger(__name__)
46 import materialxgltf.core
as core
50 logger.error(
"materialxgltf module is not installed.")
55 from pygments
import highlight
56 from pygments.lexers
import JsonLexer
57 from pygments.formatters
import HtmlFormatter
59 have_highliting =
False
66 @brief Highlighter for glTF text
70 @brief Initialize the highlighter
75 self.
formatter = HtmlFormatter(linenos=
False, style=
'github-dark')
79 @brief Highlight the text
80 @param text: The text to highlight
81 @return: The highlighted text
86 f
"{self.formatter.get_style_defs('.highlight')}"
87 f
"pre {{ line-height: 1.0; margin: 0; }}"
90 full_html = f
"<html><head>{styles}</head><body>{highlighted_html}</body></html>"
99 @brief Custom web engine page for the glTF viewer
103 @brief Initialize the custom web engine page
104 @param parent: The parent widget
111 @brief Set the debug flag
112 @param debug: The debug flag
118 @brief Handle JavaScript console messages
121 print(f
"JS: {message} (line: {lineNumber}, source: {sourceID})")
125 Add interestCohort function to the document to disable FLoC
128 document.addEventListener('DOMContentLoaded', (event) => {
129 document.interestCohort = function() { return false; };
132 self.runJavaScript(script)
137 if (
not os.path.exists(glb_file_path)):
138 print(f
"Error: glTF file not found: {glb_file_path}")
141 with open(glb_file_path,
"rb")
as f:
142 binary_data = f.read()
145 base64_data = base64.b64encode(binary_data).decode(
'utf-8')
149 script = f
"localStorage.setItem('glbData', '{base64_data}');"
150 self.runJavaScript(script)
152 print(
"GLB data injected into localStorage.")
154 self.runJavaScript(
'loadFromLocalStorage()')
155 print(
"Update viewer from localStorage.")
159 @brief glTF Viewer widget
163 @brief Sets up a web view and loads in a sample glTF viewer page.
165 super(glTFWidget, self).
__init__(parent)
167 self.setWindowTitle(
"glTF Viewer")
169 self.setFloating(
False)
174 self.
viewer_options +=
'&env=https://kwokcb.github.io/MaterialXLab/documents/resources/Lights/rural_crossroads_1k.hdr'
175 self.
viewer_address =
'https://kwokcb.github.io/MaterialXLab/documents/gltfViewer_simple.html'
198 layout = QVBoxLayout()
202 central_widget = QWidget()
203 central_widget.setLayout(layout)
209 self.setWidget(central_widget)
213 @brief glTF serializer for MaterialX
218 Initialize the plugin. Adds in:
219 - Menu items for loading and saving glTF files
220 - Menu items for setting options for glTF export
221 - Menu item for showing the current graph as glTF text
223 @param editor: The QuiltiX editor
224 @param root: The root path of QuitiX
233 editor.file_menu.addSeparator()
234 gltfMenu1 = self.
editor.file_menu.addMenu(
"glTF")
237 import_gltf_item = QAction(
"Load glTF...", editor)
239 gltfMenu1.addAction(import_gltf_item)
242 export_gltf_item = QAction(
"Save glTF...", editor)
244 gltfMenu1.addAction(export_gltf_item)
247 export_view_gltf_item = QAction(
"Export to Viewer...", editor)
249 gltfMenu1.addAction(export_view_gltf_item)
252 show_gltf_text = QAction(
"Show glTF as text...", editor)
254 gltfMenu1.addAction(show_gltf_text)
258 editor.options_menu.addSeparator()
259 gltfMenu2 = editor.options_menu.addMenu(
"glTF Options")
280 editor.view_menu.addSeparator()
288 Custom about to show event for the view menu. Updates the glTF viewer toggle.
290 self.
editor.on_view_menu_showing()
295 Toggle the glTF viewer dock widget.
301 Set up the glTF viewer dock widget.
309 Show a text box with the given text.
310 @param text: The text to show
311 @param title: The title of the text box. Default is empty string.
313 te_text = QTextEdit()
317 highlighted_html = jsonHighlighter.highlight(text)
318 te_text.setHtml(highlighted_html)
320 te_text.setText(text)
321 te_text.setReadOnly(
True)
322 te_text.setParent(self.
editor, QtCore.Qt.Window)
323 te_text.setWindowTitle(title)
324 te_text.resize(1000, 800)
329 Import a glTF file into the current graph.
331 start_path = self.
editor.mx_selection_path
333 start_path = self.
editor.geometry_selection_path
336 start_path = os.path.join(ROOT,
"resources",
"materials")
338 path = self.
editor.request_filepath(
339 title=
"Load glTF file", start_path=start_path, file_filter=
"glTF files (*.gltf)", mode=
"open",
342 if not os.path.exists(path):
343 logger.error(
'Cannot find input file: ' + path)
350 options = core.GLTF2MtlxOptions()
351 options[
'createAssignments'] =
False
352 options[
'addAllInputs'] =
False
353 options[
'addExtractNodes'] =
True
354 gltf2MtlxReader = core.GLTF2MtlxReader()
355 gltf2MtlxReader.setOptions(options)
356 doc = gltf2MtlxReader.convert(path)
361 logger.error(
'Error converting glTF file to MaterialX file')
363 success , err = doc.validate()
368 docString = core.Util.writeMaterialXDocString(doc)
369 doc = mx.createDocument()
370 mx.readFromXmlString(doc, docString)
376 self.
editor.mx_selection_path = path
377 self.
editor.qx_node_graph.load_graph_from_mx_doc(doc)
379 self.
editor.qx_node_graph.mx_file_loaded.emit(
"")
385 Set up the default export options for gltf output.
386 @param path (str): path to the gltf file
387 @param bakeFileName (str): path to the baked file
388 @param bakeResolution (int): resolution of the baked textures. Default is 1024.
389 @param embed_geometry (bool): whether to embed the geometry in the gltf file. Default is False.
390 @return options (dict): Dictionary of options for the conversion
392 options = core.MTLX2GLTFOptions()
394 options[
'debugOutput'] =
False
395 options[
'bakeFileName'] = bakeFileName
397 bakeResolution = max(bakeResolution, 16)
398 options[
'bakeResolution'] = bakeResolution
404 gltfGeometryFile = pkg_resources.resource_filename(
'materialxgltf',
'data/shaderBall.gltf')
405 msg =
'> Load default geometry: %s' % mx.FilePath(gltfGeometryFile).getBaseName()
407 options[
'geometryFile'] = gltfGeometryFile
408 options[
'primsPerMaterial'] =
True
409 options[
'writeDefaultInputs'] =
False
410 options[
'translateShaders'] =
True
411 options[
'bakeTextures'] =
True
413 options[
'addExtractNodes'] =
True
415 searchPath = mx.getDefaultDataSearchPath()
416 if not mx.FilePath(path).isAbsolute():
417 path = os.path.abspath(path)
418 searchPath.append(mx.FilePath(path).getParentPath())
419 searchPath.append(mx.FilePath.getCurrentPath())
421 searchPath.append(mx.FilePath(gltfGeometryFile).getParentPath())
422 options[
'searchPath'] = searchPath
428 Create a baked path name from an original path
429 @param path (str): The original path
430 @return: The baked path
433 if os.path.isdir(path):
434 path = os.path.join(path,
'temp_baked.mtlx')
436 path = path.replace(
'.mtlx',
'_baked.mtlx')
441 Export the current graph to a glTF file in binary format (glb)
442 - Will perform shader translation if needed to glTF
443 - Will perform baking if needed
444 - Will package to a binary file
445 @param writeToTemp (bool): Whether to write to a temporary file
449 start_path = os.environ[
"TEMP"]
451 start_path = os.path.join(ROOT,
"resources",
"materials")
452 path = os.path.join(start_path,
"_tmp_quiltix.gltf")
454 start_path = self.
editor.mx_selection_path
456 start_path = self.
editor.geometry_selection_path
459 start_path = os.path.join(ROOT,
"resources",
"materials")
461 path = self.
editor.request_filepath(
462 title=
"Save glTF file", start_path=start_path, file_filter=
"glTF files (*.gltf)", mode=
"save",
470 if gltf_string ==
'{}':
476 options[
'packageBinary'] =
True
478 with open(path,
"w")
as f:
480 logger.info(f
"Wrote .gltf file to {path}")
483 binaryFileName = str(path)
484 binaryFileName = binaryFileName.replace(
'.gltf',
'.glb')
485 logger.debug(
'- Packaging GLB file...')
486 mtlx2glTFWriter = core.MTLX2GLTFWriter()
487 mtlx2glTFWriter.setOptions(options)
488 saved, images, buffers = mtlx2glTFWriter.packageGLTF(path, binaryFileName)
489 logger.info(
'- Save GLB file:' + binaryFileName +
'. Status:' + str(saved))
491 logger.debug(
' - Embedded image: ' + image)
492 for buffer
in buffers:
493 logger.debug(
' - Embedded buffer: ' + buffer)
494 logger.debug(
'- Packaging GLB file... finished.')
497 logger.debug(f
'Loading GLB file into glTF viewer: {binaryFileName}')
501 except Exception
as e:
506 Convert the current graph to a glTF document string.
508 - Shader translation if needed (not that only standard surface is supported)
509 - Baking if needed. Note that this writes local files.
510 - Uses the materialgltf package to perform conversion
512 @param options (dict): Dictionary of options for the conversion
513 @return The glTF string.
519 doc = self.
editor.qx_node_graph.get_current_mx_graph_doc()
523 mtlx2glTFWriter = core.MTLX2GLTFWriter()
524 mtlx2glTFWriter.setOptions(options)
527 stdlib = mx.createDocument()
528 searchPath = mx.getDefaultDataSearchPath()
530 libraryFolders.extend(mx.getDefaultDataLibraryFolders())
531 mx.loadLibraries(libraryFolders, searchPath, stdlib)
532 doc.importLibrary(stdlib)
536 if options[
'translateShaders']:
537 translatedCount = mtlx2glTFWriter.translateShaders(doc)
538 logger.debug(
'- Translated shaders: ' + str(translatedCount))
542 logger.debug(
'--- Forcing baking of textures')
546 if forceBake
or (translatedCount > 0
and options[
'bakeTextures']):
547 bakedFileName = options[
'bakeFileName']
548 bakeResolution = 1024
549 if options[
'bakeResolution']:
550 bakeResolution = options[
'bakeResolution']
551 logger.debug(f
'- START baking to {bakedFileName}. Resolution: {bakeResolution} ...')
552 mtlx2glTFWriter.bakeTextures(doc,
False, bakeResolution, bakeResolution,
True,
553 False,
False, bakedFileName)
554 if os.path.exists(bakedFileName):
555 logger.debug(
' - Baked textures to: ' + bakedFileName)
556 doc, libFiles = core.Util.createMaterialXDoc()
557 mx.readFromXmlFile(doc, bakedFileName, options[
'searchPath'])
558 remappedUris = core.Util.makeFilePathsRelative(doc, bakedFileName)
559 for uri
in remappedUris:
560 logger.debug(
' - Remapped URI: ' + uri[0] +
' to ' + uri[1])
561 core.Util.writeMaterialXDoc(doc, bakedFileName)
562 logger.debug(
'- ... END baking.')
564 gltfString = mtlx2glTFWriter.convert(doc)
569 Show the current graph as glTF text popup.
571 path = self.
editor.mx_selection_path
573 path = self.
editor.geometry_selection_path
575 path = os.path.join(ROOT,
"resources",
"materials")
581 logger.debug(
'Show glTF text triggered. Path:' + path +
'. bakeFileName: ' + bakeFileName)
590 @brief After UI initialization, add the MaterialX glTF serializer to the editor.
592 logger.debug(
"Adding MaterialX glTF serializer")
597 @brief Get the name of the plugin.
598 @return The name of the plugin.'''
600 return "MaterialX glTF Serializer"
605 @brief Check if the plugin is valid. That is the glTF serializer module is installed.
606 @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.
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.