Merge pull request #586 from acq4/parametertree-updates

Parametertree updates
This commit is contained in:
Luke Campagnola 2017-10-04 09:26:34 -07:00 committed by GitHub
commit 7d992a56ee
3 changed files with 72 additions and 18 deletions

View File

@ -162,7 +162,11 @@ class Parameter(QtCore.QObject):
'title': None, 'title': None,
#'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits. #'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits.
} }
value = opts.get('value', None)
name = opts.get('name', None)
self.opts.update(opts) self.opts.update(opts)
self.opts['value'] = None # will be set later.
self.opts['name'] = None
self.childs = [] self.childs = []
self.names = {} ## map name:child self.names = {} ## map name:child
@ -172,17 +176,19 @@ class Parameter(QtCore.QObject):
self.blockTreeChangeEmit = 0 self.blockTreeChangeEmit = 0
#self.monitoringChildren = False ## prevent calling monitorChildren more than once #self.monitoringChildren = False ## prevent calling monitorChildren more than once
if 'value' not in self.opts: if not isinstance(name, basestring):
self.opts['value'] = None
if 'name' not in self.opts or not isinstance(self.opts['name'], basestring):
raise Exception("Parameter must have a string name specified in opts.") raise Exception("Parameter must have a string name specified in opts.")
self.setName(opts['name']) self.setName(name)
self.addChildren(self.opts.get('children', [])) self.addChildren(self.opts.get('children', []))
if 'value' in self.opts and 'default' not in self.opts: self.opts['value'] = None
self.opts['default'] = self.opts['value'] if value is not None:
self.setValue(value)
if 'default' not in self.opts:
self.opts['default'] = None
self.setDefault(self.opts['value'])
## Connect all state changed signals to the general sigStateChanged ## Connect all state changed signals to the general sigStateChanged
self.sigValueChanged.connect(lambda param, data: self.emitStateChanged('value', data)) self.sigValueChanged.connect(lambda param, data: self.emitStateChanged('value', data))
@ -647,15 +653,16 @@ class Parameter(QtCore.QObject):
"""Return a child parameter. """Return a child parameter.
Accepts the name of the child or a tuple (path, to, child) Accepts the name of the child or a tuple (path, to, child)
Added in version 0.9.9. Ealier versions used the 'param' method, which is still Added in version 0.9.9. Earlier versions used the 'param' method, which is still
implemented for backward compatibility.""" implemented for backward compatibility.
"""
try: try:
param = self.names[names[0]] param = self.names[names[0]]
except KeyError: except KeyError:
raise Exception("Parameter %s has no child named %s" % (self.name(), names[0])) raise KeyError("Parameter %s has no child named %s" % (self.name(), names[0]))
if len(names) > 1: if len(names) > 1:
return param.param(*names[1:]) return param.child(*names[1:])
else: else:
return param return param

View File

@ -1,5 +1,7 @@
from ..pgcollections import OrderedDict from ..pgcollections import OrderedDict
import numpy as np import numpy as np
import copy
class SystemSolver(object): class SystemSolver(object):
""" """
@ -73,6 +75,12 @@ class SystemSolver(object):
self.__dict__['_currentGets'] = set() self.__dict__['_currentGets'] = set()
self.reset() self.reset()
def copy(self):
sys = type(self)()
sys.__dict__['_vars'] = copy.deepcopy(self.__dict__['_vars'])
sys.__dict__['_currentGets'] = copy.deepcopy(self.__dict__['_currentGets'])
return sys
def reset(self): def reset(self):
""" """
Reset all variables in the solver to their default state. Reset all variables in the solver to their default state.
@ -167,6 +175,16 @@ class SystemSolver(object):
elif constraint == 'fixed': elif constraint == 'fixed':
if 'f' not in var[3]: if 'f' not in var[3]:
raise TypeError("Fixed constraints not allowed for '%s'" % name) raise TypeError("Fixed constraints not allowed for '%s'" % name)
# This is nice, but not reliable because sometimes there is 1 DOF but we set 2
# values simultaneously.
# if var[2] is None:
# try:
# self.get(name)
# # has already been computed by the system; adding a fixed constraint
# # would overspecify the system.
# raise ValueError("Cannot fix parameter '%s'; system would become overconstrained." % name)
# except RuntimeError:
# pass
var[2] = constraint var[2] = constraint
elif isinstance(constraint, tuple): elif isinstance(constraint, tuple):
if 'r' not in var[3]: if 'r' not in var[3]:
@ -177,7 +195,7 @@ class SystemSolver(object):
raise TypeError("constraint must be None, True, 'fixed', or tuple. (got %s)" % constraint) raise TypeError("constraint must be None, True, 'fixed', or tuple. (got %s)" % constraint)
# type checking / massaging # type checking / massaging
if var[1] is np.ndarray: if var[1] is np.ndarray and value is not None:
value = np.array(value, dtype=float) value = np.array(value, dtype=float)
elif var[1] in (int, float, tuple) and value is not None: elif var[1] in (int, float, tuple) and value is not None:
value = var[1](value) value = var[1](value)
@ -187,7 +205,7 @@ class SystemSolver(object):
raise ValueError("Setting %s = %s violates constraint %s" % (name, value, var[2])) raise ValueError("Setting %s = %s violates constraint %s" % (name, value, var[2]))
# invalidate other dependent values # invalidate other dependent values
if var[0] is not None: if var[0] is not None or value is None:
# todo: we can make this more clever..(and might need to) # todo: we can make this more clever..(and might need to)
# we just know that a value of None cannot have dependencies # we just know that a value of None cannot have dependencies
# (because if anyone else had asked for this value, it wouldn't be # (because if anyone else had asked for this value, it wouldn't be
@ -237,6 +255,31 @@ class SystemSolver(object):
for k in self._vars: for k in self._vars:
getattr(self, k) getattr(self, k)
def checkOverconstraint(self):
"""Check whether the system is overconstrained. If so, return the name of
the first overconstrained parameter.
Overconstraints occur when any fixed parameter can be successfully computed by the system.
(Ideally, all parameters are either fixed by the user or constrained by the
system, but never both).
"""
for k,v in self._vars.items():
if v[2] == 'fixed' and 'n' in v[3]:
oldval = v[:]
self.set(k, None, None)
try:
self.get(k)
return k
except RuntimeError:
pass
finally:
self._vars[k] = oldval
return False
def __repr__(self): def __repr__(self):
state = OrderedDict() state = OrderedDict()
for name, var in self._vars.items(): for name, var in self._vars.items():

View File

@ -463,11 +463,14 @@ class GroupParameter(Parameter):
""" """
itemClass = GroupParameterItem itemClass = GroupParameterItem
sigAddNew = QtCore.Signal(object, object) # self, type
def addNew(self, typ=None): def addNew(self, typ=None):
""" """
This method is called when the user has requested to add a new item to the group. This method is called when the user has requested to add a new item to the group.
By default, it emits ``sigAddNew(self, typ)``.
""" """
raise Exception("Must override this function in subclass.") self.sigAddNew.emit(self, typ)
def setAddList(self, vals): def setAddList(self, vals):
"""Change the list of options available for the user to add to the group.""" """Change the list of options available for the user to add to the group."""
@ -605,6 +608,7 @@ class ActionParameterItem(ParameterItem):
ParameterItem.__init__(self, param, depth) ParameterItem.__init__(self, param, depth)
self.layoutWidget = QtGui.QWidget() self.layoutWidget = QtGui.QWidget()
self.layout = QtGui.QHBoxLayout() self.layout = QtGui.QHBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layoutWidget.setLayout(self.layout) self.layoutWidget.setLayout(self.layout)
self.button = QtGui.QPushButton(param.name()) self.button = QtGui.QPushButton(param.name())
#self.layout.addSpacing(100) #self.layout.addSpacing(100)