mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-07-21 02:27:15 +02:00
Merge remote-tracking branch 'origin/develop' into dev/nodesAndTaskManager
This commit is contained in:
commit
79e1c69d5d
39 changed files with 2060 additions and 209 deletions
|
@ -8,13 +8,14 @@ Property = None
|
||||||
BaseObject = None
|
BaseObject = None
|
||||||
Variant = None
|
Variant = None
|
||||||
VariantList = None
|
VariantList = None
|
||||||
|
JSValue = None
|
||||||
|
|
||||||
if meshroom.backend == meshroom.Backend.PYSIDE:
|
if meshroom.backend == meshroom.Backend.PYSIDE:
|
||||||
# PySide types
|
# 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:
|
elif meshroom.backend == meshroom.Backend.STANDALONE:
|
||||||
# Core types
|
# 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:
|
class _BaseModel:
|
||||||
|
|
|
@ -146,3 +146,4 @@ Property = CoreProperty
|
||||||
BaseObject = CoreObject
|
BaseObject = CoreObject
|
||||||
Variant = object
|
Variant = object
|
||||||
VariantList = object
|
VariantList = object
|
||||||
|
JSValue = None
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from PySide2 import QtCore
|
from PySide2 import QtCore, QtQml
|
||||||
import shiboken2
|
import shiboken2
|
||||||
|
|
||||||
class QObjectListModel(QtCore.QAbstractListModel):
|
class QObjectListModel(QtCore.QAbstractListModel):
|
||||||
|
@ -375,3 +375,4 @@ Property = QtCore.Property
|
||||||
BaseObject = QtCore.QObject
|
BaseObject = QtCore.QObject
|
||||||
Variant = "QVariant"
|
Variant = "QVariant"
|
||||||
VariantList = "QVariantList"
|
VariantList = "QVariantList"
|
||||||
|
JSValue = QtQml.QJSValue
|
||||||
|
|
|
@ -269,6 +269,7 @@ class Attribute(BaseObject):
|
||||||
hasOutputConnections = Property(bool, hasOutputConnections.fget, notify=hasOutputConnectionsChanged)
|
hasOutputConnections = Property(bool, hasOutputConnections.fget, notify=hasOutputConnectionsChanged)
|
||||||
isDefault = Property(bool, _isDefault, notify=valueChanged)
|
isDefault = Property(bool, _isDefault, notify=valueChanged)
|
||||||
linkParam = Property(BaseObject, getLinkParam, notify=isLinkChanged)
|
linkParam = Property(BaseObject, getLinkParam, notify=isLinkChanged)
|
||||||
|
rootLinkParam = Property(BaseObject, lambda self: self.getLinkParam(recursive=True), notify=isLinkChanged)
|
||||||
node = Property(BaseObject, node.fget, constant=True)
|
node = Property(BaseObject, node.fget, constant=True)
|
||||||
enabledChanged = Signal()
|
enabledChanged = Signal()
|
||||||
enabled = Property(bool, getEnabled, setEnabled, notify=enabledChanged)
|
enabled = Property(bool, getEnabled, setEnabled, notify=enabledChanged)
|
||||||
|
@ -312,8 +313,8 @@ class ListAttribute(Attribute):
|
||||||
self._value = value
|
self._value = value
|
||||||
# New value
|
# New value
|
||||||
else:
|
else:
|
||||||
self.desc.validateValue(value)
|
newValue = self.desc.validateValue(value)
|
||||||
self.extend(value)
|
self.extend(newValue)
|
||||||
self.requestGraphUpdate()
|
self.requestGraphUpdate()
|
||||||
|
|
||||||
@raiseIfLink
|
@raiseIfLink
|
||||||
|
@ -422,10 +423,16 @@ class GroupAttribute(Attribute):
|
||||||
raise AttributeError(key)
|
raise AttributeError(key)
|
||||||
|
|
||||||
def _set_value(self, exportedValue):
|
def _set_value(self, exportedValue):
|
||||||
self.desc.validateValue(exportedValue)
|
value = self.desc.validateValue(exportedValue)
|
||||||
|
if isinstance(value, dict):
|
||||||
# set individual child attribute values
|
# set individual child attribute values
|
||||||
for key, value in exportedValue.items():
|
for key, v in value.items():
|
||||||
self._value.get(key).value = value
|
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)
|
@Slot(str, result=Attribute)
|
||||||
def childAttribute(self, key):
|
def childAttribute(self, key):
|
||||||
|
@ -446,7 +453,7 @@ class GroupAttribute(Attribute):
|
||||||
def uid(self, uidIndex):
|
def uid(self, uidIndex):
|
||||||
uids = []
|
uids = []
|
||||||
for k, v in self._value.items():
|
for k, v in self._value.items():
|
||||||
if uidIndex in v.desc.uid:
|
if v.enabled and uidIndex in v.desc.uid:
|
||||||
uids.append(v.uid(uidIndex))
|
uids.append(v.uid(uidIndex))
|
||||||
return hashValue(uids)
|
return hashValue(uids)
|
||||||
|
|
||||||
|
|
|
@ -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 meshroom.core import pyCompatibility
|
||||||
from enum import Enum # available by default in python3. For python2: "pip install enum34"
|
from enum import Enum # available by default in python3. For python2: "pip install enum34"
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import psutil
|
import psutil
|
||||||
|
import ast
|
||||||
|
|
||||||
class Attribute(BaseObject):
|
class Attribute(BaseObject):
|
||||||
"""
|
"""
|
||||||
|
@ -32,12 +32,12 @@ class Attribute(BaseObject):
|
||||||
type = Property(str, lambda self: self.__class__.__name__, constant=True)
|
type = Property(str, lambda self: self.__class__.__name__, constant=True)
|
||||||
|
|
||||||
def validateValue(self, value):
|
def validateValue(self, value):
|
||||||
""" Return validated/conformed 'value'.
|
""" Return validated/conformed 'value'. Need to be implemented in derived classes.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: if value does not have the proper type
|
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):
|
def matchDescription(self, value, conform=False):
|
||||||
""" Returns whether the value perfectly match attribute's description.
|
""" Returns whether the value perfectly match attribute's description.
|
||||||
|
@ -68,6 +68,14 @@ class ListAttribute(Attribute):
|
||||||
joinChar = Property(str, lambda self: self._joinChar, constant=True)
|
joinChar = Property(str, lambda self: self._joinChar, constant=True)
|
||||||
|
|
||||||
def validateValue(self, value):
|
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)):
|
if not isinstance(value, (list, tuple)):
|
||||||
raise ValueError('ListAttribute only supports list/tuple input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))
|
raise ValueError('ListAttribute only supports list/tuple input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))
|
||||||
return value
|
return value
|
||||||
|
@ -95,12 +103,25 @@ class GroupAttribute(Attribute):
|
||||||
groupDesc = Property(Variant, lambda self: self._groupDesc, constant=True)
|
groupDesc = Property(Variant, lambda self: self._groupDesc, constant=True)
|
||||||
|
|
||||||
def validateValue(self, value):
|
def validateValue(self, value):
|
||||||
""" Ensure value is a dictionary with keys compatible with the group description. """
|
""" Ensure value is compatible with the group description and convert value if needed. """
|
||||||
if not isinstance(value, dict):
|
if JSValue is not None and isinstance(value, JSValue):
|
||||||
raise ValueError('GroupAttribute only supports dict input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))
|
# 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])
|
invalidKeys = set(value.keys()).difference([attr.name for attr in self._groupDesc])
|
||||||
if invalidKeys:
|
if invalidKeys:
|
||||||
raise ValueError('Value contains key that does not match group description : {}'.format(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
|
return value
|
||||||
|
|
||||||
def matchDescription(self, value, conform=False):
|
def matchDescription(self, value, conform=False):
|
||||||
|
|
|
@ -223,8 +223,11 @@ class Graph(BaseObject):
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.header.clear()
|
self.header.clear()
|
||||||
self._compatibilityNodes.clear()
|
self._compatibilityNodes.clear()
|
||||||
self._nodes.clear()
|
|
||||||
self._edges.clear()
|
self._edges.clear()
|
||||||
|
# Tell QML nodes are going to be deleted
|
||||||
|
for node in self._nodes:
|
||||||
|
node.alive = False
|
||||||
|
self._nodes.clear()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fileFeatures(self):
|
def fileFeatures(self):
|
||||||
|
@ -437,6 +440,7 @@ class Graph(BaseObject):
|
||||||
self.removeEdge(edge.dst)
|
self.removeEdge(edge.dst)
|
||||||
inEdges[edge.dst.getFullName()] = edge.src.getFullName()
|
inEdges[edge.dst.getFullName()] = edge.src.getFullName()
|
||||||
|
|
||||||
|
node.alive = False
|
||||||
self._nodes.remove(node)
|
self._nodes.remove(node)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
|
|
@ -470,6 +470,7 @@ class BaseNode(BaseObject):
|
||||||
self._position = position or Position()
|
self._position = position or Position()
|
||||||
self._attributes = DictModel(keyAttrName='name', parent=self)
|
self._attributes = DictModel(keyAttrName='name', parent=self)
|
||||||
self.attributesPerUid = defaultdict(set)
|
self.attributesPerUid = defaultdict(set)
|
||||||
|
self._alive = True # for QML side to know if the node can be used or is going to be deleted
|
||||||
self._locked = False
|
self._locked = False
|
||||||
self._duplicates = ListModel(parent=self) # list of nodes with the same uid
|
self._duplicates = ListModel(parent=self) # list of nodes with the same uid
|
||||||
|
|
||||||
|
@ -566,6 +567,17 @@ class BaseNode(BaseObject):
|
||||||
self._position = value
|
self._position = value
|
||||||
self.positionChanged.emit()
|
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
|
@property
|
||||||
def depth(self):
|
def depth(self):
|
||||||
return self.graph.getDepth(self)
|
return self.graph.getDepth(self)
|
||||||
|
@ -927,6 +939,8 @@ class BaseNode(BaseObject):
|
||||||
globalExecModeChanged = Signal()
|
globalExecModeChanged = Signal()
|
||||||
globalExecMode = Property(str, globalExecMode.fget, notify=globalExecModeChanged)
|
globalExecMode = Property(str, globalExecMode.fget, notify=globalExecModeChanged)
|
||||||
isComputed = Property(bool, _isComputed, notify=globalStatusChanged)
|
isComputed = Property(bool, _isComputed, notify=globalStatusChanged)
|
||||||
|
aliveChanged = Signal()
|
||||||
|
alive = Property(bool, alive.fget, alive.fset, notify=aliveChanged)
|
||||||
lockedChanged = Signal()
|
lockedChanged = Signal()
|
||||||
locked = Property(bool, getLocked, setLocked, notify=lockedChanged)
|
locked = Property(bool, getLocked, setLocked, notify=lockedChanged)
|
||||||
duplicatesChanged = Signal()
|
duplicatesChanged = Signal()
|
||||||
|
|
|
@ -150,7 +150,12 @@ class LdrToHdrCalibration(desc.CommandLineNode):
|
||||||
if not cameraInitOutput.node.hasAttribute('viewpoints'):
|
if not cameraInitOutput.node.hasAttribute('viewpoints'):
|
||||||
if cameraInitOutput.node.hasAttribute('input'):
|
if cameraInitOutput.node.hasAttribute('input'):
|
||||||
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
|
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
|
||||||
|
if cameraInitOutput and cameraInitOutput.node and cameraInitOutput.node.hasAttribute('viewpoints'):
|
||||||
viewpoints = cameraInitOutput.node.viewpoints.value
|
viewpoints = cameraInitOutput.node.viewpoints.value
|
||||||
|
else:
|
||||||
|
# No connected CameraInit
|
||||||
|
node.nbBrackets.value = 0
|
||||||
|
return
|
||||||
|
|
||||||
# logging.info("[LDRToHDR] Update start: nb viewpoints:" + str(len(viewpoints)))
|
# logging.info("[LDRToHDR] Update start: nb viewpoints:" + str(len(viewpoints)))
|
||||||
inputs = []
|
inputs = []
|
||||||
|
@ -184,7 +189,12 @@ class LdrToHdrCalibration(desc.CommandLineNode):
|
||||||
exposures = None
|
exposures = None
|
||||||
bracketSizes = set()
|
bracketSizes = set()
|
||||||
if len(exposureGroups) == 1:
|
if len(exposureGroups) == 1:
|
||||||
|
if len(set(exposureGroups[0])) == 1:
|
||||||
|
# Single exposure and multiple views
|
||||||
node.nbBrackets.value = 1
|
node.nbBrackets.value = 1
|
||||||
|
else:
|
||||||
|
# Single view and multiple exposures
|
||||||
|
node.nbBrackets.value = len(exposureGroups[0])
|
||||||
else:
|
else:
|
||||||
for expGroup in exposureGroups:
|
for expGroup in exposureGroups:
|
||||||
bracketSizes.add(len(expGroup))
|
bracketSizes.add(len(expGroup))
|
||||||
|
|
|
@ -180,7 +180,12 @@ class LdrToHdrMerge(desc.CommandLineNode):
|
||||||
if not cameraInitOutput.node.hasAttribute('viewpoints'):
|
if not cameraInitOutput.node.hasAttribute('viewpoints'):
|
||||||
if cameraInitOutput.node.hasAttribute('input'):
|
if cameraInitOutput.node.hasAttribute('input'):
|
||||||
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
|
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
|
||||||
|
if cameraInitOutput and cameraInitOutput.node and cameraInitOutput.node.hasAttribute('viewpoints'):
|
||||||
viewpoints = cameraInitOutput.node.viewpoints.value
|
viewpoints = cameraInitOutput.node.viewpoints.value
|
||||||
|
else:
|
||||||
|
# No connected CameraInit
|
||||||
|
node.nbBrackets.value = 0
|
||||||
|
return
|
||||||
|
|
||||||
# logging.info("[LDRToHDR] Update start: nb viewpoints:" + str(len(viewpoints)))
|
# logging.info("[LDRToHDR] Update start: nb viewpoints:" + str(len(viewpoints)))
|
||||||
inputs = []
|
inputs = []
|
||||||
|
@ -214,7 +219,12 @@ class LdrToHdrMerge(desc.CommandLineNode):
|
||||||
exposures = None
|
exposures = None
|
||||||
bracketSizes = set()
|
bracketSizes = set()
|
||||||
if len(exposureGroups) == 1:
|
if len(exposureGroups) == 1:
|
||||||
|
if len(set(exposureGroups[0])) == 1:
|
||||||
|
# Single exposure and multiple views
|
||||||
node.nbBrackets.value = 1
|
node.nbBrackets.value = 1
|
||||||
|
else:
|
||||||
|
# Single view and multiple exposures
|
||||||
|
node.nbBrackets.value = len(exposureGroups[0])
|
||||||
else:
|
else:
|
||||||
for expGroup in exposureGroups:
|
for expGroup in exposureGroups:
|
||||||
bracketSizes.add(len(expGroup))
|
bracketSizes.add(len(expGroup))
|
||||||
|
|
|
@ -176,7 +176,12 @@ class LdrToHdrSampling(desc.CommandLineNode):
|
||||||
if not cameraInitOutput.node.hasAttribute('viewpoints'):
|
if not cameraInitOutput.node.hasAttribute('viewpoints'):
|
||||||
if cameraInitOutput.node.hasAttribute('input'):
|
if cameraInitOutput.node.hasAttribute('input'):
|
||||||
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
|
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
|
||||||
|
if cameraInitOutput and cameraInitOutput.node and cameraInitOutput.node.hasAttribute('viewpoints'):
|
||||||
viewpoints = cameraInitOutput.node.viewpoints.value
|
viewpoints = cameraInitOutput.node.viewpoints.value
|
||||||
|
else:
|
||||||
|
# No connected CameraInit
|
||||||
|
node.nbBrackets.value = 0
|
||||||
|
return
|
||||||
|
|
||||||
# logging.info("[LDRToHDR] Update start: nb viewpoints:" + str(len(viewpoints)))
|
# logging.info("[LDRToHDR] Update start: nb viewpoints:" + str(len(viewpoints)))
|
||||||
inputs = []
|
inputs = []
|
||||||
|
@ -210,7 +215,12 @@ class LdrToHdrSampling(desc.CommandLineNode):
|
||||||
exposures = None
|
exposures = None
|
||||||
bracketSizes = set()
|
bracketSizes = set()
|
||||||
if len(exposureGroups) == 1:
|
if len(exposureGroups) == 1:
|
||||||
|
if len(set(exposureGroups[0])) == 1:
|
||||||
|
# Single exposure and multiple views
|
||||||
node.nbBrackets.value = 1
|
node.nbBrackets.value = 1
|
||||||
|
else:
|
||||||
|
# Single view and multiple exposures
|
||||||
|
node.nbBrackets.value = len(exposureGroups[0])
|
||||||
else:
|
else:
|
||||||
for expGroup in exposureGroups:
|
for expGroup in exposureGroups:
|
||||||
bracketSizes.add(len(expGroup))
|
bracketSizes.add(len(expGroup))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
__version__ = "5.0"
|
__version__ = "6.0"
|
||||||
|
|
||||||
from meshroom.core import desc
|
from meshroom.core import desc
|
||||||
|
|
||||||
|
@ -35,6 +35,101 @@ A Graph Cut Max-Flow is applied to optimally cut the volume. This cut represents
|
||||||
value='',
|
value='',
|
||||||
uid=[0],
|
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(
|
desc.BoolParam(
|
||||||
name='estimateSpaceFromSfM',
|
name='estimateSpaceFromSfM',
|
||||||
label='Estimate Space From SfM',
|
label='Estimate Space From SfM',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
__version__ = "2.0"
|
__version__ = "3.0"
|
||||||
|
|
||||||
from meshroom.core import desc
|
from meshroom.core import desc
|
||||||
|
|
||||||
|
@ -34,12 +34,13 @@ The transformation can be based on:
|
||||||
label='Transformation Method',
|
label='Transformation Method',
|
||||||
description="Transformation method:\n"
|
description="Transformation method:\n"
|
||||||
" * transformation: Apply a given transformation\n"
|
" * transformation: Apply a given transformation\n"
|
||||||
|
" * manual: Apply the gizmo transformation (show the transformed input)\n"
|
||||||
" * auto_from_cameras: Use cameras\n"
|
" * auto_from_cameras: Use cameras\n"
|
||||||
" * auto_from_landmarks: Use landmarks\n"
|
" * auto_from_landmarks: Use landmarks\n"
|
||||||
" * from_single_camera: Use a specific camera as the origin of the coordinate system\n"
|
" * from_single_camera: Use a specific camera as the origin of the coordinate system\n"
|
||||||
" * from_markers: Align specific markers to custom coordinates",
|
" * from_markers: Align specific markers to custom coordinates",
|
||||||
value='auto_from_landmarks',
|
value='auto_from_landmarks',
|
||||||
values=['transformation', 'auto_from_cameras', 'auto_from_landmarks', 'from_single_camera', 'from_markers'],
|
values=['transformation', 'manual', 'auto_from_cameras', 'auto_from_landmarks', 'from_single_camera', 'from_markers'],
|
||||||
exclusive=True,
|
exclusive=True,
|
||||||
uid=[0],
|
uid=[0],
|
||||||
),
|
),
|
||||||
|
@ -51,6 +52,76 @@ The transformation can be based on:
|
||||||
" * from_single_camera: Camera UID or image filename",
|
" * from_single_camera: Camera UID or image filename",
|
||||||
value='',
|
value='',
|
||||||
uid=[0],
|
uid=[0],
|
||||||
|
enabled=lambda node: node.method.value == "transformation" or node.method.value == "from_single_camera",
|
||||||
|
),
|
||||||
|
desc.GroupAttribute(
|
||||||
|
name="manualTransform",
|
||||||
|
label="Manual Transform (Gizmo)",
|
||||||
|
description="Translation, rotation (Euler ZXY) and uniform scale.",
|
||||||
|
groupDesc=[
|
||||||
|
desc.GroupAttribute(
|
||||||
|
name="manualTranslation",
|
||||||
|
label="Translation",
|
||||||
|
description="Translation 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="manualRotation",
|
||||||
|
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.FloatParam(
|
||||||
|
name="manualScale",
|
||||||
|
label="Scale",
|
||||||
|
description="Uniform Scale.",
|
||||||
|
value=1.0,
|
||||||
|
uid=[0],
|
||||||
|
range=(0.0, 20.0, 0.01)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
joinChar=",",
|
||||||
|
enabled=lambda node: node.method.value == "manual",
|
||||||
),
|
),
|
||||||
desc.ChoiceParam(
|
desc.ChoiceParam(
|
||||||
name='landmarksDescriberTypes',
|
name='landmarksDescriberTypes',
|
||||||
|
@ -88,21 +159,24 @@ The transformation can be based on:
|
||||||
label='Scale',
|
label='Scale',
|
||||||
description='Apply scale transformation.',
|
description='Apply scale transformation.',
|
||||||
value=True,
|
value=True,
|
||||||
uid=[0]
|
uid=[0],
|
||||||
|
enabled=lambda node: node.method.value != "manual",
|
||||||
),
|
),
|
||||||
desc.BoolParam(
|
desc.BoolParam(
|
||||||
name='applyRotation',
|
name='applyRotation',
|
||||||
label='Rotation',
|
label='Rotation',
|
||||||
description='Apply rotation transformation.',
|
description='Apply rotation transformation.',
|
||||||
value=True,
|
value=True,
|
||||||
uid=[0]
|
uid=[0],
|
||||||
|
enabled=lambda node: node.method.value != "manual",
|
||||||
),
|
),
|
||||||
desc.BoolParam(
|
desc.BoolParam(
|
||||||
name='applyTranslation',
|
name='applyTranslation',
|
||||||
label='Translation',
|
label='Translation',
|
||||||
description='Apply translation transformation.',
|
description='Apply translation transformation.',
|
||||||
value=True,
|
value=True,
|
||||||
uid=[0]
|
uid=[0],
|
||||||
|
enabled=lambda node: node.method.value != "manual",
|
||||||
),
|
),
|
||||||
desc.ChoiceParam(
|
desc.ChoiceParam(
|
||||||
name='verboseLevel',
|
name='verboseLevel',
|
||||||
|
|
|
@ -283,7 +283,7 @@ It iterates like that, adding cameras and triangulating new 2D features into 3D
|
||||||
label='Filter Track Forks',
|
label='Filter Track Forks',
|
||||||
description='Enable/Disable the track forks removal. A track contains a fork when incoherent matches \n'
|
description='Enable/Disable the track forks removal. A track contains a fork when incoherent matches \n'
|
||||||
'lead to multiple features in the same image for a single track. \n',
|
'lead to multiple features in the same image for a single track. \n',
|
||||||
value=True,
|
value=False,
|
||||||
uid=[0],
|
uid=[0],
|
||||||
),
|
),
|
||||||
desc.File(
|
desc.File(
|
||||||
|
|
|
@ -13,7 +13,7 @@ from meshroom.core import pyCompatibility
|
||||||
from meshroom.ui import components
|
from meshroom.ui import components
|
||||||
from meshroom.ui.components.clipboard import ClipboardHelper
|
from meshroom.ui.components.clipboard import ClipboardHelper
|
||||||
from meshroom.ui.components.filepath import FilepathHelper
|
from meshroom.ui.components.filepath import FilepathHelper
|
||||||
from meshroom.ui.components.scene3D import Scene3DHelper
|
from meshroom.ui.components.scene3D import Scene3DHelper, Transformations3DHelper
|
||||||
from meshroom.ui.palette import PaletteManager
|
from meshroom.ui.palette import PaletteManager
|
||||||
from meshroom.ui.reconstruction import Reconstruction
|
from meshroom.ui.reconstruction import Reconstruction
|
||||||
from meshroom.ui.utils import QmlInstantEngine
|
from meshroom.ui.utils import QmlInstantEngine
|
||||||
|
@ -127,6 +127,7 @@ class MeshroomApp(QApplication):
|
||||||
# => expose them as context properties instead
|
# => expose them as context properties instead
|
||||||
self.engine.rootContext().setContextProperty("Filepath", FilepathHelper(parent=self))
|
self.engine.rootContext().setContextProperty("Filepath", FilepathHelper(parent=self))
|
||||||
self.engine.rootContext().setContextProperty("Scene3DHelper", Scene3DHelper(parent=self))
|
self.engine.rootContext().setContextProperty("Scene3DHelper", Scene3DHelper(parent=self))
|
||||||
|
self.engine.rootContext().setContextProperty("Transformations3DHelper", Transformations3DHelper(parent=self))
|
||||||
self.engine.rootContext().setContextProperty("Clipboard", ClipboardHelper(parent=self))
|
self.engine.rootContext().setContextProperty("Clipboard", ClipboardHelper(parent=self))
|
||||||
|
|
||||||
# additional context properties
|
# additional context properties
|
||||||
|
|
|
@ -4,10 +4,13 @@ def registerTypes():
|
||||||
from meshroom.ui.components.clipboard import ClipboardHelper
|
from meshroom.ui.components.clipboard import ClipboardHelper
|
||||||
from meshroom.ui.components.edge import EdgeMouseArea
|
from meshroom.ui.components.edge import EdgeMouseArea
|
||||||
from meshroom.ui.components.filepath import FilepathHelper
|
from meshroom.ui.components.filepath import FilepathHelper
|
||||||
from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController
|
from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController, Transformations3DHelper
|
||||||
|
from meshroom.ui.components.csvData import CsvData
|
||||||
|
|
||||||
qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea")
|
qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea")
|
||||||
qmlRegisterType(ClipboardHelper, "Meshroom.Helpers", 1, 0, "ClipboardHelper") # TODO: uncreatable
|
qmlRegisterType(ClipboardHelper, "Meshroom.Helpers", 1, 0, "ClipboardHelper") # TODO: uncreatable
|
||||||
qmlRegisterType(FilepathHelper, "Meshroom.Helpers", 1, 0, "FilepathHelper") # TODO: uncreatable
|
qmlRegisterType(FilepathHelper, "Meshroom.Helpers", 1, 0, "FilepathHelper") # TODO: uncreatable
|
||||||
qmlRegisterType(Scene3DHelper, "Meshroom.Helpers", 1, 0, "Scene3DHelper") # TODO: uncreatable
|
qmlRegisterType(Scene3DHelper, "Meshroom.Helpers", 1, 0, "Scene3DHelper") # TODO: uncreatable
|
||||||
|
qmlRegisterType(Transformations3DHelper, "Meshroom.Helpers", 1, 0, "Transformations3DHelper") # TODO: uncreatable
|
||||||
qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController")
|
qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController")
|
||||||
|
qmlRegisterType(CsvData, "DataObjects", 1, 0, "CsvData")
|
||||||
|
|
117
meshroom/ui/components/csvData.py
Normal file
117
meshroom/ui/components/csvData.py
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
from meshroom.common.qt import QObjectListModel
|
||||||
|
|
||||||
|
from PySide2.QtCore import QObject, Slot, Signal, Property
|
||||||
|
from PySide2.QtCharts import QtCharts
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
class CsvData(QObject):
|
||||||
|
"""Store data from a CSV file."""
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
"""Initialize the object without any parameter."""
|
||||||
|
super(CsvData, self).__init__(parent=parent)
|
||||||
|
self._filepath = ""
|
||||||
|
self._data = QObjectListModel(parent=self) # List of CsvColumn
|
||||||
|
self._ready = False
|
||||||
|
self.filepathChanged.connect(self.updateData)
|
||||||
|
|
||||||
|
@Slot(int, result=QObject)
|
||||||
|
def getColumn(self, index):
|
||||||
|
return self._data.at(index)
|
||||||
|
|
||||||
|
def getFilepath(self):
|
||||||
|
return self._filepath
|
||||||
|
|
||||||
|
@Slot(result=int)
|
||||||
|
def getNbColumns(self):
|
||||||
|
return len(self._data) if self._ready else 0
|
||||||
|
|
||||||
|
def setFilepath(self, filepath):
|
||||||
|
if self._filepath == filepath:
|
||||||
|
return
|
||||||
|
self.setReady(False)
|
||||||
|
self._filepath = filepath
|
||||||
|
self.filepathChanged.emit()
|
||||||
|
|
||||||
|
def setReady(self, ready):
|
||||||
|
if self._ready == ready:
|
||||||
|
return
|
||||||
|
self._ready = ready
|
||||||
|
self.readyChanged.emit()
|
||||||
|
|
||||||
|
def updateData(self):
|
||||||
|
self.setReady(False)
|
||||||
|
self._data.clear()
|
||||||
|
newColumns = self.read()
|
||||||
|
if newColumns:
|
||||||
|
self._data.setObjectList(newColumns)
|
||||||
|
self.setReady(True)
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
"""Read the CSV file and return a list containing CsvColumn objects."""
|
||||||
|
if not self._filepath or not self._filepath.lower().endswith(".csv") or not os.path.isfile(self._filepath):
|
||||||
|
return []
|
||||||
|
|
||||||
|
csvRows = []
|
||||||
|
with open(self._filepath, "r") as fp:
|
||||||
|
reader = csv.reader(fp)
|
||||||
|
for row in reader:
|
||||||
|
csvRows.append(row)
|
||||||
|
|
||||||
|
dataList = []
|
||||||
|
|
||||||
|
# Create the objects in dataList
|
||||||
|
# with the first line elements as objects' title
|
||||||
|
for elt in csvRows[0]:
|
||||||
|
dataList.append(CsvColumn(elt, parent=self._data))
|
||||||
|
|
||||||
|
# Populate the content attribute
|
||||||
|
for elt in csvRows[1:]:
|
||||||
|
for idx, value in enumerate(elt):
|
||||||
|
dataList[idx].appendValue(value)
|
||||||
|
|
||||||
|
return dataList
|
||||||
|
|
||||||
|
filepathChanged = Signal()
|
||||||
|
filepath = Property(str, getFilepath, setFilepath, notify=filepathChanged)
|
||||||
|
readyChanged = Signal()
|
||||||
|
ready = Property(bool, lambda self: self._ready, notify=readyChanged)
|
||||||
|
data = Property(QObject, lambda self: self._data, notify=readyChanged)
|
||||||
|
nbColumns = Property(int, getNbColumns, notify=readyChanged)
|
||||||
|
|
||||||
|
|
||||||
|
class CsvColumn(QObject):
|
||||||
|
"""Store content of a CSV column."""
|
||||||
|
def __init__(self, title="", parent=None):
|
||||||
|
"""Initialize the object with optional column title parameter."""
|
||||||
|
super(CsvColumn, self).__init__(parent=parent)
|
||||||
|
self._title = title
|
||||||
|
self._content = []
|
||||||
|
|
||||||
|
def appendValue(self, value):
|
||||||
|
self._content.append(value)
|
||||||
|
|
||||||
|
@Slot(result=str)
|
||||||
|
def getFirst(self):
|
||||||
|
if not self._content:
|
||||||
|
return ""
|
||||||
|
return self._content[0]
|
||||||
|
|
||||||
|
@Slot(result=str)
|
||||||
|
def getLast(self):
|
||||||
|
if not self._content:
|
||||||
|
return ""
|
||||||
|
return self._content[-1]
|
||||||
|
|
||||||
|
@Slot(QtCharts.QXYSeries)
|
||||||
|
def fillChartSerie(self, serie):
|
||||||
|
"""Fill XYSerie used for displaying QML Chart."""
|
||||||
|
if not serie:
|
||||||
|
return
|
||||||
|
serie.clear()
|
||||||
|
for index, value in enumerate(self._content):
|
||||||
|
serie.append(float(index), float(value))
|
||||||
|
|
||||||
|
title = Property(str, lambda self: self._title, constant=True)
|
||||||
|
content = Property("QStringList", lambda self: self._content, constant=True)
|
|
@ -3,7 +3,7 @@ from math import acos, pi, sqrt
|
||||||
from PySide2.QtCore import QObject, Slot, QSize, Signal, QPointF
|
from PySide2.QtCore import QObject, Slot, QSize, Signal, QPointF
|
||||||
from PySide2.Qt3DCore import Qt3DCore
|
from PySide2.Qt3DCore import Qt3DCore
|
||||||
from PySide2.Qt3DRender import Qt3DRender
|
from PySide2.Qt3DRender import Qt3DRender
|
||||||
from PySide2.QtGui import QVector3D, QQuaternion, QVector2D
|
from PySide2.QtGui import QVector3D, QQuaternion, QVector2D, QVector4D, QMatrix4x4
|
||||||
|
|
||||||
from meshroom.ui.utils import makeProperty
|
from meshroom.ui.utils import makeProperty
|
||||||
|
|
||||||
|
@ -103,3 +103,206 @@ class TrackballController(QObject):
|
||||||
trackballSize = makeProperty(float, '_trackballSize', trackballSizeChanged)
|
trackballSize = makeProperty(float, '_trackballSize', trackballSizeChanged)
|
||||||
rotationSpeedChanged = Signal()
|
rotationSpeedChanged = Signal()
|
||||||
rotationSpeed = makeProperty(float, '_rotationSpeed', rotationSpeedChanged)
|
rotationSpeed = makeProperty(float, '_rotationSpeed', rotationSpeedChanged)
|
||||||
|
|
||||||
|
|
||||||
|
class Transformations3DHelper(QObject):
|
||||||
|
|
||||||
|
# ---------- Exposed to QML ---------- #
|
||||||
|
|
||||||
|
@Slot(QVector4D, Qt3DRender.QCamera, QSize, result=QVector2D)
|
||||||
|
def pointFromWorldToScreen(self, point, camera, windowSize):
|
||||||
|
""" Compute the Screen point corresponding to a World Point.
|
||||||
|
Args:
|
||||||
|
point (QVector4D): point in world coordinates
|
||||||
|
camera (QCamera): camera viewing the scene
|
||||||
|
windowSize (QSize): size of the Scene3D window
|
||||||
|
Returns:
|
||||||
|
QVector2D: point in screen coordinates
|
||||||
|
"""
|
||||||
|
# Transform the point from World Coord to Normalized Device Coord
|
||||||
|
viewMatrix = camera.transform().matrix().inverted()
|
||||||
|
projectedPoint = (camera.projectionMatrix() * viewMatrix[0]).map(point)
|
||||||
|
projectedPoint2D = QVector2D(
|
||||||
|
projectedPoint.x()/projectedPoint.w(),
|
||||||
|
projectedPoint.y()/projectedPoint.w()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Transform the point from Normalized Device Coord to Screen Coord
|
||||||
|
screenPoint2D = QVector2D(
|
||||||
|
int((projectedPoint2D.x() + 1) * windowSize.width() / 2),
|
||||||
|
int((projectedPoint2D.y() - 1) * windowSize.height() / -2)
|
||||||
|
)
|
||||||
|
|
||||||
|
return screenPoint2D
|
||||||
|
|
||||||
|
@Slot(Qt3DCore.QTransform, QMatrix4x4, QMatrix4x4, QMatrix4x4, QVector3D)
|
||||||
|
def relativeLocalTranslate(self, transformQtInstance, initialPosMat, initialRotMat, initialScaleMat, translateVec):
|
||||||
|
""" Translate the QTransform in its local space relatively to an initial state.
|
||||||
|
Args:
|
||||||
|
transformQtInstance (QTransform): reference to the Transform to modify
|
||||||
|
initialPosMat (QMatrix4x4): initial position matrix
|
||||||
|
initialRotMat (QMatrix4x4): initial rotation matrix
|
||||||
|
initialScaleMat (QMatrix4x4): initial scale matrix
|
||||||
|
translateVec (QVector3D): vector used for the local translation
|
||||||
|
"""
|
||||||
|
# Compute the translation transformation matrix
|
||||||
|
translationMat = QMatrix4x4()
|
||||||
|
translationMat.translate(translateVec)
|
||||||
|
|
||||||
|
# Compute the new model matrix (POSITION * ROTATION * TRANSLATE * SCALE) and set it to the Transform
|
||||||
|
mat = initialPosMat * initialRotMat * translationMat * initialScaleMat
|
||||||
|
transformQtInstance.setMatrix(mat)
|
||||||
|
|
||||||
|
@Slot(Qt3DCore.QTransform, QMatrix4x4, QQuaternion, QMatrix4x4, QVector3D, int)
|
||||||
|
def relativeLocalRotate(self, transformQtInstance, initialPosMat, initialRotQuat, initialScaleMat, axis, degree):
|
||||||
|
""" Rotate the QTransform in its local space relatively to an initial state.
|
||||||
|
Args:
|
||||||
|
transformQtInstance (QTransform): reference to the Transform to modify
|
||||||
|
initialPosMat (QMatrix4x4): initial position matrix
|
||||||
|
initialRotQuat (QQuaternion): initial rotation quaternion
|
||||||
|
initialScaleMat (QMatrix4x4): initial scale matrix
|
||||||
|
axis (QVector3D): axis to rotate around
|
||||||
|
degree (int): angle of rotation in degree
|
||||||
|
"""
|
||||||
|
# Compute the transformation quaternion from axis and angle in degrees
|
||||||
|
transformQuat = QQuaternion.fromAxisAndAngle(axis, degree)
|
||||||
|
|
||||||
|
# Compute the new rotation quaternion and then calculate the matrix
|
||||||
|
newRotQuat = initialRotQuat * transformQuat # Order is important
|
||||||
|
newRotationMat = self.quaternionToRotationMatrix(newRotQuat)
|
||||||
|
|
||||||
|
# Compute the new model matrix (POSITION * NEW_COMPUTED_ROTATION * SCALE) and set it to the Transform
|
||||||
|
mat = initialPosMat * newRotationMat * initialScaleMat
|
||||||
|
transformQtInstance.setMatrix(mat)
|
||||||
|
|
||||||
|
@Slot(Qt3DCore.QTransform, QMatrix4x4, QMatrix4x4, QMatrix4x4, QVector3D)
|
||||||
|
def relativeLocalScale(self, transformQtInstance, initialPosMat, initialRotMat, initialScaleMat, scaleVec):
|
||||||
|
""" Scale the QTransform in its local space relatively to an initial state.
|
||||||
|
Args:
|
||||||
|
transformQtInstance (QTransform): reference to the Transform to modify
|
||||||
|
initialPosMat (QMatrix4x4): initial position matrix
|
||||||
|
initialRotMat (QMatrix4x4): initial rotation matrix
|
||||||
|
initialScaleMat (QMatrix4x4): initial scale matrix
|
||||||
|
scaleVec (QVector3D): vector used for the relative scale
|
||||||
|
"""
|
||||||
|
# Make a copy of the scale matrix (otherwise, it is a reference and it does not work as expected)
|
||||||
|
scaleMat = self.copyMatrix4x4(initialScaleMat)
|
||||||
|
|
||||||
|
# Update the scale matrix copy (X then Y then Z) with the scaleVec values
|
||||||
|
scaleVecTuple = scaleVec.toTuple()
|
||||||
|
for i in range(3):
|
||||||
|
currentRow = list(scaleMat.row(i).toTuple()) # QVector3D does not implement [] operator or easy way to access value by index so this little hack is required
|
||||||
|
value = currentRow[i] + scaleVecTuple[i]
|
||||||
|
value = value if value >= 0 else -value # Make sure to have only positive scale (because negative scale can make issues with matrix decomposition)
|
||||||
|
currentRow[i] = value
|
||||||
|
|
||||||
|
scaleMat.setRow(i, QVector3D(currentRow[0], currentRow[1], currentRow[2])) # Apply the new row to the scale matrix
|
||||||
|
|
||||||
|
# Compute the new model matrix (POSITION * ROTATION * SCALE) and set it to the Transform
|
||||||
|
mat = initialPosMat * initialRotMat * scaleMat
|
||||||
|
transformQtInstance.setMatrix(mat)
|
||||||
|
|
||||||
|
@Slot(QMatrix4x4, result="QVariant")
|
||||||
|
def modelMatrixToMatrices(self, modelMat):
|
||||||
|
""" Decompose a model matrix into individual matrices.
|
||||||
|
Args:
|
||||||
|
modelMat (QMatrix4x4): model matrix to decompose
|
||||||
|
Returns:
|
||||||
|
QVariant: object containing position, rotation and scale matrices + rotation quaternion
|
||||||
|
"""
|
||||||
|
decomposition = self.decomposeModelMatrix(modelMat)
|
||||||
|
|
||||||
|
posMat = QMatrix4x4()
|
||||||
|
posMat.translate(decomposition.get("translation"))
|
||||||
|
|
||||||
|
rotMat = self.quaternionToRotationMatrix(decomposition.get("quaternion"))
|
||||||
|
|
||||||
|
scaleMat = QMatrix4x4()
|
||||||
|
scaleMat.scale(decomposition.get("scale"))
|
||||||
|
|
||||||
|
return {"position": posMat, "rotation": rotMat, "scale": scaleMat, "quaternion": decomposition.get("quaternion")}
|
||||||
|
|
||||||
|
@Slot(QVector3D, QVector3D, QVector3D, result=QMatrix4x4)
|
||||||
|
def computeModelMatrixWithEuler(self, translation, rotation, scale):
|
||||||
|
""" Compute a model matrix from three Vector3D.
|
||||||
|
Args:
|
||||||
|
translation (QVector3D): position in space (x, y, z)
|
||||||
|
rotation (QVector3D): Euler angles in degrees (x, y, z)
|
||||||
|
scale (QVector3D): scale of the object (x, y, z)
|
||||||
|
Returns:
|
||||||
|
QMatrix4x4: corresponding model matrix
|
||||||
|
"""
|
||||||
|
posMat = QMatrix4x4()
|
||||||
|
posMat.translate(translation)
|
||||||
|
|
||||||
|
quaternion = QQuaternion.fromEulerAngles(rotation)
|
||||||
|
rotMat = self.quaternionToRotationMatrix(quaternion)
|
||||||
|
|
||||||
|
scaleMat = QMatrix4x4()
|
||||||
|
scaleMat.scale(scale)
|
||||||
|
|
||||||
|
modelMat = posMat * rotMat * scaleMat
|
||||||
|
|
||||||
|
return modelMat
|
||||||
|
|
||||||
|
@Slot(QVector3D, QMatrix4x4, Qt3DRender.QCamera, QSize, result=float)
|
||||||
|
def computeScaleUnitFromModelMatrix(self, axis, modelMat, camera, windowSize):
|
||||||
|
""" Compute the length of the screen projected vector axis unit transformed by the model matrix.
|
||||||
|
Args:
|
||||||
|
axis (QVector3D): chosen axis ((1,0,0) or (0,1,0) or (0,0,1))
|
||||||
|
modelMat (QMatrix4x4): model matrix used for the transformation
|
||||||
|
camera (QCamera): camera viewing the scene
|
||||||
|
windowSize (QSize): size of the window in pixels
|
||||||
|
Returns:
|
||||||
|
float: length (in pixels)
|
||||||
|
"""
|
||||||
|
decomposition = self.decomposeModelMatrix(modelMat)
|
||||||
|
|
||||||
|
posMat = QMatrix4x4()
|
||||||
|
posMat.translate(decomposition.get("translation"))
|
||||||
|
|
||||||
|
rotMat = self.quaternionToRotationMatrix(decomposition.get("quaternion"))
|
||||||
|
|
||||||
|
unitScaleModelMat = posMat * rotMat * QMatrix4x4()
|
||||||
|
|
||||||
|
worldCenterPoint = unitScaleModelMat.map(QVector4D(0,0,0,1))
|
||||||
|
worldAxisUnitPoint = unitScaleModelMat.map(QVector4D(axis.x(),axis.y(),axis.z(),1))
|
||||||
|
screenCenter2D = self.pointFromWorldToScreen(worldCenterPoint, camera, windowSize)
|
||||||
|
screenAxisUnitPoint2D = self.pointFromWorldToScreen(worldAxisUnitPoint, camera, windowSize)
|
||||||
|
|
||||||
|
screenVector = QVector2D(screenAxisUnitPoint2D.x() - screenCenter2D.x(), -(screenAxisUnitPoint2D.y() - screenCenter2D.y()))
|
||||||
|
|
||||||
|
value = screenVector.length()
|
||||||
|
return value if (value and value > 10) else 10 # Threshold to avoid problems in extreme case
|
||||||
|
|
||||||
|
# ---------- "Private" Methods ---------- #
|
||||||
|
|
||||||
|
def copyMatrix4x4(self, mat):
|
||||||
|
""" Make a deep copy of a QMatrix4x4. """
|
||||||
|
newMat = QMatrix4x4()
|
||||||
|
for i in range(4):
|
||||||
|
newMat.setRow(i, mat.row(i))
|
||||||
|
return newMat
|
||||||
|
|
||||||
|
def decomposeModelMatrix(self, modelMat):
|
||||||
|
""" Decompose a model matrix into individual component.
|
||||||
|
Args:
|
||||||
|
modelMat (QMatrix4x4): model matrix to decompose
|
||||||
|
Returns:
|
||||||
|
QVariant: object containing translation and scale vectors + rotation quaternion
|
||||||
|
"""
|
||||||
|
translation = modelMat.column(3).toVector3D()
|
||||||
|
quaternion = QQuaternion.fromDirection(modelMat.column(2).toVector3D(), modelMat.column(1).toVector3D())
|
||||||
|
scale = QVector3D(modelMat.column(0).length(), modelMat.column(1).length(), modelMat.column(2).length())
|
||||||
|
|
||||||
|
return {"translation": translation, "quaternion": quaternion, "scale": scale}
|
||||||
|
|
||||||
|
def quaternionToRotationMatrix(self, q):
|
||||||
|
""" Return a rotation matrix from a quaternion. """
|
||||||
|
rotMat3x3 = q.toRotationMatrix()
|
||||||
|
return QMatrix4x4(
|
||||||
|
rotMat3x3(0, 0), rotMat3x3(0, 1), rotMat3x3(0, 2), 0,
|
||||||
|
rotMat3x3(1, 0), rotMat3x3(1, 1), rotMat3x3(1, 2), 0,
|
||||||
|
rotMat3x3(2, 0), rotMat3x3(2, 1), rotMat3x3(2, 2), 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
)
|
||||||
|
|
|
@ -281,7 +281,11 @@ class UIGraph(QObject):
|
||||||
if self._graph:
|
if self._graph:
|
||||||
self.stopExecution()
|
self.stopExecution()
|
||||||
self.clear()
|
self.clear()
|
||||||
|
oldGraph = self._graph
|
||||||
self._graph = g
|
self._graph = g
|
||||||
|
if oldGraph:
|
||||||
|
oldGraph.deleteLater()
|
||||||
|
|
||||||
self._graph.updated.connect(self.onGraphUpdated)
|
self._graph.updated.connect(self.onGraphUpdated)
|
||||||
self._graph.update()
|
self._graph.update()
|
||||||
self._taskManager.update(self._graph)
|
self._taskManager.update(self._graph)
|
||||||
|
@ -328,8 +332,7 @@ class UIGraph(QObject):
|
||||||
self.clearNodeHover()
|
self.clearNodeHover()
|
||||||
self.clearNodeSelection()
|
self.clearNodeSelection()
|
||||||
self._taskManager.clear()
|
self._taskManager.clear()
|
||||||
self._graph.deleteLater()
|
self._graph.clear()
|
||||||
self._graph = None
|
|
||||||
self._sortedDFSChunks.clear()
|
self._sortedDFSChunks.clear()
|
||||||
self._undoStack.clear()
|
self._undoStack.clear()
|
||||||
|
|
||||||
|
|
|
@ -23,15 +23,14 @@ RowLayout {
|
||||||
readonly property point outputAnchorPos: Qt.point(outputAnchor.x + outputAnchor.width/2,
|
readonly property point outputAnchorPos: Qt.point(outputAnchor.x + outputAnchor.width/2,
|
||||||
outputAnchor.y + outputAnchor.height/2)
|
outputAnchor.y + outputAnchor.height/2)
|
||||||
|
|
||||||
readonly property bool isList: attribute.type == "ListAttribute"
|
readonly property bool isList: attribute && attribute.type === "ListAttribute"
|
||||||
|
|
||||||
signal childPinCreated(var childAttribute, var pin)
|
signal childPinCreated(var childAttribute, var pin)
|
||||||
signal childPinDeleted(var childAttribute, var pin)
|
signal childPinDeleted(var childAttribute, var pin)
|
||||||
|
|
||||||
signal pressed(var mouse)
|
signal pressed(var mouse)
|
||||||
|
|
||||||
|
objectName: attribute ? attribute.name + "." : ""
|
||||||
objectName: attribute.name + "."
|
|
||||||
layoutDirection: Qt.LeftToRight
|
layoutDirection: Qt.LeftToRight
|
||||||
spacing: 3
|
spacing: 3
|
||||||
|
|
||||||
|
@ -58,7 +57,6 @@ RowLayout {
|
||||||
border.color: Colors.sysPalette.mid
|
border.color: Colors.sysPalette.mid
|
||||||
color: Colors.sysPalette.base
|
color: Colors.sysPalette.base
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: inputConnectMA.containsMouse || childrenRepeater.count > 0 || attribute.isLink
|
visible: inputConnectMA.containsMouse || childrenRepeater.count > 0 || attribute.isLink
|
||||||
radius: isList ? 0 : 2
|
radius: isList ? 0 : 2
|
||||||
|
@ -114,7 +112,7 @@ RowLayout {
|
||||||
readonly property string connectorType: "input"
|
readonly property string connectorType: "input"
|
||||||
readonly property alias attribute: root.attribute
|
readonly property alias attribute: root.attribute
|
||||||
readonly property alias nodeItem: root.nodeItem
|
readonly property alias nodeItem: root.nodeItem
|
||||||
readonly property bool isOutput: attribute.isOutput
|
readonly property bool isOutput: attribute && attribute.isOutput
|
||||||
readonly property alias isList: root.isList
|
readonly property alias isList: root.isList
|
||||||
property bool dragAccepted: false
|
property bool dragAccepted: false
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
@ -175,8 +173,8 @@ RowLayout {
|
||||||
elide: hovered ? Text.ElideNone : Text.ElideMiddle
|
elide: hovered ? Text.ElideNone : Text.ElideMiddle
|
||||||
width: hovered ? contentWidth : parent.width
|
width: hovered ? contentWidth : parent.width
|
||||||
font.pointSize: 7
|
font.pointSize: 7
|
||||||
horizontalAlignment: attribute.isOutput ? Text.AlignRight : Text.AlignLeft
|
horizontalAlignment: attribute && attribute.isOutput ? Text.AlignRight : Text.AlignLeft
|
||||||
anchors.right: attribute.isOutput ? parent.right : undefined
|
anchors.right: attribute && attribute.isOutput ? parent.right : undefined
|
||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
color: hovered ? palette.highlight : palette.text
|
color: hovered ? palette.highlight : palette.text
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,15 +91,15 @@ MessageDialog {
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.preferredWidth: 130
|
Layout.preferredWidth: 130
|
||||||
text: compatibilityNodeDelegate.node.nodeType
|
text: compatibilityNodeDelegate.node ? compatibilityNodeDelegate.node.nodeType : ""
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: compatibilityNodeDelegate.node.issueDetails
|
text: compatibilityNodeDelegate.node ? compatibilityNodeDelegate.node.issueDetails : ""
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: compatibilityNodeDelegate.node.canUpgrade ? MaterialIcons.check : MaterialIcons.clear
|
text: compatibilityNodeDelegate.node && compatibilityNodeDelegate.node.canUpgrade ? MaterialIcons.check : MaterialIcons.clear
|
||||||
color: compatibilityNodeDelegate.node.canUpgrade ? "#4CAF50" : "#F44336"
|
color: compatibilityNodeDelegate.node && compatibilityNodeDelegate.node.canUpgrade ? "#4CAF50" : "#F44336"
|
||||||
font.family: MaterialIcons.fontFamily
|
font.family: MaterialIcons.fontFamily
|
||||||
font.pointSize: 14
|
font.pointSize: 14
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
|
@ -232,11 +232,11 @@ Item {
|
||||||
id: edgesRepeater
|
id: edgesRepeater
|
||||||
|
|
||||||
// delay edges loading after nodes (edges needs attribute pins to be created)
|
// delay edges loading after nodes (edges needs attribute pins to be created)
|
||||||
model: nodeRepeater.loaded ? root.graph.edges : undefined
|
model: nodeRepeater.loaded && root.graph ? root.graph.edges : undefined
|
||||||
|
|
||||||
delegate: Edge {
|
delegate: Edge {
|
||||||
property var src: root._attributeToDelegate[edge.src]
|
property var src: edge ? root._attributeToDelegate[edge.src] : undefined
|
||||||
property var dst: root._attributeToDelegate[edge.dst]
|
property var dst: edge ? root._attributeToDelegate[edge.dst] : undefined
|
||||||
property var srcAnchor: src.nodeItem.mapFromItem(src, src.outputAnchorPos.x, src.outputAnchorPos.y)
|
property var srcAnchor: src.nodeItem.mapFromItem(src, src.outputAnchorPos.x, src.outputAnchorPos.y)
|
||||||
property var dstAnchor: dst.nodeItem.mapFromItem(dst, dst.inputAnchorPos.x, dst.inputAnchorPos.y)
|
property var dstAnchor: dst.nodeItem.mapFromItem(dst, dst.inputAnchorPos.x, dst.inputAnchorPos.y)
|
||||||
|
|
||||||
|
@ -406,8 +406,8 @@ Item {
|
||||||
Repeater {
|
Repeater {
|
||||||
id: nodeRepeater
|
id: nodeRepeater
|
||||||
|
|
||||||
model: root.graph.nodes
|
model: root.graph ? root.graph.nodes : undefined
|
||||||
property bool loaded: count === model.count
|
property bool loaded: model ? count === model.count : false
|
||||||
|
|
||||||
delegate: Node {
|
delegate: Node {
|
||||||
id: nodeDelegate
|
id: nodeDelegate
|
||||||
|
|
|
@ -18,7 +18,7 @@ Item {
|
||||||
/// Whether the node can be modified
|
/// Whether the node can be modified
|
||||||
property bool readOnly: node.locked
|
property bool readOnly: node.locked
|
||||||
/// Whether the node is in compatibility mode
|
/// Whether the node is in compatibility mode
|
||||||
readonly property bool isCompatibilityNode: node.hasOwnProperty("compatibilityIssue")
|
readonly property bool isCompatibilityNode: node ? node.hasOwnProperty("compatibilityIssue") : false
|
||||||
/// Mouse related states
|
/// Mouse related states
|
||||||
property bool selected: false
|
property bool selected: false
|
||||||
property bool hovered: false
|
property bool hovered: false
|
||||||
|
@ -40,11 +40,11 @@ Item {
|
||||||
signal attributePinDeleted(var attribute, var pin)
|
signal attributePinDeleted(var attribute, var pin)
|
||||||
|
|
||||||
// use node name as object name to simplify debugging
|
// use node name as object name to simplify debugging
|
||||||
objectName: node.name
|
objectName: node ? node.name : ""
|
||||||
|
|
||||||
// initialize position with node coordinates
|
// initialize position with node coordinates
|
||||||
x: root.node.x
|
x: root.node ? root.node.x : undefined
|
||||||
y: root.node.y
|
y: root.node ? root.node.y : undefined
|
||||||
|
|
||||||
implicitHeight: childrenRect.height
|
implicitHeight: childrenRect.height
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ Panel {
|
||||||
|
|
||||||
SensorDBDialog {
|
SensorDBDialog {
|
||||||
id: sensorDBDialog
|
id: sensorDBDialog
|
||||||
sensorDatabase: Filepath.stringToUrl(cameraInit.attribute("sensorDatabase").value)
|
sensorDatabase: cameraInit ? Filepath.stringToUrl(cameraInit.attribute("sensorDatabase").value) : ""
|
||||||
readOnly: _reconstruction.computing
|
readOnly: _reconstruction.computing
|
||||||
onUpdateIntrinsicsRequest: _reconstruction.rebuildIntrinsics(cameraInit)
|
onUpdateIntrinsicsRequest: _reconstruction.rebuildIntrinsics(cameraInit)
|
||||||
}
|
}
|
||||||
|
|
6
meshroom/ui/qml/Utils/Transformations3DHelper.qml
Normal file
6
meshroom/ui/qml/Utils/Transformations3DHelper.qml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
pragma Singleton
|
||||||
|
import Meshroom.Helpers 1.0
|
||||||
|
|
||||||
|
Transformations3DHelper {
|
||||||
|
|
||||||
|
}
|
|
@ -8,3 +8,4 @@ Format 1.0 format.js
|
||||||
# singleton Clipboard 1.0 Clipboard.qml
|
# singleton Clipboard 1.0 Clipboard.qml
|
||||||
# singleton Filepath 1.0 Filepath.qml
|
# singleton Filepath 1.0 Filepath.qml
|
||||||
# singleton Scene3DHelper 1.0 Scene3DHelper.qml
|
# singleton Scene3DHelper 1.0 Scene3DHelper.qml
|
||||||
|
# singleton Transformations3DHelper 1.0 Transformations3DHelper.qml
|
||||||
|
|
139
meshroom/ui/qml/Viewer/CameraResponseGraph.qml
Normal file
139
meshroom/ui/qml/Viewer/CameraResponseGraph.qml
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
import MaterialIcons 2.2
|
||||||
|
import QtPositioning 5.8
|
||||||
|
import QtLocation 5.9
|
||||||
|
import QtCharts 2.13
|
||||||
|
import Charts 1.0
|
||||||
|
|
||||||
|
import Controls 1.0
|
||||||
|
import Utils 1.0
|
||||||
|
import DataObjects 1.0
|
||||||
|
|
||||||
|
FloatingPane {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var ldrHdrCalibrationNode: null
|
||||||
|
property color textColor: Colors.sysPalette.text
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
padding: 4
|
||||||
|
|
||||||
|
CsvData {
|
||||||
|
id: csvData
|
||||||
|
filepath: ldrHdrCalibrationNode ? ldrHdrCalibrationNode.attribute("response").value : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// To avoid interaction with components in background
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
onPressed: {}
|
||||||
|
onReleased: {}
|
||||||
|
onWheel: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool crfReady: csvData.ready && csvData.nbColumns >= 4
|
||||||
|
onCrfReadyChanged: {
|
||||||
|
if(crfReady)
|
||||||
|
{
|
||||||
|
redCurve.clear()
|
||||||
|
greenCurve.clear()
|
||||||
|
blueCurve.clear()
|
||||||
|
csvData.getColumn(1).fillChartSerie(redCurve)
|
||||||
|
csvData.getColumn(2).fillChartSerie(greenCurve)
|
||||||
|
csvData.getColumn(3).fillChartSerie(blueCurve)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
redCurve.clear()
|
||||||
|
greenCurve.clear()
|
||||||
|
blueCurve.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.horizontalCenterOffset: -responseChart.width/2
|
||||||
|
anchors.verticalCenterOffset: -responseChart.height/2
|
||||||
|
|
||||||
|
InteractiveChartView {
|
||||||
|
id: responseChart
|
||||||
|
width: root.width > 400 ? 400 : (root.width < 350 ? 350 : root.width)
|
||||||
|
height: width * 0.75
|
||||||
|
|
||||||
|
title: "Camera Response Function (CRF)"
|
||||||
|
legend.visible: false
|
||||||
|
antialiasing: true
|
||||||
|
|
||||||
|
ValueAxis {
|
||||||
|
id: valueAxisX
|
||||||
|
labelFormat: "%i"
|
||||||
|
titleText: "Camera Brightness"
|
||||||
|
min: crfReady ? csvData.getColumn(0).getFirst() : 0
|
||||||
|
max: crfReady ? csvData.getColumn(0).getLast() : 1
|
||||||
|
}
|
||||||
|
ValueAxis {
|
||||||
|
id: valueAxisY
|
||||||
|
titleText: "Normalized Radiance"
|
||||||
|
min: 0.0
|
||||||
|
max: 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot use a Repeater with these Components so we need to instantiate them one by one
|
||||||
|
// Red curve
|
||||||
|
LineSeries {
|
||||||
|
id: redCurve
|
||||||
|
axisX: valueAxisX
|
||||||
|
axisY: valueAxisY
|
||||||
|
name: crfReady ? csvData.getColumn(1).title : ""
|
||||||
|
color: name.toLowerCase()
|
||||||
|
}
|
||||||
|
// Green curve
|
||||||
|
LineSeries {
|
||||||
|
id: greenCurve
|
||||||
|
axisX: valueAxisX
|
||||||
|
axisY: valueAxisY
|
||||||
|
name: crfReady ? csvData.getColumn(2).title : ""
|
||||||
|
color: name.toLowerCase()
|
||||||
|
}
|
||||||
|
// Blue curve
|
||||||
|
LineSeries {
|
||||||
|
id: blueCurve
|
||||||
|
axisX: valueAxisX
|
||||||
|
axisY: valueAxisY
|
||||||
|
name: crfReady ? csvData.getColumn(3).title : ""
|
||||||
|
color: name.toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: btnContainer
|
||||||
|
|
||||||
|
anchors.bottom: responseChart.bottom
|
||||||
|
anchors.bottomMargin: 35
|
||||||
|
anchors.left: responseChart.left
|
||||||
|
anchors.leftMargin: responseChart.width * 0.15
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
ChartViewCheckBox {
|
||||||
|
text: "ALL"
|
||||||
|
color: textColor
|
||||||
|
checkState: legend.buttonGroup.checkState
|
||||||
|
onClicked: {
|
||||||
|
const _checked = checked
|
||||||
|
for(let i = 0; i < responseChart.count; ++i) {
|
||||||
|
responseChart.series(i).visible = _checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChartViewLegend {
|
||||||
|
id: legend
|
||||||
|
chartView: responseChart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -552,6 +552,18 @@ FocusScope {
|
||||||
featuresViewer: featuresViewerLoader.item
|
featuresViewer: featuresViewerLoader.item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: ldrHdrCalibrationGraph
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property var activeNode: _reconstruction.activeNodes.get('LdrToHdrCalibration').node
|
||||||
|
active: activeNode && activeNode.isComputed && displayLdrHdrCalibrationGraph.checked
|
||||||
|
|
||||||
|
sourceComponent: CameraResponseGraph {
|
||||||
|
ldrHdrCalibrationNode: activeNode
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
FloatingPane {
|
FloatingPane {
|
||||||
id: bottomToolbar
|
id: bottomToolbar
|
||||||
|
@ -628,6 +640,25 @@ FocusScope {
|
||||||
visible: activeNode
|
visible: activeNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MaterialToolButton {
|
||||||
|
id: displayLdrHdrCalibrationGraph
|
||||||
|
property var activeNode: _reconstruction.activeNodes.get("LdrToHdrCalibration").node
|
||||||
|
property bool isComputed: activeNode && activeNode.isComputed
|
||||||
|
ToolTip.text: "Display Camera Response Function: " + (activeNode ? activeNode.label : "No Node")
|
||||||
|
text: MaterialIcons.timeline
|
||||||
|
font.pointSize: 11
|
||||||
|
Layout.minimumWidth: 0
|
||||||
|
checkable: true
|
||||||
|
checked: false
|
||||||
|
enabled: activeNode && activeNode.isComputed
|
||||||
|
visible: activeNode
|
||||||
|
|
||||||
|
onIsComputedChanged: {
|
||||||
|
if(!isComputed)
|
||||||
|
checked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: resolutionLabel
|
id: resolutionLabel
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
92
meshroom/ui/qml/Viewer3D/BoundingBox.qml
Normal file
92
meshroom/ui/qml/Viewer3D/BoundingBox.qml
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import Qt3D.Core 2.0
|
||||||
|
import Qt3D.Render 2.9
|
||||||
|
import Qt3D.Input 2.0
|
||||||
|
import Qt3D.Extras 2.10
|
||||||
|
import QtQuick 2.9
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
id: root
|
||||||
|
property Transform transform: Transform {}
|
||||||
|
|
||||||
|
components: [transform]
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
components: [cube, greyMaterial]
|
||||||
|
|
||||||
|
CuboidMesh {
|
||||||
|
id: cube
|
||||||
|
property real edge : 1.995 // Almost 2: important to have all the cube's vertices with a unit of 1
|
||||||
|
xExtent: edge
|
||||||
|
yExtent: edge
|
||||||
|
zExtent: edge
|
||||||
|
}
|
||||||
|
PhongAlphaMaterial {
|
||||||
|
id: greyMaterial
|
||||||
|
property color base: "#fff"
|
||||||
|
ambient: base
|
||||||
|
alpha: 0.15
|
||||||
|
|
||||||
|
// Pretty convincing combination
|
||||||
|
blendFunctionArg: BlendEquation.Add
|
||||||
|
sourceRgbArg: BlendEquationArguments.SourceAlpha
|
||||||
|
sourceAlphaArg: BlendEquationArguments.OneMinusSourceAlpha
|
||||||
|
destinationRgbArg: BlendEquationArguments.DestinationColor
|
||||||
|
destinationAlphaArg: BlendEquationArguments.OneMinusSourceAlpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
components: [edges, orangeMaterial]
|
||||||
|
|
||||||
|
PhongMaterial {
|
||||||
|
id: orangeMaterial
|
||||||
|
property color base: "#f49b2b"
|
||||||
|
ambient: base
|
||||||
|
}
|
||||||
|
|
||||||
|
GeometryRenderer {
|
||||||
|
id: edges
|
||||||
|
primitiveType: GeometryRenderer.Lines
|
||||||
|
geometry: Geometry {
|
||||||
|
Attribute {
|
||||||
|
id: boundingBoxPosition
|
||||||
|
attributeType: Attribute.VertexAttribute
|
||||||
|
vertexBaseType: Attribute.Float
|
||||||
|
vertexSize: 3
|
||||||
|
count: 24
|
||||||
|
name: defaultPositionAttributeName
|
||||||
|
buffer: Buffer {
|
||||||
|
type: Buffer.VertexBuffer
|
||||||
|
data: new Float32Array([
|
||||||
|
1.0, 1.0, 1.0,
|
||||||
|
1.0, -1.0, 1.0,
|
||||||
|
1.0, 1.0, 1.0,
|
||||||
|
1.0, 1.0, -1.0,
|
||||||
|
1.0, 1.0, 1.0,
|
||||||
|
-1.0, 1.0, 1.0,
|
||||||
|
-1.0, -1.0, -1.0,
|
||||||
|
-1.0, 1.0, -1.0,
|
||||||
|
-1.0, -1.0, -1.0,
|
||||||
|
1.0, -1.0, -1.0,
|
||||||
|
-1.0, -1.0, -1.0,
|
||||||
|
-1.0, -1.0, 1.0,
|
||||||
|
1.0, -1.0, 1.0,
|
||||||
|
1.0, -1.0, -1.0,
|
||||||
|
1.0, 1.0, -1.0,
|
||||||
|
1.0, -1.0, -1.0,
|
||||||
|
-1.0, 1.0, 1.0,
|
||||||
|
-1.0, 1.0, -1.0,
|
||||||
|
1.0, -1.0, 1.0,
|
||||||
|
-1.0, -1.0, 1.0,
|
||||||
|
-1.0, 1.0, 1.0,
|
||||||
|
-1.0, -1.0, 1.0,
|
||||||
|
-1.0, 1.0, -1.0,
|
||||||
|
1.0, 1.0, -1.0
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boundingVolumePositionAttribute: boundingBoxPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ Entity {
|
||||||
property alias windowSize: trackball.windowSize
|
property alias windowSize: trackball.windowSize
|
||||||
property alias trackballSize: trackball.trackballSize
|
property alias trackballSize: trackball.trackballSize
|
||||||
|
|
||||||
|
property bool loseMouseFocus: false // Must be changed by other entities when they want to take mouse focus
|
||||||
|
|
||||||
readonly property alias pressed: mouseHandler._pressed
|
readonly property alias pressed: mouseHandler._pressed
|
||||||
signal mousePressed(var mouse)
|
signal mousePressed(var mouse)
|
||||||
signal mouseReleased(var mouse, var moved)
|
signal mouseReleased(var mouse, var moved)
|
||||||
|
@ -44,7 +46,7 @@ Entity {
|
||||||
property point lastPosition
|
property point lastPosition
|
||||||
property point currentPosition
|
property point currentPosition
|
||||||
property bool hasMoved
|
property bool hasMoved
|
||||||
sourceDevice: mouseSourceDevice
|
sourceDevice: loseMouseFocus ? null : mouseSourceDevice
|
||||||
onPressed: {
|
onPressed: {
|
||||||
_pressed = true;
|
_pressed = true;
|
||||||
currentPosition.x = lastPosition.x = mouse.x;
|
currentPosition.x = lastPosition.x = mouse.x;
|
||||||
|
@ -60,6 +62,30 @@ Entity {
|
||||||
onPositionChanged: {
|
onPositionChanged: {
|
||||||
currentPosition.x = mouse.x;
|
currentPosition.x = mouse.x;
|
||||||
currentPosition.y = mouse.y;
|
currentPosition.y = mouse.y;
|
||||||
|
|
||||||
|
const dt = 0.02
|
||||||
|
|
||||||
|
if(panning) { // translate
|
||||||
|
var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.03;
|
||||||
|
var tx = axisMX.value * root.translateSpeed * d;
|
||||||
|
var ty = axisMY.value * root.translateSpeed * d;
|
||||||
|
mouseHandler.hasMoved = true;
|
||||||
|
root.camera.translate(Qt.vector3d(-tx, -ty, 0).times(dt));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(moving){ // trackball rotation
|
||||||
|
trackball.rotate(mouseHandler.lastPosition, mouseHandler.currentPosition, dt);
|
||||||
|
mouseHandler.lastPosition = mouseHandler.currentPosition;
|
||||||
|
mouseHandler.hasMoved = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(zooming) { // zoom with alt + RMD
|
||||||
|
var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.1;
|
||||||
|
var tz = axisMX.value * root.translateSpeed * d;
|
||||||
|
mouseHandler.hasMoved = true;
|
||||||
|
root.camera.translate(Qt.vector3d(0, 0, tz).times(dt), Camera.DontTranslateViewCenter)
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onDoubleClicked: mouseDoubleClicked(mouse)
|
onDoubleClicked: mouseDoubleClicked(mouse)
|
||||||
onWheel: {
|
onWheel: {
|
||||||
|
@ -162,32 +188,4 @@ Entity {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
components: [
|
|
||||||
FrameAction {
|
|
||||||
onTriggered: {
|
|
||||||
if(panning) { // translate
|
|
||||||
var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.03;
|
|
||||||
var tx = axisMX.value * root.translateSpeed * d;
|
|
||||||
var ty = axisMY.value * root.translateSpeed * d;
|
|
||||||
mouseHandler.hasMoved = true;
|
|
||||||
root.camera.translate(Qt.vector3d(-tx, -ty, 0).times(dt));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(moving){ // trackball rotation
|
|
||||||
trackball.rotate(mouseHandler.lastPosition, mouseHandler.currentPosition, dt);
|
|
||||||
mouseHandler.lastPosition = mouseHandler.currentPosition;
|
|
||||||
mouseHandler.hasMoved = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(zooming) { // zoom with alt + RMD
|
|
||||||
var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.1;
|
|
||||||
var tz = axisMX.value * root.translateSpeed * d;
|
|
||||||
mouseHandler.hasMoved = true;
|
|
||||||
root.camera.translate(Qt.vector3d(0, 0, tz).times(dt), Camera.DontTranslateViewCenter)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
36
meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml
Normal file
36
meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import Qt3D.Core 2.0
|
||||||
|
import Qt3D.Render 2.9
|
||||||
|
import Qt3D.Input 2.0
|
||||||
|
import Qt3D.Extras 2.10
|
||||||
|
import QtQuick 2.9
|
||||||
|
import Qt3D.Logic 2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for TransformGizmo.
|
||||||
|
* Must be instantiated to control an other entity.
|
||||||
|
* The goal is to instantiate the other entity inside this wrapper to gather the object and the gizmo.
|
||||||
|
* objectTranform is the component the other entity should use as a Transform.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
id: root
|
||||||
|
property DefaultCameraController sceneCameraController
|
||||||
|
property Layer frontLayerComponent
|
||||||
|
property var window
|
||||||
|
property alias uniformScale: transformGizmo.uniformScale // By default, if not set, the value is: false
|
||||||
|
property TransformGizmo transformGizmo: TransformGizmo {
|
||||||
|
id: transformGizmo
|
||||||
|
camera: root.camera
|
||||||
|
windowSize: root.windowSize
|
||||||
|
frontLayerComponent: root.frontLayerComponent
|
||||||
|
window: root.window
|
||||||
|
|
||||||
|
onPickedChanged: {
|
||||||
|
sceneCameraController.loseMouseFocus = pressed // Notify the camera if the transform takes/releases the focus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property Camera camera : sceneCameraController.camera
|
||||||
|
readonly property var windowSize: sceneCameraController.windowSize
|
||||||
|
readonly property alias objectTransform : transformGizmo.objectTransform // The Transform the object should use
|
||||||
|
}
|
|
@ -284,6 +284,38 @@ FloatingPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BoundingBox visibility (if meshing node)
|
||||||
|
MaterialToolButton {
|
||||||
|
visible: model.hasBoundingBox
|
||||||
|
enabled: model.visible
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillHeight: true
|
||||||
|
text: MaterialIcons.transform
|
||||||
|
font.pointSize: 10
|
||||||
|
ToolTip.text: model.displayBoundingBox ? "Hide BBox" : "Show BBox"
|
||||||
|
flat: true
|
||||||
|
opacity: model.visible ? (model.displayBoundingBox ? 1.0 : 0.6) : 0.6
|
||||||
|
onClicked: {
|
||||||
|
model.displayBoundingBox = !model.displayBoundingBox
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform visibility (if SfMTransform node)
|
||||||
|
MaterialToolButton {
|
||||||
|
visible: model.hasTransform
|
||||||
|
enabled: model.visible
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillHeight: true
|
||||||
|
text: MaterialIcons._3d_rotation
|
||||||
|
font.pointSize: 10
|
||||||
|
ToolTip.text: model.displayTransform ? "Hide Gizmo" : "Show Gizmo"
|
||||||
|
flat: true
|
||||||
|
opacity: model.visible ? (model.displayTransform ? 1.0 : 0.6) : 0.6
|
||||||
|
onClicked: {
|
||||||
|
model.displayTransform = !model.displayTransform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Media label and info
|
// Media label and info
|
||||||
Item {
|
Item {
|
||||||
implicitHeight: childrenRect.height
|
implicitHeight: childrenRect.height
|
||||||
|
|
|
@ -16,6 +16,11 @@ Entity {
|
||||||
property bool pickingEnabled: false
|
property bool pickingEnabled: false
|
||||||
readonly property alias count: instantiator.count // number of instantiated media delegates
|
readonly property alias count: instantiator.count // number of instantiated media delegates
|
||||||
|
|
||||||
|
// For TransformGizmo in BoundingBox
|
||||||
|
property DefaultCameraController sceneCameraController
|
||||||
|
property Layer frontLayerComponent
|
||||||
|
property var window
|
||||||
|
|
||||||
/// Camera to consider for positionning
|
/// Camera to consider for positionning
|
||||||
property Camera camera: null
|
property Camera camera: null
|
||||||
|
|
||||||
|
@ -41,6 +46,10 @@ Entity {
|
||||||
"valid": true,
|
"valid": true,
|
||||||
"label": "",
|
"label": "",
|
||||||
"visible": true,
|
"visible": true,
|
||||||
|
"hasBoundingBox": false, // for Meshing node only
|
||||||
|
"displayBoundingBox": true, // for Meshing node only
|
||||||
|
"hasTransform": false, // for SfMTransform node only
|
||||||
|
"displayTransform": true, // for SfMTransform node only
|
||||||
"section": "",
|
"section": "",
|
||||||
"attribute": null,
|
"attribute": null,
|
||||||
"entity": null,
|
"entity": null,
|
||||||
|
@ -146,7 +155,36 @@ Entity {
|
||||||
id: instantiator
|
id: instantiator
|
||||||
model: m.mediaModel
|
model: m.mediaModel
|
||||||
|
|
||||||
delegate: MediaLoader {
|
delegate: Entity {
|
||||||
|
id: instantiatedEntity
|
||||||
|
property alias fullyInstantiated: mediaLoader.fullyInstantiated
|
||||||
|
readonly property alias modelSource: mediaLoader.modelSource
|
||||||
|
|
||||||
|
// Get the node
|
||||||
|
property var currentNode: model.attribute ? model.attribute.node : null
|
||||||
|
property string nodeType: currentNode ? currentNode.nodeType: null
|
||||||
|
|
||||||
|
// Specific properties to the MESHING node (declared and initialized for every Entity anyway)
|
||||||
|
property bool hasBoundingBox: {
|
||||||
|
if(nodeType === "Meshing" && currentNode.attribute("useBoundingBox")) // Can have a BoundingBox
|
||||||
|
return currentNode.attribute("useBoundingBox").value
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
onHasBoundingBoxChanged: model.hasBoundingBox = hasBoundingBox
|
||||||
|
property bool displayBoundingBox: model.displayBoundingBox
|
||||||
|
|
||||||
|
// Specific properties to the SFMTRANSFORM node (declared and initialized for every Entity anyway)
|
||||||
|
property bool hasTransform: {
|
||||||
|
if(nodeType === "SfMTransform" && currentNode.attribute("method")) // Can have a Transform
|
||||||
|
return currentNode.attribute("method").value === "manual"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
onHasTransformChanged: model.hasTransform = hasTransform
|
||||||
|
property bool displayTransform: model.displayTransform
|
||||||
|
|
||||||
|
|
||||||
|
// Create the media
|
||||||
|
MediaLoader {
|
||||||
id: mediaLoader
|
id: mediaLoader
|
||||||
|
|
||||||
// whether MediaLoader has been fully instantiated by the NodeInstantiator
|
// whether MediaLoader has been fully instantiated by the NodeInstantiator
|
||||||
|
@ -162,19 +200,30 @@ Entity {
|
||||||
// updated when needed, whether raw source is valid or not
|
// updated when needed, whether raw source is valid or not
|
||||||
|
|
||||||
// raw source path
|
// raw source path
|
||||||
readonly property string rawSource: attribute ? attribute.value : model.source
|
property string rawSource: attribute ? attribute.value : model.source
|
||||||
// whether dependencies are statified (applies for output/connected input attributes only)
|
// whether dependencies are statified (applies for output/connected input attributes only)
|
||||||
readonly property bool dependencyReady: {
|
readonly property bool dependencyReady: {
|
||||||
if(attribute && attribute.isOutput)
|
if(!attribute)
|
||||||
return attribute.node.globalStatus === "SUCCESS";
|
// if the node is removed, the attribute will be invalid
|
||||||
if(attribute && attribute.isLink)
|
return false
|
||||||
return attribute.linkParam.node.globalStatus === "SUCCESS";
|
|
||||||
return true;
|
const rootAttribute = attribute.isLink ? attribute.rootLinkParam : attribute
|
||||||
|
if(rootAttribute.isOutput)
|
||||||
|
return rootAttribute.node.globalStatus === "SUCCESS"
|
||||||
|
return true // is an input param so no dependency
|
||||||
}
|
}
|
||||||
// source based on raw source + dependency status
|
// source based on raw source + dependency status
|
||||||
readonly property string currentSource: dependencyReady ? rawSource : ""
|
property string currentSource: dependencyReady ? rawSource : ""
|
||||||
// source based on currentSource + "requested" property
|
// source based on currentSource + "requested" property
|
||||||
readonly property string finalSource: model.requested ? currentSource : ""
|
property string finalSource: model.requested ? currentSource : ""
|
||||||
|
|
||||||
|
// To use only if we want to draw the input source and not the current node output (Warning: to use with caution)
|
||||||
|
// There is maybe a better way to do this to avoid overwritting bindings which should be readonly properties
|
||||||
|
function drawInputSource() {
|
||||||
|
rawSource = Qt.binding(() => instantiatedEntity.currentNode ? instantiatedEntity.currentNode.attribute("input").value: "")
|
||||||
|
currentSource = Qt.binding(() => rawSource)
|
||||||
|
finalSource = Qt.binding(() => rawSource)
|
||||||
|
}
|
||||||
|
|
||||||
camera: root.camera
|
camera: root.camera
|
||||||
renderMode: root.renderMode
|
renderMode: root.renderMode
|
||||||
|
@ -184,8 +233,14 @@ Entity {
|
||||||
// Use the object as NodeInstantiator model to be notified of its deletion
|
// Use the object as NodeInstantiator model to be notified of its deletion
|
||||||
NodeInstantiator {
|
NodeInstantiator {
|
||||||
model: attribute
|
model: attribute
|
||||||
delegate: Entity { objectName: "DestructionWatcher [" + attribute.toString() + "]" }
|
delegate: Entity { objectName: "DestructionWatcher [" + model.toString() + "]" }
|
||||||
onObjectRemoved: remove(idx)
|
onObjectRemoved: remove(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool alive: attribute ? attribute.node.alive : false
|
||||||
|
onAliveChanged: {
|
||||||
|
if(!alive && index >= 0)
|
||||||
|
remove(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'visible' property drives media loading request
|
// 'visible' property drives media loading request
|
||||||
|
@ -200,7 +255,7 @@ Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCacheAndModel(forceRequest) {
|
function updateCacheAndModel(forceRequest) {
|
||||||
// don't cache explicitely unloaded media
|
// don't cache explicitly unloaded media
|
||||||
if(model.requested && object && dependencyReady) {
|
if(model.requested && object && dependencyReady) {
|
||||||
// cache current object
|
// cache current object
|
||||||
if(cache.add(Filepath.urlToString(mediaLoader.source), object));
|
if(cache.add(Filepath.urlToString(mediaLoader.source), object));
|
||||||
|
@ -221,7 +276,7 @@ Entity {
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// keep 'source' -> 'entity' reference
|
// keep 'source' -> 'entity' reference
|
||||||
m.sourceToEntity[modelSource] = mediaLoader;
|
m.sourceToEntity[modelSource] = instantiatedEntity;
|
||||||
// always request media loading when delegate has been created
|
// always request media loading when delegate has been created
|
||||||
updateModel(true);
|
updateModel(true);
|
||||||
// if external media failed to open, remove element from model
|
// if external media failed to open, remove element from model
|
||||||
|
@ -231,6 +286,10 @@ Entity {
|
||||||
|
|
||||||
onCurrentSourceChanged: {
|
onCurrentSourceChanged: {
|
||||||
updateCacheAndModel(false)
|
updateCacheAndModel(false)
|
||||||
|
|
||||||
|
// Avoid the bounding box to disappear when we move it after a mesh already computed
|
||||||
|
if(instantiatedEntity.hasBoundingBox && !currentSource)
|
||||||
|
model.visible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
onFinalSourceChanged: {
|
onFinalSourceChanged: {
|
||||||
|
@ -258,9 +317,10 @@ Entity {
|
||||||
model[prop] = Qt.binding(function() { return object ? object[prop] : 0; });
|
model[prop] = Qt.binding(function() { return object ? object[prop] : 0; });
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else if(finalSource) {
|
else if(finalSource && status === Component.Ready) {
|
||||||
// source was valid but no loader was created, remove element
|
// source was valid but no loader was created, remove element
|
||||||
remove(index);
|
// check if component is ready to avoid removing element from the model before adding instance to the node
|
||||||
|
remove(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,13 +346,53 @@ Entity {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transform: display a TransformGizmo for SfMTransform node only
|
||||||
|
// note: use a NodeInstantiator to evaluate if the current node is a SfMTransform node and if the transform mode is set to Manual
|
||||||
|
NodeInstantiator {
|
||||||
|
id: sfmTransformGizmoInstantiator
|
||||||
|
active: instantiatedEntity.hasTransform
|
||||||
|
model: 1
|
||||||
|
|
||||||
|
SfMTransformGizmo {
|
||||||
|
id: sfmTransformGizmoEntity
|
||||||
|
sceneCameraController: root.sceneCameraController
|
||||||
|
frontLayerComponent: root.frontLayerComponent
|
||||||
|
window: root.window
|
||||||
|
currentSfMTransformNode: instantiatedEntity.currentNode
|
||||||
|
enabled: mediaLoader.visible && instantiatedEntity.displayTransform
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
mediaLoader.drawInputSource() // Because we are sure we want to show the input in MANUAL mode only
|
||||||
|
Scene3DHelper.addComponent(mediaLoader, sfmTransformGizmoEntity.objectTransform) // Add the transform to the media to see real-time transformations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoundingBox: display bounding box for MESHING computation
|
||||||
|
// note: use a NodeInstantiator to evaluate if the current node is a MESHING node and if the checkbox is active
|
||||||
|
NodeInstantiator {
|
||||||
|
id: boundingBoxInstantiator
|
||||||
|
active: instantiatedEntity.hasBoundingBox
|
||||||
|
model: 1
|
||||||
|
|
||||||
|
MeshingBoundingBox {
|
||||||
|
sceneCameraController: root.sceneCameraController
|
||||||
|
frontLayerComponent: root.frontLayerComponent
|
||||||
|
window: root.window
|
||||||
|
currentMeshingNode: instantiatedEntity.currentNode
|
||||||
|
enabled: mediaLoader.visible && instantiatedEntity.displayBoundingBox
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onObjectAdded: {
|
onObjectAdded: {
|
||||||
// notify object that it is now fully instantiated
|
// notify object that it is now fully instantiated
|
||||||
object.fullyInstantiated = true;
|
object.fullyInstantiated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onObjectRemoved: {
|
onObjectRemoved: {
|
||||||
delete m.sourceToEntity[object.modelSource];
|
if(m.sourceToEntity[object.modelSource])
|
||||||
|
delete m.sourceToEntity[object.modelSource]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import Utils 1.0
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear previously created objet if any
|
// clear previously created object if any
|
||||||
if(object) {
|
if(object) {
|
||||||
object.destroy();
|
object.destroy();
|
||||||
object = null;
|
object = null;
|
||||||
|
|
91
meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml
Normal file
91
meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import Qt3D.Core 2.0
|
||||||
|
import Qt3D.Render 2.9
|
||||||
|
import Qt3D.Input 2.0
|
||||||
|
import Qt3D.Extras 2.10
|
||||||
|
import QtQuick 2.9
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BoundingBox entity for Meshing node. Used to define the area to reconstruct.
|
||||||
|
* Simple box controlled by a gizmo to give easy and visual representation.
|
||||||
|
*/
|
||||||
|
Entity {
|
||||||
|
id: root
|
||||||
|
property DefaultCameraController sceneCameraController
|
||||||
|
property Layer frontLayerComponent
|
||||||
|
property var window
|
||||||
|
property var currentMeshingNode: null
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
EntityWithGizmo {
|
||||||
|
id: boundingBoxEntity
|
||||||
|
sceneCameraController: root.sceneCameraController
|
||||||
|
frontLayerComponent: root.frontLayerComponent
|
||||||
|
window: root.window
|
||||||
|
|
||||||
|
// Update node meshing slider values when the gizmo has changed: translation, rotation, scale, type
|
||||||
|
transformGizmo.onGizmoChanged: {
|
||||||
|
switch(type) {
|
||||||
|
case TransformGizmo.Type.TRANSLATION: {
|
||||||
|
_reconstruction.setAttribute(
|
||||||
|
root.currentMeshingNode.attribute("boundingBox.bboxTranslation"),
|
||||||
|
JSON.stringify([translation.x, translation.y, translation.z])
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Type.ROTATION: {
|
||||||
|
_reconstruction.setAttribute(
|
||||||
|
root.currentMeshingNode.attribute("boundingBox.bboxRotation"),
|
||||||
|
JSON.stringify([rotation.x, rotation.y, rotation.z])
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Type.SCALE: {
|
||||||
|
_reconstruction.setAttribute(
|
||||||
|
root.currentMeshingNode.attribute("boundingBox.bboxScale"),
|
||||||
|
JSON.stringify([scale.x, scale.y, scale.z])
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Type.ALL: {
|
||||||
|
_reconstruction.setAttribute(
|
||||||
|
root.currentMeshingNode.attribute("boundingBox"),
|
||||||
|
JSON.stringify([
|
||||||
|
[translation.x, translation.y, translation.z],
|
||||||
|
[rotation.x, rotation.y, rotation.z],
|
||||||
|
[scale.x, scale.y, scale.z]
|
||||||
|
])
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translation values from node (vector3d because this is the type of QTransform.translation)
|
||||||
|
property var nodeTranslation : Qt.vector3d(
|
||||||
|
root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxTranslation.x").value : 0,
|
||||||
|
root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxTranslation.y").value : 0,
|
||||||
|
root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxTranslation.z").value : 0
|
||||||
|
)
|
||||||
|
// Rotation values from node (3 separated values because QTransform stores Euler angles like this)
|
||||||
|
property var nodeRotationX: root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxRotation.x").value : 0
|
||||||
|
property var nodeRotationY: root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxRotation.y").value : 0
|
||||||
|
property var nodeRotationZ: root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxRotation.z").value : 0
|
||||||
|
// Scale values from node (vector3d because this is the type of QTransform.scale3D)
|
||||||
|
property var nodeScale: Qt.vector3d(
|
||||||
|
root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxScale.x").value : 1,
|
||||||
|
root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxScale.y").value : 1,
|
||||||
|
root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxScale.z").value : 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse.
|
||||||
|
// When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values.
|
||||||
|
transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation
|
||||||
|
transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX
|
||||||
|
transformGizmo.gizmoDisplayTransform.rotationY: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationY : nodeRotationY
|
||||||
|
transformGizmo.gizmoDisplayTransform.rotationZ: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationZ : nodeRotationZ
|
||||||
|
transformGizmo.objectTransform.scale3D: transformGizmo.focusGizmoPriority ? transformGizmo.objectTransform.scale3D : nodeScale
|
||||||
|
|
||||||
|
// The entity
|
||||||
|
BoundingBox { transform: boundingBoxEntity.objectTransform }
|
||||||
|
}
|
||||||
|
}
|
88
meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml
Normal file
88
meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import Qt3D.Core 2.0
|
||||||
|
import Qt3D.Render 2.9
|
||||||
|
import Qt3D.Input 2.0
|
||||||
|
import Qt3D.Extras 2.10
|
||||||
|
import QtQuick 2.9
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gizmo for SfMTransform node.
|
||||||
|
* Uses EntityWithGizmo wrapper because we should not instantiate TransformGizmo alone.
|
||||||
|
*/
|
||||||
|
Entity {
|
||||||
|
id: root
|
||||||
|
property DefaultCameraController sceneCameraController
|
||||||
|
property Layer frontLayerComponent
|
||||||
|
property var window
|
||||||
|
property var currentSfMTransformNode: null
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
readonly property alias objectTransform: sfmTranformGizmoEntity.objectTransform // The Transform the object should use
|
||||||
|
|
||||||
|
EntityWithGizmo {
|
||||||
|
id: sfmTranformGizmoEntity
|
||||||
|
sceneCameraController: root.sceneCameraController
|
||||||
|
frontLayerComponent: root.frontLayerComponent
|
||||||
|
window: root.window
|
||||||
|
uniformScale: true // We want to make uniform scale transformations
|
||||||
|
|
||||||
|
// Update node SfMTransform slider values when the gizmo has changed: translation, rotation, scale, type
|
||||||
|
transformGizmo.onGizmoChanged: {
|
||||||
|
switch(type) {
|
||||||
|
case TransformGizmo.Type.TRANSLATION: {
|
||||||
|
_reconstruction.setAttribute(
|
||||||
|
root.currentSfMTransformNode.attribute("manualTransform.manualTranslation"),
|
||||||
|
JSON.stringify([translation.x, translation.y, translation.z])
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Type.ROTATION: {
|
||||||
|
_reconstruction.setAttribute(
|
||||||
|
root.currentSfMTransformNode.attribute("manualTransform.manualRotation"),
|
||||||
|
JSON.stringify([rotation.x, rotation.y, rotation.z])
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Type.SCALE: {
|
||||||
|
// Only one scale is needed since the scale is uniform
|
||||||
|
_reconstruction.setAttribute(
|
||||||
|
root.currentSfMTransformNode.attribute("manualTransform.manualScale"),
|
||||||
|
scale.x
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Type.ALL: {
|
||||||
|
_reconstruction.setAttribute(
|
||||||
|
root.currentSfMTransformNode.attribute("manualTransform"),
|
||||||
|
JSON.stringify([
|
||||||
|
[translation.x, translation.y, translation.z],
|
||||||
|
[rotation.x, rotation.y, rotation.z],
|
||||||
|
scale.x
|
||||||
|
])
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translation values from node (vector3d because this is the type of QTransform.translation)
|
||||||
|
property var nodeTranslation : Qt.vector3d(
|
||||||
|
root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.x").value : 0,
|
||||||
|
root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.y").value : 0,
|
||||||
|
root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.z").value : 0
|
||||||
|
)
|
||||||
|
// Rotation values from node (3 separated values because QTransform stores Euler angles like this)
|
||||||
|
property var nodeRotationX: root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualRotation.x").value : 0
|
||||||
|
property var nodeRotationY: root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualRotation.y").value : 0
|
||||||
|
property var nodeRotationZ: root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualRotation.z").value : 0
|
||||||
|
// Scale value from node (simple number because we use uniform scale)
|
||||||
|
property var nodeScale: root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualScale").value : 1
|
||||||
|
|
||||||
|
// Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse.
|
||||||
|
// When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values.
|
||||||
|
transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation
|
||||||
|
transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX
|
||||||
|
transformGizmo.gizmoDisplayTransform.rotationY: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationY : nodeRotationY
|
||||||
|
transformGizmo.gizmoDisplayTransform.rotationZ: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationZ : nodeRotationZ
|
||||||
|
transformGizmo.objectTransform.scale3D: transformGizmo.focusGizmoPriority ? transformGizmo.objectTransform.scale3D : Qt.vector3d(nodeScale, nodeScale, nodeScale)
|
||||||
|
}
|
||||||
|
}
|
594
meshroom/ui/qml/Viewer3D/TransformGizmo.qml
Normal file
594
meshroom/ui/qml/Viewer3D/TransformGizmo.qml
Normal file
|
@ -0,0 +1,594 @@
|
||||||
|
import Qt3D.Core 2.0
|
||||||
|
import Qt3D.Render 2.9
|
||||||
|
import Qt3D.Input 2.0
|
||||||
|
import Qt3D.Extras 2.10
|
||||||
|
import QtQuick 2.9
|
||||||
|
import Qt3D.Logic 2.0
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import Utils 1.0
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple transformation gizmo entirely made with Qt3D entities.
|
||||||
|
* Uses Python Transformations3DHelper to compute matrices.
|
||||||
|
* This TransformGizmo entity should only be instantiated in EntityWithGizmo entity which is its wrapper.
|
||||||
|
* It means, to use it for a specified application, make sure to instantiate EntityWithGizmo.
|
||||||
|
*/
|
||||||
|
Entity {
|
||||||
|
id: root
|
||||||
|
property Camera camera
|
||||||
|
property var windowSize
|
||||||
|
property Layer frontLayerComponent // Used to draw gizmo on top of everything
|
||||||
|
property var window
|
||||||
|
readonly property alias gizmoScale: gizmoScaleLookSlider.value
|
||||||
|
property bool uniformScale: false // By default, the scale is not uniform
|
||||||
|
property bool focusGizmoPriority: false // If true, it is used to give the priority to the current transformation (and not to a upper-level binding)
|
||||||
|
property Transform gizmoDisplayTransform: Transform {
|
||||||
|
id: gizmoDisplayTransform
|
||||||
|
scale: root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length() // The gizmo needs a constant apparent size
|
||||||
|
}
|
||||||
|
// Component the object controlled by the gizmo must use
|
||||||
|
property Transform objectTransform : Transform {
|
||||||
|
translation: gizmoDisplayTransform.translation
|
||||||
|
rotation: gizmoDisplayTransform.rotation
|
||||||
|
scale3D: Qt.vector3d(1,1,1)
|
||||||
|
}
|
||||||
|
|
||||||
|
signal pickedChanged(bool pressed)
|
||||||
|
signal gizmoChanged(var translation, var rotation, var scale, int type)
|
||||||
|
|
||||||
|
function emitGizmoChanged(type) {
|
||||||
|
const translation = gizmoDisplayTransform.translation // Position in space
|
||||||
|
const rotation = Qt.vector3d(gizmoDisplayTransform.rotationX, gizmoDisplayTransform.rotationY, gizmoDisplayTransform.rotationZ) // Euler angles
|
||||||
|
const scale = objectTransform.scale3D // Scale of the object
|
||||||
|
|
||||||
|
gizmoChanged(translation, rotation, scale, type)
|
||||||
|
root.focusGizmoPriority = false
|
||||||
|
}
|
||||||
|
|
||||||
|
components: [gizmoDisplayTransform, mouseHandler, frontLayerComponent]
|
||||||
|
|
||||||
|
|
||||||
|
/***** ENUMS *****/
|
||||||
|
|
||||||
|
enum Axis {
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
Z
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
TRANSLATION,
|
||||||
|
ROTATION,
|
||||||
|
SCALE,
|
||||||
|
ALL
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAxisEnum(axis) {
|
||||||
|
switch(axis) {
|
||||||
|
case TransformGizmo.Axis.X: return Qt.vector3d(1,0,0)
|
||||||
|
case TransformGizmo.Axis.Y: return Qt.vector3d(0,1,0)
|
||||||
|
case TransformGizmo.Axis.Z: return Qt.vector3d(0,0,1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertTypeEnum(type) {
|
||||||
|
switch(type) {
|
||||||
|
case TransformGizmo.Type.TRANSLATION: return "TRANSLATION"
|
||||||
|
case TransformGizmo.Type.ROTATION: return "ROTATION"
|
||||||
|
case TransformGizmo.Type.SCALE: return "SCALE"
|
||||||
|
case TransformGizmo.Type.ALL: return "ALL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** TRANSFORMATIONS (using local vars) *****/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Translate locally the gizmo and the object.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* To make local translation, we need to recompute a new matrix.
|
||||||
|
* Update gizmoDisplayTransform's matrix and all its properties while avoiding the override of translation property.
|
||||||
|
* Update objectTransform in the same time thanks to binding on translation property.
|
||||||
|
*
|
||||||
|
* @param initialModelMatrix object containing position, rotation and scale matrices + rotation quaternion
|
||||||
|
* @param translateVec vector3d used to make the local translation
|
||||||
|
*/
|
||||||
|
function doRelativeTranslation(initialModelMatrix, translateVec) {
|
||||||
|
Transformations3DHelper.relativeLocalTranslate(
|
||||||
|
gizmoDisplayTransform,
|
||||||
|
initialModelMatrix.position,
|
||||||
|
initialModelMatrix.rotation,
|
||||||
|
initialModelMatrix.scale,
|
||||||
|
translateVec
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rotate the gizmo and the object around a specific axis.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* To make local rotation around an axis, we need to recompute a new matrix from a quaternion.
|
||||||
|
* Update gizmoDisplayTransform's matrix and all its properties while avoiding the override of rotation, rotationX, rotationY and rotationZ properties.
|
||||||
|
* Update objectTransform in the same time thanks to binding on rotation property.
|
||||||
|
*
|
||||||
|
* @param initialModelMatrix object containing position, rotation and scale matrices + rotation quaternion
|
||||||
|
* @param axis vector3d describing the axis to rotate around
|
||||||
|
* @param degree angle of rotation in degrees
|
||||||
|
*/
|
||||||
|
function doRelativeRotation(initialModelMatrix, axis, degree) {
|
||||||
|
Transformations3DHelper.relativeLocalRotate(
|
||||||
|
gizmoDisplayTransform,
|
||||||
|
initialModelMatrix.position,
|
||||||
|
initialModelMatrix.quaternion,
|
||||||
|
initialModelMatrix.scale,
|
||||||
|
axis,
|
||||||
|
degree
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scale the object relatively to its current scale.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* To change scale of the object, we need to recompute a new matrix to avoid overriding bindings.
|
||||||
|
* Update objectTransform properties only (gizmoDisplayTransform is not affected).
|
||||||
|
*
|
||||||
|
* @param initialModelMatrix object containing position, rotation and scale matrices + rotation quaternion
|
||||||
|
* @param scaleVec vector3d used to make the relative scale
|
||||||
|
*/
|
||||||
|
function doRelativeScale(initialModelMatrix, scaleVec) {
|
||||||
|
Transformations3DHelper.relativeLocalScale(
|
||||||
|
objectTransform,
|
||||||
|
initialModelMatrix.position,
|
||||||
|
initialModelMatrix.rotation,
|
||||||
|
initialModelMatrix.scale,
|
||||||
|
scaleVec
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the translation of the gizmo and the object.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Update gizmoDisplayTransform's matrix and all its properties while avoiding the override of translation property.
|
||||||
|
* Update objectTransform in the same time thanks to binding on translation property.
|
||||||
|
*/
|
||||||
|
function resetTranslation() {
|
||||||
|
const mat = gizmoDisplayTransform.matrix
|
||||||
|
const newMat = Qt.matrix4x4(
|
||||||
|
mat.m11, mat.m12, mat.m13, 0,
|
||||||
|
mat.m21, mat.m22, mat.m23, 0,
|
||||||
|
mat.m31, mat.m32, mat.m33, 0,
|
||||||
|
mat.m41, mat.m42, mat.m43, 1
|
||||||
|
)
|
||||||
|
gizmoDisplayTransform.setMatrix(newMat)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the rotation of the gizmo and the object.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Update gizmoDisplayTransform's quaternion while avoiding the override of rotationX, rotationY and rotationZ properties.
|
||||||
|
* Update objectTransform in the same time thanks to binding on rotation property.
|
||||||
|
* Here, we can change the rotation property (but not rotationX, rotationY and rotationZ because they can be used in upper-level bindings).
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* We could implement a way of changing the matrix instead of overriding rotation (quaternion) property.
|
||||||
|
*/
|
||||||
|
function resetRotation() {
|
||||||
|
gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the scale of the object.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* To reset the scale, we make the difference of the current one to 1 and recompute the matrix.
|
||||||
|
* Like this, we kind of apply an inverse scale transformation.
|
||||||
|
* It prevents overriding scale3D property (because it can be used in upper-level binding).
|
||||||
|
*/
|
||||||
|
function resetScale() {
|
||||||
|
const modelMat = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix)
|
||||||
|
const scaleDiff = Qt.vector3d(
|
||||||
|
-(objectTransform.scale3D.x - 1),
|
||||||
|
-(objectTransform.scale3D.y - 1),
|
||||||
|
-(objectTransform.scale3D.z - 1)
|
||||||
|
)
|
||||||
|
doRelativeScale(modelMat, scaleDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** DEVICES *****/
|
||||||
|
|
||||||
|
MouseDevice { id: mouseSourceDevice }
|
||||||
|
|
||||||
|
MouseHandler {
|
||||||
|
id: mouseHandler
|
||||||
|
sourceDevice: enabled ? mouseSourceDevice : null
|
||||||
|
property var objectPicker: null
|
||||||
|
property bool enabled: false
|
||||||
|
|
||||||
|
onPositionChanged: {
|
||||||
|
if (objectPicker && objectPicker.button === Qt.LeftButton) {
|
||||||
|
root.focusGizmoPriority = true
|
||||||
|
|
||||||
|
// Get the selected axis
|
||||||
|
const pickedAxis = convertAxisEnum(objectPicker.gizmoAxis)
|
||||||
|
|
||||||
|
// TRANSLATION or SCALE transformation
|
||||||
|
if(objectPicker.gizmoType === TransformGizmo.Type.TRANSLATION || objectPicker.gizmoType === TransformGizmo.Type.SCALE) {
|
||||||
|
// Compute the vector PickedPosition -> CurrentMousePoint
|
||||||
|
const pickedPosition = objectPicker.screenPoint
|
||||||
|
const mouseVector = Qt.vector2d(mouse.x - pickedPosition.x, -(mouse.y - pickedPosition.y))
|
||||||
|
|
||||||
|
// Transform the positive picked axis vector from World Coord to Screen Coord
|
||||||
|
const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1))
|
||||||
|
const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1))
|
||||||
|
const screenPoint2D = Transformations3DHelper.pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize)
|
||||||
|
const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize)
|
||||||
|
const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y))
|
||||||
|
|
||||||
|
// Get the cosinus of the angle from the screenAxisVector to the mouseVector
|
||||||
|
// It will be used as a intensity factor
|
||||||
|
const cosAngle = screenAxisVector.dotProduct(mouseVector) / (screenAxisVector.length() * mouseVector.length())
|
||||||
|
const offset = cosAngle * mouseVector.length() / objectPicker.scaleUnit
|
||||||
|
|
||||||
|
// Do the transformation
|
||||||
|
if(objectPicker.gizmoType === TransformGizmo.Type.TRANSLATION && offset !== 0) {
|
||||||
|
doRelativeTranslation(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo
|
||||||
|
}
|
||||||
|
else if(objectPicker.gizmoType === TransformGizmo.Type.SCALE && offset !== 0) {
|
||||||
|
if(root.uniformScale)
|
||||||
|
doRelativeScale(objectPicker.modelMatrix, Qt.vector3d(1,1,1).times(offset)) // Do a uniform scale from the initial Object Model Matrix when we picked the gizmo
|
||||||
|
else
|
||||||
|
doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a scale on one axis from the initial Object Model Matrix when we picked the gizmo
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// ROTATION transformation
|
||||||
|
else if(objectPicker.gizmoType === TransformGizmo.Type.ROTATION) {
|
||||||
|
// Get Screen Coordinates of the gizmo center
|
||||||
|
const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1))
|
||||||
|
const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize)
|
||||||
|
|
||||||
|
// Get the vector screenCenter2D -> PickedPosition
|
||||||
|
const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y))
|
||||||
|
|
||||||
|
// Compute the vector screenCenter2D -> CurrentMousePoint
|
||||||
|
const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y))
|
||||||
|
|
||||||
|
// Get the angle from the originalVector to the mouseVector
|
||||||
|
const angle = Math.atan2(-originalVector.y*mouseVector.x + originalVector.x*mouseVector.y, originalVector.x*mouseVector.x + originalVector.y*mouseVector.y) * 180 / Math.PI
|
||||||
|
|
||||||
|
// Get the orientation of the gizmo in function of the camera
|
||||||
|
const gizmoLocalAxisVector = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 0))
|
||||||
|
const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint)
|
||||||
|
const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1
|
||||||
|
|
||||||
|
if (angle !== 0) doRelativeRotation(objectPicker.modelMatrix, pickedAxis, angle*orientation) // Do a rotation from the initial Object Model Matrix when we picked the gizmo
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(objectPicker && objectPicker.button === Qt.RightButton) {
|
||||||
|
resetMenu.popup(window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
if(objectPicker && mouse.button === Qt.LeftButton) {
|
||||||
|
const type = objectPicker.gizmoType
|
||||||
|
objectPicker = null // To prevent going again in the onPositionChanged
|
||||||
|
emitGizmoChanged(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: resetMenu
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Reset Translation"
|
||||||
|
onTriggered: {
|
||||||
|
resetTranslation()
|
||||||
|
emitGizmoChanged(TransformGizmo.Type.TRANSLATION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Reset Rotation"
|
||||||
|
onTriggered: {
|
||||||
|
resetRotation()
|
||||||
|
emitGizmoChanged(TransformGizmo.Type.ROTATION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Reset Scale"
|
||||||
|
onTriggered: {
|
||||||
|
resetScale()
|
||||||
|
emitGizmoChanged(TransformGizmo.Type.SCALE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Reset All"
|
||||||
|
onTriggered: {
|
||||||
|
resetTranslation()
|
||||||
|
resetRotation()
|
||||||
|
resetScale()
|
||||||
|
emitGizmoChanged(TransformGizmo.Type.ALL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Gizmo Scale Look"
|
||||||
|
Slider {
|
||||||
|
id: gizmoScaleLookSlider
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
height: parent.height
|
||||||
|
width: parent.width * 0.40
|
||||||
|
|
||||||
|
from: 0.06
|
||||||
|
to: 0.30
|
||||||
|
stepSize: 0.01
|
||||||
|
value: 0.15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** GIZMO'S BASIC COMPONENTS *****/
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
id: centerSphereEntity
|
||||||
|
components: [centerSphereMesh, centerSphereMaterial, frontLayerComponent]
|
||||||
|
|
||||||
|
SphereMesh {
|
||||||
|
id: centerSphereMesh
|
||||||
|
radius: 0.04
|
||||||
|
rings: 8
|
||||||
|
slices: 8
|
||||||
|
}
|
||||||
|
PhongMaterial {
|
||||||
|
id: centerSphereMaterial
|
||||||
|
property color base: "white"
|
||||||
|
ambient: base
|
||||||
|
shininess: 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AXIS GIZMO INSTANTIATOR => X, Y and Z
|
||||||
|
NodeInstantiator {
|
||||||
|
model: 3
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
id: axisContainer
|
||||||
|
property int axis : {
|
||||||
|
switch(index) {
|
||||||
|
case 0: return TransformGizmo.Axis.X
|
||||||
|
case 1: return TransformGizmo.Axis.Y
|
||||||
|
case 2: return TransformGizmo.Axis.Z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property color baseColor: {
|
||||||
|
switch(axis) {
|
||||||
|
case TransformGizmo.Axis.X: return "#e63b55" // Red
|
||||||
|
case TransformGizmo.Axis.Y: return "#83c414" // Green
|
||||||
|
case TransformGizmo.Axis.Z: return "#3387e2" // Blue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property real lineRadius: 0.011
|
||||||
|
|
||||||
|
// SCALE ENTITY
|
||||||
|
Entity {
|
||||||
|
id: scaleEntity
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
id: axisCylinder
|
||||||
|
components: [cylinderMesh, cylinderTransform, scaleMaterial, frontLayerComponent]
|
||||||
|
|
||||||
|
CylinderMesh {
|
||||||
|
id: cylinderMesh
|
||||||
|
length: 0.5
|
||||||
|
radius: axisContainer.lineRadius
|
||||||
|
rings: 2
|
||||||
|
slices: 16
|
||||||
|
}
|
||||||
|
Transform {
|
||||||
|
id: cylinderTransform
|
||||||
|
matrix: {
|
||||||
|
const offset = cylinderMesh.length/2 + centerSphereMesh.radius
|
||||||
|
const m = Qt.matrix4x4()
|
||||||
|
switch(axis) {
|
||||||
|
case TransformGizmo.Axis.X: {
|
||||||
|
m.translate(Qt.vector3d(offset, 0, 0))
|
||||||
|
m.rotate(90, Qt.vector3d(0,0,1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Axis.Y: {
|
||||||
|
m.translate(Qt.vector3d(0, offset, 0))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Axis.Z: {
|
||||||
|
m.translate(Qt.vector3d(0, 0, offset))
|
||||||
|
m.rotate(90, Qt.vector3d(1,0,0))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
id: axisScaleBox
|
||||||
|
components: [cubeScaleMesh, cubeScaleTransform, scaleMaterial, scalePicker, frontLayerComponent]
|
||||||
|
|
||||||
|
CuboidMesh {
|
||||||
|
id: cubeScaleMesh
|
||||||
|
property real edge: 0.06
|
||||||
|
xExtent: edge
|
||||||
|
yExtent: edge
|
||||||
|
zExtent: edge
|
||||||
|
}
|
||||||
|
Transform {
|
||||||
|
id: cubeScaleTransform
|
||||||
|
matrix: {
|
||||||
|
const offset = cylinderMesh.length + centerSphereMesh.radius
|
||||||
|
const m = Qt.matrix4x4()
|
||||||
|
switch(axis) {
|
||||||
|
case TransformGizmo.Axis.X: {
|
||||||
|
m.translate(Qt.vector3d(offset, 0, 0))
|
||||||
|
m.rotate(90, Qt.vector3d(0,0,1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Axis.Y: {
|
||||||
|
m.translate(Qt.vector3d(0, offset, 0))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Axis.Z: {
|
||||||
|
m.translate(Qt.vector3d(0, 0, offset))
|
||||||
|
m.rotate(90, Qt.vector3d(1,0,0))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PhongMaterial {
|
||||||
|
id: scaleMaterial
|
||||||
|
ambient: baseColor
|
||||||
|
}
|
||||||
|
|
||||||
|
TransformGizmoPicker {
|
||||||
|
id: scalePicker
|
||||||
|
mouseController: mouseHandler
|
||||||
|
gizmoMaterial: scaleMaterial
|
||||||
|
gizmoBaseColor: baseColor
|
||||||
|
gizmoAxis: axis
|
||||||
|
gizmoType: TransformGizmo.Type.SCALE
|
||||||
|
|
||||||
|
onPickedChanged: {
|
||||||
|
// Save the current transformations of the OBJECT
|
||||||
|
this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix)
|
||||||
|
// Compute a scale unit at picking time
|
||||||
|
this.scaleUnit = Transformations3DHelper.computeScaleUnitFromModelMatrix(convertAxisEnum(gizmoAxis), gizmoDisplayTransform.matrix, camera, root.windowSize)
|
||||||
|
// Prevent camera transformations
|
||||||
|
root.pickedChanged(picker.isPressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRANSLATION ENTITY
|
||||||
|
Entity {
|
||||||
|
id: positionEntity
|
||||||
|
components: [coneMesh, coneTransform, positionMaterial, positionPicker, frontLayerComponent]
|
||||||
|
|
||||||
|
ConeMesh {
|
||||||
|
id: coneMesh
|
||||||
|
bottomRadius : 0.035
|
||||||
|
topRadius : 0.001
|
||||||
|
hasBottomEndcap : true
|
||||||
|
hasTopEndcap : true
|
||||||
|
length : 0.13
|
||||||
|
rings : 2
|
||||||
|
slices : 8
|
||||||
|
}
|
||||||
|
Transform {
|
||||||
|
id: coneTransform
|
||||||
|
matrix: {
|
||||||
|
const offset = cylinderMesh.length + centerSphereMesh.radius + 0.4
|
||||||
|
const m = Qt.matrix4x4()
|
||||||
|
switch(axis) {
|
||||||
|
case TransformGizmo.Axis.X: {
|
||||||
|
m.translate(Qt.vector3d(offset, 0, 0))
|
||||||
|
m.rotate(-90, Qt.vector3d(0,0,1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Axis.Y: {
|
||||||
|
m.translate(Qt.vector3d(0, offset, 0))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TransformGizmo.Axis.Z: {
|
||||||
|
m.translate(Qt.vector3d(0, 0, offset))
|
||||||
|
m.rotate(90, Qt.vector3d(1,0,0))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PhongMaterial {
|
||||||
|
id: positionMaterial
|
||||||
|
ambient: baseColor
|
||||||
|
}
|
||||||
|
|
||||||
|
TransformGizmoPicker {
|
||||||
|
id: positionPicker
|
||||||
|
mouseController: mouseHandler
|
||||||
|
gizmoMaterial: positionMaterial
|
||||||
|
gizmoBaseColor: baseColor
|
||||||
|
gizmoAxis: axis
|
||||||
|
gizmoType: TransformGizmo.Type.TRANSLATION
|
||||||
|
|
||||||
|
onPickedChanged: {
|
||||||
|
// Save the current transformations of the OBJECT
|
||||||
|
this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix)
|
||||||
|
// Compute a scale unit at picking time
|
||||||
|
this.scaleUnit = Transformations3DHelper.computeScaleUnitFromModelMatrix(convertAxisEnum(gizmoAxis), gizmoDisplayTransform.matrix, camera, root.windowSize)
|
||||||
|
// Prevent camera transformations
|
||||||
|
root.pickedChanged(picker.isPressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROTATION ENTITY
|
||||||
|
Entity {
|
||||||
|
id: rotationEntity
|
||||||
|
components: [torusMesh, torusTransform, rotationMaterial, rotationPicker, frontLayerComponent]
|
||||||
|
|
||||||
|
TorusMesh {
|
||||||
|
id: torusMesh
|
||||||
|
radius: cylinderMesh.length + 0.25
|
||||||
|
minorRadius: axisContainer.lineRadius
|
||||||
|
slices: 8
|
||||||
|
rings: 32
|
||||||
|
}
|
||||||
|
Transform {
|
||||||
|
id: torusTransform
|
||||||
|
matrix: {
|
||||||
|
const scaleDiff = 2*torusMesh.minorRadius + 0.01 // Just to make sure there is no face overlapping
|
||||||
|
const m = Qt.matrix4x4()
|
||||||
|
switch(axis) {
|
||||||
|
case TransformGizmo.Axis.X: m.rotate(90, Qt.vector3d(0,1,0)); break
|
||||||
|
case TransformGizmo.Axis.Y: m.rotate(90, Qt.vector3d(1,0,0)); m.scale(Qt.vector3d(1-scaleDiff, 1-scaleDiff, 1-scaleDiff)); break
|
||||||
|
case TransformGizmo.Axis.Z: m.scale(Qt.vector3d(1-2*scaleDiff, 1-2*scaleDiff, 1-2*scaleDiff)); break
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PhongMaterial {
|
||||||
|
id: rotationMaterial
|
||||||
|
ambient: baseColor
|
||||||
|
}
|
||||||
|
|
||||||
|
TransformGizmoPicker {
|
||||||
|
id: rotationPicker
|
||||||
|
mouseController: mouseHandler
|
||||||
|
gizmoMaterial: rotationMaterial
|
||||||
|
gizmoBaseColor: baseColor
|
||||||
|
gizmoAxis: axis
|
||||||
|
gizmoType: TransformGizmo.Type.ROTATION
|
||||||
|
|
||||||
|
onPickedChanged: {
|
||||||
|
// Save the current transformations of the OBJECT
|
||||||
|
this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix)
|
||||||
|
// No need to compute a scale unit for rotation
|
||||||
|
// Prevent camera transformations
|
||||||
|
root.pickedChanged(picker.isPressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml
Normal file
46
meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import Qt3D.Core 2.0
|
||||||
|
import Qt3D.Render 2.9
|
||||||
|
import Qt3D.Input 2.0
|
||||||
|
import Qt3D.Extras 2.10
|
||||||
|
import QtQuick 2.9
|
||||||
|
import Qt3D.Logic 2.0
|
||||||
|
|
||||||
|
ObjectPicker {
|
||||||
|
id: root
|
||||||
|
property bool isPressed : false
|
||||||
|
property MouseHandler mouseController
|
||||||
|
property var gizmoMaterial
|
||||||
|
property color gizmoBaseColor
|
||||||
|
property int gizmoAxis
|
||||||
|
property int gizmoType
|
||||||
|
property point screenPoint
|
||||||
|
property var modelMatrix
|
||||||
|
property real scaleUnit
|
||||||
|
property int button
|
||||||
|
|
||||||
|
signal pickedChanged(var picker)
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
mouseController.enabled = true
|
||||||
|
mouseController.objectPicker = this
|
||||||
|
root.isPressed = true
|
||||||
|
screenPoint = pick.position
|
||||||
|
button = pick.button
|
||||||
|
pickedChanged(this)
|
||||||
|
}
|
||||||
|
onEntered: {
|
||||||
|
gizmoMaterial.ambient = "white"
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
if(!isPressed) gizmoMaterial.ambient = gizmoBaseColor
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
gizmoMaterial.ambient = gizmoBaseColor
|
||||||
|
root.isPressed = false
|
||||||
|
mouseController.objectPicker = null
|
||||||
|
mouseController.enabled = false
|
||||||
|
pickedChanged(this)
|
||||||
|
}
|
||||||
|
}
|
|
@ -207,6 +207,17 @@ FocusScope {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LayerFilter {
|
||||||
|
filterMode: LayerFilter.DiscardAnyMatchingLayers
|
||||||
|
layers: Layer {id: drawOnFront}
|
||||||
|
}
|
||||||
|
LayerFilter {
|
||||||
|
filterMode: LayerFilter.AcceptAnyMatchingLayers
|
||||||
|
layers: [drawOnFront]
|
||||||
|
RenderStateSet {
|
||||||
|
renderStates: DepthTest { depthFunction: DepthTest.GreaterOrEqual }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,6 +234,11 @@ FocusScope {
|
||||||
pickingEnabled: cameraController.pickingActive || doubleClickTimer.running
|
pickingEnabled: cameraController.pickingActive || doubleClickTimer.running
|
||||||
camera: cameraSelector.camera
|
camera: cameraSelector.camera
|
||||||
|
|
||||||
|
// Used for TransformGizmo in BoundingBox
|
||||||
|
sceneCameraController: cameraController
|
||||||
|
frontLayerComponent: drawOnFront
|
||||||
|
window: root
|
||||||
|
|
||||||
components: [
|
components: [
|
||||||
Transform {
|
Transform {
|
||||||
id: transform
|
id: transform
|
||||||
|
|
|
@ -453,6 +453,10 @@ class Reconstruction(UIGraph):
|
||||||
|
|
||||||
self.setDefaultPipeline(defaultPipeline)
|
self.setDefaultPipeline(defaultPipeline)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.clearActiveNodes()
|
||||||
|
super(Reconstruction, self).clear()
|
||||||
|
|
||||||
def setDefaultPipeline(self, defaultPipeline):
|
def setDefaultPipeline(self, defaultPipeline):
|
||||||
self._defaultPipeline = defaultPipeline
|
self._defaultPipeline = defaultPipeline
|
||||||
|
|
||||||
|
@ -463,6 +467,10 @@ class Reconstruction(UIGraph):
|
||||||
for nodeType, _ in meshroom.core.nodesDesc.items():
|
for nodeType, _ in meshroom.core.nodesDesc.items():
|
||||||
self._activeNodes.add(ActiveNode(nodeType, self))
|
self._activeNodes.add(ActiveNode(nodeType, self))
|
||||||
|
|
||||||
|
def clearActiveNodes(self):
|
||||||
|
for key in self._activeNodes.keys():
|
||||||
|
self._activeNodes.get(key).node = None
|
||||||
|
|
||||||
def onCameraInitChanged(self):
|
def onCameraInitChanged(self):
|
||||||
# Update active nodes when CameraInit changes
|
# Update active nodes when CameraInit changes
|
||||||
nodes = self._graph.nodesFromNode(self._cameraInit)[0]
|
nodes = self._graph.nodesFromNode(self._cameraInit)[0]
|
||||||
|
@ -559,7 +567,7 @@ class Reconstruction(UIGraph):
|
||||||
def getViewpoints(self):
|
def getViewpoints(self):
|
||||||
""" Return the Viewpoints model. """
|
""" Return the Viewpoints model. """
|
||||||
# TODO: handle multiple Viewpoints models
|
# TODO: handle multiple Viewpoints models
|
||||||
return self._cameraInit.viewpoints.value if self._cameraInit else None
|
return self._cameraInit.viewpoints.value if self._cameraInit else QObjectListModel(parent=self)
|
||||||
|
|
||||||
def updateCameraInits(self):
|
def updateCameraInits(self):
|
||||||
cameraInits = self._graph.nodesByType("CameraInit", sortedByIndex=True)
|
cameraInits = self._graph.nodesByType("CameraInit", sortedByIndex=True)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue