Skip to content

Commit

Permalink
Merge pull request #978 from alicevision/dev/transformGizmo3D
Browse files Browse the repository at this point in the history
[Viewer3D] Input bounding box (Meshing) & manual transformation (SfMTransform) thanks to a new 3D Gizmo
  • Loading branch information
fabiencastan authored Aug 24, 2020
2 parents a24bfc1 + 688027a commit 04c21ff
Show file tree
Hide file tree
Showing 32 changed files with 1,741 additions and 207 deletions.
5 changes: 3 additions & 2 deletions meshroom/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
BaseObject = None
Variant = None
VariantList = None
JSValue = None

if meshroom.backend == meshroom.Backend.PYSIDE:
# PySide types
from .qt import DictModel, ListModel, Slot, Signal, Property, BaseObject, Variant, VariantList
from .qt import DictModel, ListModel, Slot, Signal, Property, BaseObject, Variant, VariantList, JSValue
elif meshroom.backend == meshroom.Backend.STANDALONE:
# Core types
from .core import DictModel, ListModel, Slot, Signal, Property, BaseObject, Variant, VariantList
from .core import DictModel, ListModel, Slot, Signal, Property, BaseObject, Variant, VariantList, JSValue


class _BaseModel:
Expand Down
1 change: 1 addition & 0 deletions meshroom/common/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,4 @@ def parent(self):
BaseObject = CoreObject
Variant = object
VariantList = object
JSValue = None
3 changes: 2 additions & 1 deletion meshroom/common/qt.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from PySide2 import QtCore
from PySide2 import QtCore, QtQml


class QObjectListModel(QtCore.QAbstractListModel):
Expand Down Expand Up @@ -374,3 +374,4 @@ def sort(self):
BaseObject = QtCore.QObject
Variant = "QVariant"
VariantList = "QVariantList"
JSValue = QtQml.QJSValue
19 changes: 13 additions & 6 deletions meshroom/core/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ def updateInternals(self):
isLink = Property(bool, isLink.fget, notify=isLinkChanged)
isDefault = Property(bool, _isDefault, notify=valueChanged)
linkParam = Property(BaseObject, getLinkParam, notify=isLinkChanged)
rootLinkParam = Property(BaseObject, lambda self: self.getLinkParam(recursive=True), notify=isLinkChanged)
node = Property(BaseObject, node.fget, constant=True)
enabledChanged = Signal()
enabled = Property(bool, getEnabled, setEnabled, notify=enabledChanged)
Expand Down Expand Up @@ -300,8 +301,8 @@ def _set_value(self, value):
self._value = value
# New value
else:
self.desc.validateValue(value)
self.extend(value)
newValue = self.desc.validateValue(value)
self.extend(newValue)
self.requestGraphUpdate()

@raiseIfLink
Expand Down Expand Up @@ -410,10 +411,16 @@ def __getattr__(self, key):
raise AttributeError(key)

def _set_value(self, exportedValue):
self.desc.validateValue(exportedValue)
# set individual child attribute values
for key, value in exportedValue.items():
self._value.get(key).value = value
value = self.desc.validateValue(exportedValue)
if isinstance(value, dict):
# set individual child attribute values
for key, v in value.items():
self._value.get(key).value = v
elif isinstance(value, (list, tuple)):
for attrDesc, v in zip(self.desc._groupDesc, value):
self._value.get(attrDesc.name).value = v
else:
raise AttributeError("Failed to set on GroupAttribute: {}".format(str(value)))

@Slot(str, result=Attribute)
def childAttribute(self, key):
Expand Down
43 changes: 32 additions & 11 deletions meshroom/core/desc.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from meshroom.common import BaseObject, Property, Variant, VariantList
from meshroom.common import BaseObject, Property, Variant, VariantList, JSValue
from meshroom.core import pyCompatibility
from enum import Enum # available by default in python3. For python2: "pip install enum34"
import math
import os
import psutil

import ast

class Attribute(BaseObject):
"""
Expand Down Expand Up @@ -32,12 +32,12 @@ def __init__(self, name, label, description, value, advanced, uid, group, enable
type = Property(str, lambda self: self.__class__.__name__, constant=True)

def validateValue(self, value):
""" Return validated/conformed 'value'.
""" Return validated/conformed 'value'. Need to be implemented in derived classes.
Raises:
ValueError: if value does not have the proper type
"""
return value
raise NotImplementedError("Attribute.validateValue is an abstract function that should be implemented in the derived class.")

def matchDescription(self, value, conform=False):
""" Returns whether the value perfectly match attribute's description.
Expand Down Expand Up @@ -68,6 +68,14 @@ def __init__(self, elementDesc, name, label, description, group='allParams', adv
joinChar = Property(str, lambda self: self._joinChar, constant=True)

def validateValue(self, value):
if JSValue is not None and isinstance(value, JSValue):
# Note: we could use isArray(), property("length").toInt() to retrieve all values
raise ValueError("ListAttribute.validateValue: cannot recognize QJSValue. Please, use JSON.stringify(value) in QML.")
if isinstance(value, pyCompatibility.basestring):
# Alternative solution to set values from QML is to convert values to JSON string
# In this case, it works with all data types
value = ast.literal_eval(value)

if not isinstance(value, (list, tuple)):
raise ValueError('ListAttribute only supports list/tuple input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))
return value
Expand Down Expand Up @@ -95,12 +103,25 @@ def __init__(self, groupDesc, name, label, description, group='allParams', advan
groupDesc = Property(Variant, lambda self: self._groupDesc, constant=True)

def validateValue(self, value):
""" Ensure value is a dictionary with keys compatible with the group description. """
if not isinstance(value, dict):
raise ValueError('GroupAttribute only supports dict input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))
invalidKeys = set(value.keys()).difference([attr.name for attr in self._groupDesc])
if invalidKeys:
raise ValueError('Value contains key that does not match group description : {}'.format(invalidKeys))
""" Ensure value is compatible with the group description and convert value if needed. """
if JSValue is not None and isinstance(value, JSValue):
# Note: we could use isArray(), property("length").toInt() to retrieve all values
raise ValueError("GroupAttribute.validateValue: cannot recognize QJSValue. Please, use JSON.stringify(value) in QML.")
if isinstance(value, pyCompatibility.basestring):
# Alternative solution to set values from QML is to convert values to JSON string
# In this case, it works with all data types
value = ast.literal_eval(value)

if isinstance(value, dict):
invalidKeys = set(value.keys()).difference([attr.name for attr in self._groupDesc])
if invalidKeys:
raise ValueError('Value contains key that does not match group description : {}'.format(invalidKeys))
elif isinstance(value, (list, tuple)):
if len(value) != len(self._groupDesc):
raise ValueError('Value contains incoherent number of values: desc size: {}, value size: {}'.format(len(self._groupDesc), len(value)))
else:
raise ValueError('GroupAttribute only supports dict/list/tuple input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))

return value

def matchDescription(self, value, conform=False):
Expand Down Expand Up @@ -169,7 +190,7 @@ def __init__(self, name, label, description, value, uid, group='allParams', adva

def validateValue(self, value):
try:
return bool(int(value)) # int cast is useful to handle string values ('0', '1')
return bool(int(value)) # int cast is useful to handle string values ('0', '1')
except:
raise ValueError('BoolParam only supports bool value (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))

Expand Down
6 changes: 5 additions & 1 deletion meshroom/core/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,11 @@ def __init__(self, name, parent=None):
def clear(self):
self.header.clear()
self._compatibilityNodes.clear()
self._nodes.clear()
self._edges.clear()
# Tell QML nodes are going to be deleted
for node in self._nodes:
node.alive = False
self._nodes.clear()

@property
def fileFeatures(self):
Expand Down Expand Up @@ -437,6 +440,7 @@ def removeNode(self, nodeName):
self.removeEdge(edge.dst)
inEdges[edge.dst.getFullName()] = edge.src.getFullName()

node.alive = False
self._nodes.remove(node)
self.update()

Expand Down
14 changes: 14 additions & 0 deletions meshroom/core/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ def __init__(self, nodeType, position=None, parent=None, **kwargs):
self._position = position or Position()
self._attributes = DictModel(keyAttrName='name', parent=self)
self.attributesPerUid = defaultdict(set)
self._alive = True # for QML side to know if the node can be used or is going to be deleted

def __getattr__(self, k):
try:
Expand Down Expand Up @@ -526,6 +527,17 @@ def position(self, value):
self._position = value
self.positionChanged.emit()

@property
def alive(self):
return self._alive

@alive.setter
def alive(self, value):
if self._alive == value:
return
self._alive = value
self.aliveChanged.emit()

@property
def depth(self):
return self.graph.getDepth(self)
Expand Down Expand Up @@ -792,6 +804,8 @@ def __repr__(self):
globalStatusChanged = Signal()
globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged)
isComputed = Property(bool, _isComputed, notify=globalStatusChanged)
aliveChanged = Signal()
alive = Property(bool, alive.fget, alive.fset, notify=aliveChanged)


class Node(BaseNode):
Expand Down
97 changes: 96 additions & 1 deletion meshroom/nodes/aliceVision/Meshing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "5.0"
__version__ = "6.0"

from meshroom.core import desc

Expand Down Expand Up @@ -35,6 +35,101 @@ class Meshing(desc.CommandLineNode):
value='',
uid=[0],
),
desc.BoolParam(
name='useBoundingBox',
label='Custom Bounding Box',
description='Edit the meshing bounding box. If enabled, it takes priority over the Estimate From SfM option. Parameters can be adjusted in advanced settings.',
value=False,
uid=[0],
group=''
),
desc.GroupAttribute(
name="boundingBox",
label="Bounding Box Settings",
description="Translation, rotation and scale of the bounding box.",
groupDesc=[
desc.GroupAttribute(
name="bboxTranslation",
label="Translation",
description="Position in space.",
groupDesc=[
desc.FloatParam(
name="x", label="x", description="X Offset",
value=0.0,
uid=[0],
range=(-20.0, 20.0, 0.01)
),
desc.FloatParam(
name="y", label="y", description="Y Offset",
value=0.0,
uid=[0],
range=(-20.0, 20.0, 0.01)
),
desc.FloatParam(
name="z", label="z", description="Z Offset",
value=0.0,
uid=[0],
range=(-20.0, 20.0, 0.01)
)
],
joinChar=","
),
desc.GroupAttribute(
name="bboxRotation",
label="Euler Rotation",
description="Rotation in Euler degrees.",
groupDesc=[
desc.FloatParam(
name="x", label="x", description="Euler X Rotation",
value=0.0,
uid=[0],
range=(-90.0, 90.0, 1)
),
desc.FloatParam(
name="y", label="y", description="Euler Y Rotation",
value=0.0,
uid=[0],
range=(-180.0, 180.0, 1)
),
desc.FloatParam(
name="z", label="z", description="Euler Z Rotation",
value=0.0,
uid=[0],
range=(-180.0, 180.0, 1)
)
],
joinChar=","
),
desc.GroupAttribute(
name="bboxScale",
label="Scale",
description="Scale of the bounding box.",
groupDesc=[
desc.FloatParam(
name="x", label="x", description="X Scale",
value=1.0,
uid=[0],
range=(0.0, 20.0, 0.01)
),
desc.FloatParam(
name="y", label="y", description="Y Scale",
value=1.0,
uid=[0],
range=(0.0, 20.0, 0.01)
),
desc.FloatParam(
name="z", label="z", description="Z Scale",
value=1.0,
uid=[0],
range=(0.0, 20.0, 0.01)
)
],
joinChar=","
)
],
joinChar=",",
enabled=lambda node: node.useBoundingBox.value,
),
desc.BoolParam(
name='estimateSpaceFromSfM',
label='Estimate Space From SfM',
Expand Down
Loading

0 comments on commit 04c21ff

Please sign in to comment.