Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions meshroom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,121 +86,136 @@
ERROR_NO_RETRY = -999 # It's actually -999 % 256 => 25


def setupEnvironment(backend=Backend.STANDALONE):
"""
Setup environment for Meshroom to work in a prebuilt, standalone configuration.

Use 'MESHROOM_INSTALL_DIR' to simulate standalone configuration with a path to a Meshroom installation folder.

# Meshroom standalone structure

- Meshroom/
- aliceVision/
- bin/ # runtime bundled binaries (windows: exe + libs, unix: executables)
- lib/ # runtime bundled libraries (unix: libs)
- share/ # resource files
- aliceVision/
- COPYING.md # AliceVision COPYING file
- cameraSensors.db # sensor database
- vlfeat_K80L3.tree # voctree file
- lib/ # Python lib folder
- qtPlugins/
- plugins/
Meshroom # main executable
COPYING.md # Meshroom COPYING file
"""

init(backend)

def addToEnvPath(var, val, index=-1):
"""
Add paths to the given environment variable.

Args:
var (str): the name of the variable to add paths to
val (str or list of str): the path(s) to add
index (int): insertion index
"""
if not val:
return

paths = os.environ.get(var, "").split(os.pathsep)

if not isinstance(val, (list, tuple)):
val = [val]

if index == -1:
paths.extend(val)
elif index == 0:
paths = val + paths
else:
raise ValueError("addToEnvPath: index must be -1 or 0.")
os.environ[var] = os.pathsep.join(paths)

# setup root directory (override possible by setting "MESHROOM_INSTALL_DIR" environment variable)
rootDir = os.path.dirname(sys.executable) if isFrozen else os.environ.get("MESHROOM_INSTALL_DIR", None)
logging.debug(f"isFrozen={isFrozen}")
logging.debug(f"sys.executable={sys.executable}")
logging.debug(f"rootDir={rootDir}")

if rootDir:
os.environ["MESHROOM_INSTALL_DIR"] = rootDir

aliceVisionDir = os.path.join(rootDir, "aliceVision")
# default directories
aliceVisionBinDir = os.path.join(aliceVisionDir, "bin")
aliceVisionShareDir = os.path.join(aliceVisionDir, "share", "aliceVision")
qtPluginsDir = os.path.join(rootDir, "qtPlugins")
pluginsDir = os.path.join(rootDir, "plugins")
sensorDBPath = os.path.join(aliceVisionShareDir, "cameraSensors.db")
voctreePath = os.path.join(aliceVisionShareDir, "vlfeat_K80L3.SIFT.tree")
sphereDetectionModel = os.path.join(aliceVisionShareDir, "sphereDetection_Mask-RCNN.onnx")
semanticSegmentationModel = os.path.join(aliceVisionShareDir, "fcn_resnet50.onnx")
colorChartDetectionModelFolder = os.path.join(aliceVisionShareDir, "ColorChartDetectionModel")

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)

# Add all available plugins
if os.path.exists(pluginsDir):
subfolders = [f.path for f in os.scandir(pluginsDir) if f.is_dir()]
for plugin in subfolders:
addToEnvPath("MESHROOM_PLUGINS_PATH", plugin, 0)

variables = {
"ALICEVISION_ROOT": aliceVisionDir,
"ALICEVISION_SENSOR_DB": sensorDBPath,
"ALICEVISION_VOCTREE": voctreePath,
"ALICEVISION_SPHERE_DETECTION_MODEL": sphereDetectionModel,
"ALICEVISION_SEMANTIC_SEGMENTATION_MODEL": semanticSegmentationModel,
"ALICEVISION_COLORCHARTDETECTION_MODEL_FOLDER": colorChartDetectionModelFolder
}

for key, value in variables.items():
if key not in os.environ and os.path.exists(value):
logging.debug(f"Set {key}: {value}")
os.environ[key] = value

# Add nodes and templates from AliceVision
aliceVisionPluginDir = os.path.join(aliceVisionDir, "share", "meshroom")
addToEnvPath("MESHROOM_NODES_PATH", aliceVisionPluginDir)
addToEnvPath("MESHROOM_PIPELINE_TEMPLATES_PATH", aliceVisionPluginDir)

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", ""))

Check notice on line 212 in meshroom/__init__.py

View check run for this annotation

codefactor.io / CodeFactor

meshroom/__init__.py#L89-L212

Complex Method


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"
6 changes: 6 additions & 0 deletions meshroom/core/cgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
7 changes: 5 additions & 2 deletions meshroom/core/desc/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,15 @@ def matchDescription(self, value, strict=True):
class GroupAttribute(Attribute):
""" A macro Attribute composed of several Attributes """
@deprecated.depreciateParam("group", "Param 'group' on {name} should not be used anymore. Please use 'commandLineGroup' instead")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@deprecated.depreciateParam("group", "Param 'group' on {name} should not be used anymore. Please use 'commandLineGroup' instead")
@deprecated.depreciateParam("group", "Param argument 'group' on {name} should not be used anymore. Please use 'commandLineGroup' instead")
@deprecated.depreciateParam("groupDesc", "GroupAttribute argument 'groupDesc' on {name} should not be used anymore. Please use 'items' instead")

def __init__(self, items, name, label, description, group="allParams", commandLineGroup=_setParamSentinel,
def __init__(self, items=None, name="", label="", description="", 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
Expand Down
14 changes: 14 additions & 0 deletions meshroom/ui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())})
Expand Down
44 changes: 43 additions & 1 deletion meshroom/ui/components/scene3D.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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('<fff', 0.0, 1.0, 0.0) * vertexCount)
normalBuffer = Qt3DCore.QBuffer(geo)
normalBuffer.setData(normalData)

# Create the normal attribute
normalAttr = Qt3DCore.QAttribute(geo)
normalAttr.setName("vertexNormal")
normalAttr.setVertexBaseType(Qt3DCore.QAttribute.Float)
normalAttr.setVertexSize(3)
normalAttr.setAttributeType(Qt3DCore.QAttribute.VertexAttribute)
normalAttr.setBuffer(normalBuffer)
normalAttr.setByteStride(3 * 4) # 3 floats * 4 bytes
normalAttr.setByteOffset(0)
normalAttr.setCount(vertexCount)

geo.addAttribute(normalAttr)


class TrackballController(QObject):
"""
Expand Down
18 changes: 18 additions & 0 deletions meshroom/ui/qml/Viewer3D/BoundingBox.qml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,24 @@ Entity {
])
}
}
Attribute {
attributeType: Attribute.VertexAttribute
vertexBaseType: Attribute.Float
vertexSize: 3
count: 24
name: defaultNormalAttributeName
buffer: Buffer {
data: {
var f32 = new Float32Array(24 * 3)
for (var i = 0; i < 24; i++) {
f32[3 * i] = 0.0
f32[3 * i + 1] = 1.0
f32[3 * i + 2] = 0.0
}
return f32
}
}
}
boundingVolumePositionAttribute: boundingBoxPosition
}
}
Expand Down
19 changes: 19 additions & 0 deletions meshroom/ui/qml/Viewer3D/Grid3D.qml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ Entity {
}
}
}
Attribute {
id: gridNormal
attributeType: Attribute.VertexAttribute
vertexBaseType: Attribute.Float
vertexSize: 3
count: gridPosition.count
name: defaultNormalAttributeName
buffer: Buffer {
data: {
var f32 = new Float32Array(gridPosition.count * 3)
for (var i = 0; i < gridPosition.count; i++) {
f32[3 * i] = 0.0
f32[3 * i + 1] = 1.0
f32[3 * i + 2] = 0.0
}
return f32
}
}
}
boundingVolumePositionAttribute: gridPosition
}
},
Expand Down
17 changes: 17 additions & 0 deletions meshroom/ui/qml/Viewer3D/Locator3D.qml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ Entity {
])
}
}
Attribute {
attributeType: Attribute.VertexAttribute
vertexBaseType: Attribute.Float
vertexSize: 3
count: 6
name: defaultNormalAttributeName
buffer: Buffer {
data: new Float32Array([
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0
])
}
}
Attribute {
attributeType: Attribute.VertexAttribute
vertexBaseType: Attribute.Float
Expand Down
6 changes: 3 additions & 3 deletions meshroom/ui/qml/Viewer3D/MaterialSwitcher.qml
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ Entity {
DiffuseMapMaterial {
id: textured
objectName: "TexturedMaterial"
ambient: root.ambient
shininess: root.shininess
specular: root.specular
ambient: "#111"
shininess: 1.0
specular: "#000"
Comment on lines -98 to +100
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason to change that?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the mesh preview is looking a bit phased off, like whiteish. I tried to make it look like full saturated colour but it didn't worked either and I was feeling very happy about the app is working, I directly go into PR.

Tldr; it just a mess I forgot to revert

diffuse: TextureLoader {
magnificationFilter: Texture.Linear
mirrored: false
Expand Down
19 changes: 19 additions & 0 deletions meshroom/ui/qml/Viewer3D/Materials/SphericalHarmonicsEffect.qml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ Effect {
}
}
]
},
Technique {
graphicsApiFilter {
api: GraphicsApiFilter.OpenGL
profile: GraphicsApiFilter.CoreProfile
majorVersion: 3
minorVersion: 2
}

filterKeys: [ FilterKey { name: "renderingStyle"; value: "forward" } ]

renderPasses: [
RenderPass {
shaderProgram: ShaderProgram {
vertexShaderCode: loadSource(Qt.resolvedUrl("shaders/SphericalHarmonics_gl.vert"))
fragmentShaderCode: loadSource(Qt.resolvedUrl("shaders/SphericalHarmonics_gl.frag"))
}
}
]
}
]
}
23 changes: 23 additions & 0 deletions meshroom/ui/qml/Viewer3D/Materials/WireframeEffect.qml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ Effect {
}
}
]
},
Technique {
graphicsApiFilter {
api: GraphicsApiFilter.OpenGL
profile: GraphicsApiFilter.CoreProfile
majorVersion: 3
minorVersion: 2
}

filterKeys: [ FilterKey { name: "renderingStyle"; value: "forward" } ]

parameters: [
]

renderPasses: [
RenderPass {
shaderProgram: ShaderProgram {
vertexShaderCode: loadSource(Qt.resolvedUrl("shaders/robustwireframe_gl.vert"))
geometryShaderCode: loadSource(Qt.resolvedUrl("shaders/robustwireframe_gl.geom"))
fragmentShaderCode: loadSource(Qt.resolvedUrl("shaders/robustwireframe_gl.frag"))
}
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#version 450 core

layout(location = 0) in vec3 normal;
layout(location = 0) in vec3 worldPos;
layout(location = 0) out vec4 fragColor;

layout(std140, binding = 0) uniform qt3d_render_view_uniforms {
Expand Down Expand Up @@ -47,18 +47,19 @@ vec3 resolveSH_Opt(vec3 premulCoefficients[9], vec3 dir)
result += premulCoefficients[5] * (dir.x * dir.z);
result += premulCoefficients[6] * (dir.y * dir.z);
result += premulCoefficients[7] * (dirSq.x - dirSq.y);
result += premulCoefficients[8] * (3 * dirSq.z - 1);
result += premulCoefficients[8] * (3.0 * dirSq.z - 1.0);
return result;
}

void main()
{
if(displayNormals) {
// Display normals mode
// Compute flat face normal from screen-space position derivatives
vec3 normal = normalize(cross(dFdx(worldPos), dFdy(worldPos)));

if (displayNormals) {
fragColor = vec4(normal, 1.0);
}
else {
// Calculate the color from spherical harmonics coeffs
fragColor = vec4(resolveSH_Opt(shCoeffs, normal), 1.0);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#version 450 core

layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec3 vertexNormal;

layout(location = 0) out vec3 normal;
layout(location = 0) out vec3 worldPos;

layout(std140, binding = 0) uniform qt3d_render_view_uniforms {
mat4 viewMatrix;
Expand Down Expand Up @@ -35,6 +34,6 @@ layout(std140, binding = 1) uniform qt3d_command_uniforms {

void main()
{
normal = vertexNormal;
worldPos = (modelMatrix * vec4(vertexPosition, 1.0)).xyz;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#version 330 core

in vec3 worldPos;

out vec4 fragColor;

uniform vec3 shCoeffs[9];
uniform bool displayNormals;

vec3 resolveSH_Opt(vec3 premulCoefficients[9], vec3 dir)
{
vec3 result = premulCoefficients[0] * dir.x;
result += premulCoefficients[1] * dir.y;
result += premulCoefficients[2] * dir.z;
result += premulCoefficients[3];
vec3 dirSq = dir * dir;
result += premulCoefficients[4] * (dir.x * dir.y);
result += premulCoefficients[5] * (dir.x * dir.z);
result += premulCoefficients[6] * (dir.y * dir.z);
result += premulCoefficients[7] * (dirSq.x - dirSq.y);
result += premulCoefficients[8] * (3.0 * dirSq.z - 1.0);
return result;
}

void main()
{
// Compute flat face normal from screen-space position derivatives
vec3 normal = normalize(cross(dFdx(worldPos), dFdy(worldPos)));

if (displayNormals) {
fragColor = vec4(normal, 1.0);
}
else {
fragColor = vec4(resolveSH_Opt(shCoeffs, normal), 1.0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#version 330 core

in vec3 vertexPosition;

out vec3 worldPos;

uniform mat4 modelMatrix;
uniform mat4 mvp;

void main()
{
worldPos = (modelMatrix * vec4(vertexPosition, 1.0)).xyz;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
Loading
Loading