diff --git a/meshroom/__init__.py b/meshroom/__init__.py index 874719e012..b84d461485 100644 --- a/meshroom/__init__.py +++ b/meshroom/__init__.py @@ -160,10 +160,15 @@ def addToEnvPath(var, val, index=-1): env = { "PATH": aliceVisionBinDir, - "QT_PLUGIN_PATH": [qtPluginsDir], - "QML2_IMPORT_PATH": [os.path.join(qtPluginsDir, "qml")] } + # Only add Qt plugin/QML paths if the directories actually exist + if os.path.isdir(qtPluginsDir): + env["QT_PLUGIN_PATH"] = [qtPluginsDir] + qmlImportDir = os.path.join(qtPluginsDir, "qml") + if os.path.isdir(qmlImportDir): + env["QML2_IMPORT_PATH"] = [qmlImportDir] + for key, value in env.items(): logging.debug(f"Add to {key}: {value}") addToEnvPath(key, value, 0) @@ -196,6 +201,13 @@ def addToEnvPath(var, val, index=-1): addToEnvPath("PATH", os.environ.get("ALICEVISION_BIN_PATH", "")) if sys.platform == "win32": addToEnvPath("PATH", os.environ.get("ALICEVISION_LIBPATH", "")) + elif sys.platform == "darwin": + aliceVisionLibPath = os.environ.get("ALICEVISION_LIBPATH", "") + addToEnvPath("DYLD_FALLBACK_LIBRARY_PATH", aliceVisionLibPath) + if rootDir: + aliceVisionLibDir = os.path.join(rootDir, "aliceVision", "lib") + if os.path.isdir(aliceVisionLibDir): + addToEnvPath("DYLD_FALLBACK_LIBRARY_PATH", aliceVisionLibDir) else: addToEnvPath("LD_LIBRARY_PATH", os.environ.get("ALICEVISION_LIBPATH", "")) @@ -203,4 +215,7 @@ def addToEnvPath(var, val, index=-1): os.environ["QML_XHR_ALLOW_FILE_READ"] = '1' os.environ["QML_XHR_ALLOW_FILE_WRITE"] = '1' os.environ["PYSEQ_STRICT_PAD"] = '1' -os.environ["QSG_RHI_BACKEND"] = "opengl" +if sys.platform == "darwin": + os.environ.setdefault("QSG_RHI_BACKEND", "metal") +else: + os.environ["QSG_RHI_BACKEND"] = "opengl" diff --git a/meshroom/core/cgroup.py b/meshroom/core/cgroup.py index 7d78a8f895..6e580d2841 100755 --- a/meshroom/core/cgroup.py +++ b/meshroom/core/cgroup.py @@ -63,6 +63,12 @@ def parseNumericList(numericListString): # Try to retrieve limits of cores for the current process' cgroup def getCgroupCpuCount(): + # On non-Linux systems (macOS, Windows), cgroups don't exist. + # Fall back to os.cpu_count() to detect available cores. + import sys + if sys.platform != "linux": + return os.cpu_count() or -1 + # First of all, get pid of process pid = os.getpid() diff --git a/meshroom/core/desc/attribute.py b/meshroom/core/desc/attribute.py index 4a41c3d4e6..6dd0ccd4a0 100644 --- a/meshroom/core/desc/attribute.py +++ b/meshroom/core/desc/attribute.py @@ -237,10 +237,13 @@ class GroupAttribute(Attribute): @deprecated.depreciateParam("group", "Param 'group' on {name} should not be used anymore. Please use 'commandLineGroup' instead") def __init__(self, items, name, label=None, description=None, group="allParams", commandLineGroup=_setParamSentinel, advanced=False, semantic="", enabled=True, joinChar=" ", brackets=None, visible=True, - exposed=False): + exposed=False, groupDesc=None): """ :param items: the description of the Attributes composing this group + :param groupDesc: deprecated alias for 'items' """ + if items is None and groupDesc is not None: + items = groupDesc self._items = items self._joinChar = joinChar self._brackets = brackets diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index 0b42b4ca7c..32ff30cbca 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -277,6 +277,20 @@ def __init__(self, inputArgs): qInstallMessageHandler(MessageHandler.handler) self.engine.addImportPath(qmlDir) + # Add PySide6's bundled QML modules path (Qt3D, QtQuick.Scene3D, etc.) + pyside6QmlPath = os.path.join(os.path.dirname(QtCore.__file__), "Qt", "qml") + if os.path.isdir(pyside6QmlPath): + self.engine.addImportPath(pyside6QmlPath) + + # Add qtAliceVision QML plugins from the AliceVision bundle + for envVar in ("AV_BUNDLE", "MESHROOM_INSTALL_DIR"): + bundleBase = os.environ.get(envVar, "") + if bundleBase: + pluginsPath = os.path.join(bundleBase, "PlugIns") + if os.path.isdir(pluginsPath): + self.engine.addImportPath(pluginsPath) + logging.info(f"Added QML import path: {pluginsPath}") + break # expose available node types that can be instantiated self.engine.rootContext().setContextProperty("_nodeTypes", {n: {"category": pluginManager.getRegisteredNodePlugins()[n].nodeDescriptor.category} for n in sorted(pluginManager.getRegisteredNodePlugins().keys())}) diff --git a/meshroom/ui/components/scene3D.py b/meshroom/ui/components/scene3D.py index afeac42f50..0a0419b04d 100644 --- a/meshroom/ui/components/scene3D.py +++ b/meshroom/ui/components/scene3D.py @@ -1,6 +1,7 @@ +import struct from math import acos, pi, sqrt, atan2, cos, sin, asin -from PySide6.QtCore import QObject, Slot, QSize, Signal, QPointF +from PySide6.QtCore import QObject, Slot, QSize, Signal, QPointF, QByteArray from PySide6.Qt3DCore import Qt3DCore from PySide6.Qt3DRender import Qt3DRender from PySide6.QtGui import QVector3D, QQuaternion, QVector2D, QVector4D, QMatrix4x4 @@ -52,6 +53,47 @@ def vertexColorCount(self, entity): count += sum([attr.count() for attr in geo.attributes() if attr.name() == "vertexColor"]) return count + @Slot(Qt3DCore.QEntity) + def ensureNormals(self, entity): + """ + Add default normal attributes to geometries that don't have them. + This prevents Metal RHI pipeline crashes when built-in Qt3D materials + (which require vertexNormal) are used with meshes lacking normals. + """ + for geo in entity.findChildren(Qt3DCore.QGeometry): + hasNormals = any(attr.name() == "vertexNormal" for attr in geo.attributes()) + if hasNormals: + continue + + # Find the vertexPosition attribute to get vertex count + posAttr = None + for attr in geo.attributes(): + if attr.name() == "vertexPosition": + posAttr = attr + break + if not posAttr: + continue + + vertexCount = posAttr.count() + + # Create a buffer filled with (0, 1, 0) default normals + normalData = QByteArray(struct.pack('