...
 
Commits (1)
......@@ -44,7 +44,6 @@ SET_PROPERTY_FIELD_UNITS_AND_MINIMUM(ParticlesComputePropertyModifierDelegate, c
* Constructs a new instance of this class.
******************************************************************************/
ParticlesComputePropertyModifierDelegate::ParticlesComputePropertyModifierDelegate(DataSet* dataset) : ComputePropertyModifierDelegate(dataset),
_neighborExpressions(QStringList(QString())),
_cutoff(3),
_useMultilineFields(false)
{
......@@ -82,8 +81,9 @@ std::shared_ptr<ComputePropertyModifierDelegate::PropertyComputeEngine> Particle
ParticleInputHelper pih(dataset(), input);
ParticleProperty* posProperty = pih.expectStandardProperty<ParticleProperty>(ParticleProperty::PositionProperty);
if(!neighborExpressions().empty() && neighborExpressions().size() != outputProperty->componentCount())
throwException(tr("Number of neighbor expressions does not match component count of output property."));
if(!neighborExpressions().empty() && neighborExpressions().size() != outputProperty->componentCount() && (neighborExpressions().size() != 1 || !neighborExpressions().front().isEmpty()))
throwException(tr("Number of neighbor expressions that have been specified (%1) does not match the number of components per particle (%2) of the output property '%3'.")
.arg(neighborExpressions().size()).arg(outputProperty->componentCount()).arg(outputProperty->name()));
TimeInterval validityInterval = input.stateValidity();
......
......@@ -15,8 +15,7 @@ def ComputePropertyModifier_cutoff_radius(self):
if self.operate_on != 'particles': return 3.0
return self.delegate.cutoff_radius
def ComputePropertyModifier_set_cutoff_radius(self, radius):
if self.operate_on != 'particles':
raise AttributeError("Setting cutoff_radius is only permitted if operate_on was previously set to 'particles'.")
self.operate_on = 'particles'
self.delegate.cutoff_radius = radius
ComputePropertyModifier.cutoff_radius = property(ComputePropertyModifier_cutoff_radius, ComputePropertyModifier_set_cutoff_radius)
......@@ -35,8 +34,7 @@ def ComputePropertyModifier_neighbor_expressions(self):
if self.operate_on != 'particles': return []
return self.delegate.neighbor_expressions
def ComputePropertyModifier_set_neighbor_expressions(self, expressions):
if self.operate_on != 'particles':
raise AttributeError("Setting neighbor_expressions is only permitted if operate_on was previously set to 'particles'.")
self.operate_on = 'particles'
self.delegate.neighbor_expressions = expressions
ComputePropertyModifier.neighbor_expressions = property(ComputePropertyModifier_neighbor_expressions, ComputePropertyModifier_set_neighbor_expressions)
......@@ -50,8 +48,6 @@ def ComputePropertyModifier_neighbor_mode(self):
if self.operate_on != 'particles': return False
return True
def ComputePropertyModifier_set_neighbor_mode(self, flag):
if self.operate_on != 'particles':
raise AttributeError("Setting neighbor_mode is only permitted if operate_on was previously set to 'particles'.")
if flag != True:
raise ValueError("Setting neighbor_mode to False is not supported anymore. Please set the neighbor expression(s) to empty strings instead.")
raise ValueError("Setting neighbor_mode to False is not supported anymore in this version of OVITO. Please set the neighbor expression(s) to empty strings instead.")
ComputePropertyModifier.neighbor_mode = property(ComputePropertyModifier_neighbor_mode, ComputePropertyModifier_set_neighbor_mode)
......@@ -416,7 +416,7 @@ void ovito_class_initialization_helper::applyParameters(py::object& pyobj, const
{
// Iterate over the keys of the dictionary and set attributes of the
// newly created object.
for(auto item : params) {
for(const auto& item : params) {
// Check if the attribute exists. Otherwise raise error.
if(!py::hasattr(pyobj, item.first)) {
PyErr_SetObject(PyExc_AttributeError,
......
......@@ -73,6 +73,14 @@ void ComputePropertyModifierEditor::createUI(const RolloutInsertionParameters& r
ComputePropertyModifier* modifier = static_object_cast<ComputePropertyModifier>(editObject);
outputPropertyUI->setPropertyClass((modifier && modifier->delegate()) ? &modifier->delegate()->propertyClass() : nullptr);
});
connect(outputPropertyUI, &PropertyReferenceParameterUI::valueEntered, this, [this]() {
if(ComputePropertyModifier* modifier = static_object_cast<ComputePropertyModifier>(editObject())) {
if(modifier->delegate() && modifier->outputProperty().type() != PropertyStorage::GenericUserProperty)
modifier->setPropertyComponentCount(modifier->delegate()->propertyClass().standardPropertyComponentCount(modifier->outputProperty().type()));
else
modifier->setPropertyComponentCount(1);
}
});
// Create the check box for the selection flag.
BooleanParameterUI* selectionFlagUI = new BooleanParameterUI(this, PROPERTY_FIELD(ComputePropertyModifier::onlySelectedElements));
......
......@@ -151,11 +151,11 @@ void ColorCodingModifier::initializeModifier(ModifierApplication* modApp)
******************************************************************************/
void ColorCodingModifier::referenceReplaced(const PropertyFieldDescriptor& field, RefTarget* oldTarget, RefTarget* newTarget)
{
// Whenever the delegate of this modifier is being replaced, reset the source property reference.
// Whenever the delegate of this modifier is being replaced, update the source property reference.
if(field == PROPERTY_FIELD(DelegatingModifier::delegate) && !isBeingLoaded()) {
ColorCodingModifierDelegate* colorDelegate = static_object_cast<ColorCodingModifierDelegate>(delegate());
if(!colorDelegate || &colorDelegate->propertyClass() != sourceProperty().propertyClass()) {
setSourceProperty({});
if(!dataset()->undoStack().isUndoingOrRedoing() && !isBeingLoaded() && colorDelegate) {
setSourceProperty(sourceProperty().convertToPropertyClass(&colorDelegate->propertyClass()));
}
}
DelegatingModifier::referenceReplaced(field, oldTarget, newTarget);
......
......@@ -63,6 +63,9 @@ ComputePropertyModifier::ComputePropertyModifier(DataSet* dataset) : Asynchronou
{
// Let this modifier act on particles by default.
createDefaultModifierDelegate(ComputePropertyModifierDelegate::OOClass(), QStringLiteral("ParticlesComputePropertyModifierDelegate"));
// Set default output property.
if(delegate())
setOutputProperty(PropertyReference(&delegate()->propertyClass(), QStringLiteral("My property")));
}
/******************************************************************************
......@@ -79,28 +82,8 @@ void ComputePropertyModifier::setPropertyComponentCount(int newComponentCount)
newList.append("0");
setExpressions(newList);
}
}
/******************************************************************************
* Is called when the value of a property of this object has changed.
******************************************************************************/
void ComputePropertyModifier::propertyChanged(const PropertyFieldDescriptor& field)
{
if(field == PROPERTY_FIELD(outputProperty)) {
if(!dataset()->undoStack().isUndoingOrRedoing()) {
if(delegate() && outputProperty().type() != PropertyStorage::GenericUserProperty)
setPropertyComponentCount(delegate()->propertyClass().standardPropertyComponentCount(outputProperty().type()));
else
setPropertyComponentCount(1);
}
}
else if(field == PROPERTY_FIELD(expressions)) {
if(!dataset()->undoStack().isUndoingOrRedoing() && delegate()) {
delegate()->setComponentCount(expressions().size());
}
}
AsynchronousDelegatingModifier::propertyChanged(field);
if(delegate())
delegate()->setComponentCount(newComponentCount);
}
/******************************************************************************
......@@ -110,8 +93,8 @@ void ComputePropertyModifier::referenceReplaced(const PropertyFieldDescriptor& f
{
if(field == PROPERTY_FIELD(AsynchronousDelegatingModifier::delegate)) {
if(!dataset()->undoStack().isUndoingOrRedoing() && !isBeingLoaded() && delegate()) {
setOutputProperty(outputProperty().convertToPropertyClass(&delegate()->propertyClass()));
delegate()->setComponentCount(expressions().size());
setOutputProperty(PropertyReference(&delegate()->propertyClass(), QStringLiteral("My property")));
}
}
......@@ -132,6 +115,8 @@ Future<AsynchronousModifier::ComputeEnginePtr> ComputePropertyModifier::createEn
const PropertyClass& propertyClass = delegate()->propertyClass();
if(!propertyClass.isDataPresent(input))
throwException(tr("Cannot compute property '%1', because the input data contains no %2.").arg(outputProperty().name()).arg(propertyClass.elementDescriptionName()));
if(outputProperty().propertyClass() != &propertyClass)
throwException(tr("Property %1 to be computed is not a %2 property.").arg(outputProperty().name()).arg(propertyClass.elementDescriptionName()));
// Get the number of input elements.
size_t nelements = propertyClass.elementCount(input);
......
......@@ -193,9 +193,6 @@ public:
protected:
/// \brief Is called when the value of a property of this object has changed.
virtual void propertyChanged(const PropertyFieldDescriptor& field) override;
/// \brief Is called when the value of a reference field of this RefMaker changes.
virtual void referenceReplaced(const PropertyFieldDescriptor& field, RefTarget* oldTarget, RefTarget* newTarget) override;
......
......@@ -84,12 +84,10 @@ void FreezePropertyModifier::initializeModifier(ModifierApplication* modApp)
******************************************************************************/
void FreezePropertyModifier::propertyChanged(const PropertyFieldDescriptor& field)
{
// Whenever the selected property class of this modifier is changed, clear the source property reference.
if(field == PROPERTY_FIELD(GenericPropertyModifier::propertyClass) && !isBeingLoaded()) {
if(propertyClass() != sourceProperty().propertyClass()) {
setSourceProperty({});
setDestinationProperty({});
}
// Whenever the selected property class of this modifier changes, update the property references accordingly.
if(field == PROPERTY_FIELD(GenericPropertyModifier::propertyClass) && !isBeingLoaded() && !dataset()->undoStack().isUndoingOrRedoing()) {
setSourceProperty(sourceProperty().convertToPropertyClass(propertyClass()));
setDestinationProperty(destinationProperty().convertToPropertyClass(propertyClass()));
}
GenericPropertyModifier::propertyChanged(field);
}
......
......@@ -117,11 +117,9 @@ void HistogramModifier::initializeModifier(ModifierApplication* modApp)
******************************************************************************/
void HistogramModifier::propertyChanged(const PropertyFieldDescriptor& field)
{
// Whenever the selected property class of this modifier is changed, clear the source property reference.
if(field == PROPERTY_FIELD(GenericPropertyModifier::propertyClass) && !isBeingLoaded()) {
if(propertyClass() != sourceProperty().propertyClass()) {
setSourceProperty({});
}
// Whenever the selected property class of this modifier changes, update the source property reference accordingly.
if(field == PROPERTY_FIELD(GenericPropertyModifier::propertyClass) && !isBeingLoaded() && !dataset()->undoStack().isUndoingOrRedoing()) {
setSourceProperty(sourceProperty().convertToPropertyClass(propertyClass()));
}
GenericPropertyModifier::propertyChanged(field);
}
......
......@@ -117,11 +117,10 @@ void ScatterPlotModifier::initializeModifier(ModifierApplication* modApp)
******************************************************************************/
void ScatterPlotModifier::propertyChanged(const PropertyFieldDescriptor& field)
{
// Whenever the selected property class of this modifier is changed, clear the source property references.
// Otherwise they might be pointing to the wrong kind of property.
if(field == PROPERTY_FIELD(GenericPropertyModifier::propertyClass) && !isBeingLoaded()) {
setXAxisProperty({});
setYAxisProperty({});
// Whenever the selected property class of this modifier is changed, update the source property references.
if(field == PROPERTY_FIELD(GenericPropertyModifier::propertyClass) && !isBeingLoaded() && !dataset()->undoStack().isUndoingOrRedoing()) {
setXAxisProperty(xAxisProperty().convertToPropertyClass(propertyClass()));
setYAxisProperty(yAxisProperty().convertToPropertyClass(propertyClass()));
}
GenericPropertyModifier::propertyChanged(field);
}
......@@ -221,6 +220,11 @@ PipelineFlowState ScatterPlotModifier::evaluatePreliminary(TimePoint time, Modif
xyData[i].rx() = xProperty->getIntComponent(i, xVecComponent);
}
}
else if(xProperty->dataType() == PropertyStorage::Int64) {
for(size_t i = 0; i < xProperty->size(); i++) {
xyData[i].rx() = xProperty->getInt64Component(i, xVecComponent);
}
}
else throwException(tr("Property '%1' has a data type that is not supported by the scatter plot modifier.").arg(xProperty->name()));
// Collect Y coordinates.
......@@ -234,6 +238,11 @@ PipelineFlowState ScatterPlotModifier::evaluatePreliminary(TimePoint time, Modif
xyData[i].ry() = yProperty->getIntComponent(i, yVecComponent);
}
}
else if(yProperty->dataType() == PropertyStorage::Int64) {
for(size_t i = 0; i < yProperty->size(); i++) {
xyData[i].ry() = yProperty->getInt64Component(i, yVecComponent);
}
}
else throwException(tr("Property '%1' has a data type that is not supported by the scatter plot modifier.").arg(yProperty->name()));
// Determine value ranges.
......
......@@ -79,12 +79,9 @@ void SelectTypeModifier::initializeModifier(ModifierApplication* modApp)
******************************************************************************/
void SelectTypeModifier::propertyChanged(const PropertyFieldDescriptor& field)
{
// Whenever the selected property class of this modifier is changed, clear the source property reference.
// Otherwise it might be pointing to the wrong kind of property.
if(field == PROPERTY_FIELD(GenericPropertyModifier::propertyClass) && !isBeingLoaded()) {
if(propertyClass() != sourceProperty().propertyClass()) {
setSourceProperty({});
}
// Whenever the selected property class of this modifier is changed, update the source property reference accordingly.
if(field == PROPERTY_FIELD(GenericPropertyModifier::propertyClass) && !isBeingLoaded() && !dataset()->undoStack().isUndoingOrRedoing()) {
setSourceProperty(sourceProperty().convertToPropertyClass(propertyClass()));
}
GenericPropertyModifier::propertyChanged(field);
}
......
......@@ -287,10 +287,7 @@ PYBIND11_MODULE(StdMod, m)
"If :py:attr:`.operate_on` is set to ``'bonds'``, this can be one of the :ref:`standard bond properties <bond-types-list>` "
"or a name of a user-defined :py:class:`~ovito.data.BondProperty`. "
"\n\n"
"When the input property has multiple components, then a component name must be appended to the property base name, e.g. ``\"Velocity.X\"``. "
"\n\n"
"Note: Make sure that :py:attr:`.operate_on` is set to the desired value *before* setting this attribute, "
"because changing :py:attr:`.operate_on` will implicitly reset the :py:attr:`!property` attribute. ")
"When the input property has multiple components, then a component name must be appended to the property base name, e.g. ``\"Velocity.X\"``. ")
.def_property("start_value", &ColorCodingModifier::startValue, &ColorCodingModifier::setStartValue,
"This parameter defines, together with the :py:attr:`.end_value` parameter, the normalization range for mapping the input property values to colors.")
.def_property("end_value", &ColorCodingModifier::endValue, &ColorCodingModifier::setEndValue,
......@@ -318,8 +315,6 @@ PYBIND11_MODULE(StdMod, m)
"Selects the kind of data elements this modifier should operate on. "
"Supported values are: ``'particles'``, ``'bonds'``, ``'vectors'``. "
"\n\n"
"Note: Assigning a new value to this attribute resets the :py:attr:`.property` field. "
"\n\n"
":Default: ``'particles'``\n")
;
......@@ -381,16 +376,11 @@ PYBIND11_MODULE(StdMod, m)
"When selecting particles, possible input properties are ``\'Particle Type\'`` and ``\'Structure Type\'``, for example. "
"When selecting bonds, ``'Bond Type'`` is a typical input property for this modifier. "
"\n\n"
"Note: Make sure that :py:attr:`.operate_on` is set to the desired value *before* setting this attribute, "
"because changing :py:attr:`.operate_on` will implicitly reset the :py:attr:`!property` attribute. "
"\n\n"
":Default: ``''``\n")
.def_property("operate_on", modifierPropertyClassGetter(), modifierPropertyClassSetter(),
"Selects the kind of data elements this modifier should select. "
"Supported values are: ``'particles'``, ``'bonds'``. "
"\n\n"
"Note: Assigning a new value to this attribute resets the :py:attr:`.property` field. "
"\n\n"
":Default: ``'particles'``\n")
// Required by implementation of SelectTypeModifier.types attribute:
.def_property("_selected_type_ids", &SelectTypeModifier::selectedTypeIDs, &SelectTypeModifier::setSelectedTypeIDs)
......@@ -422,8 +412,6 @@ PYBIND11_MODULE(StdMod, m)
"Selects the kind of data elements this modifier should operate on. "
"Supported values are: ``'particles'``, ``'bonds'``, ``'voxels'``. "
"\n\n"
"Note: Assigning a new value to this attribute resets the :py:attr:`.property` field. "
"\n\n"
":Default: ``'particles'``\n")
.def_property("property", &HistogramModifier::sourceProperty, [](HistogramModifier& mod, py::object val) {
mod.setSourceProperty(convertPythonPropertyReference(val, mod.propertyClass()));
......@@ -431,9 +419,6 @@ PYBIND11_MODULE(StdMod, m)
"The name of the input property for which to compute the histogram. "
"For vector properties a component name must be appended in the string, e.g. ``\"Velocity.X\"``. "
"\n\n"
"Note: Make sure that :py:attr:`.operate_on` is set to the desired value *before* setting this attribute, "
"because changing :py:attr:`.operate_on` will implicitly reset the :py:attr:`!property` attribute. "
"\n\n"
":Default: ``''``\n")
.def_property("bin_count", &HistogramModifier::numberOfBins, &HistogramModifier::setNumberOfBins,
"The number of histogram bins."
......@@ -663,18 +648,12 @@ PYBIND11_MODULE(StdMod, m)
.def_property("source_property", &FreezePropertyModifier::sourceProperty, [](FreezePropertyModifier& mod, py::object val) {
mod.setSourceProperty(convertPythonPropertyReference(val, mod.propertyClass()));
},
"The name of the input property that should be evaluated by the modifier on the animation frame specified by :py:attr:`.freeze_at`. "
"\n\n"
"Note: Make sure that :py:attr:`.operate_on` is set to the desired value *before* setting this attribute, "
"because changing :py:attr:`.operate_on` will implicitly reset the :py:attr:`!source_property` attribute. ")
"The name of the input property that should be evaluated by the modifier on the animation frame specified by :py:attr:`.freeze_at`. ")
.def_property("destination_property", &FreezePropertyModifier::destinationProperty, [](FreezePropertyModifier& mod, py::object val) {
mod.setDestinationProperty(convertPythonPropertyReference(val, mod.propertyClass()));
},
"The name of the output property that should be created by the modifier. "
"It may be the same as :py:attr:`.source_property`. If the destination property already exists in the modifier's input, the values are overwritten. "
"\n\n"
"Note: Make sure that :py:attr:`.operate_on` is set to the desired value *before* setting this attribute, "
"because changing :py:attr:`.operate_on` will implicitly reset the :py:attr:`!destination_property` attribute. ")
"It may be the same as :py:attr:`.source_property`. If the destination property already exists in the modifier's input, the values are overwritten. ")
.def_property("freeze_at",
[](FreezePropertyModifier& mod) {
return mod.dataset()->animationSettings()->timeToFrame(mod.freezeTime());
......@@ -689,8 +668,6 @@ PYBIND11_MODULE(StdMod, m)
"Selects the kind of properties this modifier should operate on. "
"Supported values are: ``'particles'``, ``'bonds'``, ``'voxels'``. "
"\n\n"
"Note: Assigning a new value to this attribute resets the :py:attr:`.source_property` and :py:attr:`.destination_property` fields. "
"\n\n"
":Default: ``'particles'``\n")
;
ovito_class<FreezePropertyModifierApplication, ModifierApplication>{m};
......@@ -734,9 +711,6 @@ PYBIND11_MODULE(StdMod, m)
"A list of strings containing the math expressions to compute, one for each vector component of the selected output property. "
"If the output property is scalar, the list must comprise one expression string. "
"\n\n"
"Note: Before setting this field, make sure that :py:attr:`.output_property` is already set to the desired value, "
"because changing the :py:attr:`.output_property` will implicitly resize the :py:attr:`!expressions` list. "
"\n\n"
"See the corresponding `user manual page <../../particles.modifiers.compute_property.html>`__ for a description of the expression syntax. "
"\n\n"
":Default: ``[\"0\"]``\n")
......@@ -752,11 +726,7 @@ PYBIND11_MODULE(StdMod, m)
"If :py:attr:`.operate_on` is set to ``'bonds'``, this can be one of the :ref:`standard bond properties <bond-types-list>` "
"or a name of a new user-defined :py:class:`~ovito.data.BondProperty`. "
"\n\n"
"Note: Make sure that the :py:attr:`.operate_on` field is set to the desired value *before* setting this field, "
"because changing :py:attr:`.operate_on` will implicitly reset :py:attr:`!output_property` to its default value. "
"\n\n"
":Default: ``\"My property\"``\n")
.def_property("component_count", &ComputePropertyModifier::propertyComponentCount, &ComputePropertyModifier::setPropertyComponentCount)
.def_property("only_selected", &ComputePropertyModifier::onlySelectedElements, &ComputePropertyModifier::setOnlySelectedElements,
"If ``True``, the property is only computed for currently selected elements. "
"In this case, the property values of unselected elements will be preserved if the output property already exists. "
......
......@@ -65,6 +65,21 @@ QString PropertyReference::nameWithComponent() const
return QString("%1.%2").arg(name()).arg(vectorComponent() + 1);
}
/******************************************************************************
* Returns a new property reference that uses the same name as the current one,
* but with a different property class.
******************************************************************************/
PropertyReference PropertyReference::convertToPropertyClass(PropertyClassPtr pclass) const
{
OVITO_ASSERT(pclass != nullptr);
PropertyReference newref = *this;
if(pclass != propertyClass()) {
newref._propertyClass = pclass;
newref._type = pclass->standardPropertyTypeId(name());
}
return newref;
}
/// Writes a PropertyReference to an output stream.
/// \relates PropertyReference
SaveStream& operator<<(SaveStream& stream, const PropertyReference& r)
......
......@@ -85,6 +85,9 @@ public:
/// Finds the referenced property in the given pipeline state.
PropertyObject* findInState(const PipelineFlowState& state) const;
/// Returns a new property reference that uses the same name as the current one, but with a different property class.
PropertyReference convertToPropertyClass(PropertyClassPtr pclass) const;
private:
......
......@@ -53,8 +53,10 @@ assert(np.array_equal(data.bonds['testprop'], expected_result))
# Test: bond length computation
pipeline.source.load("../../files/XSF/1symb.xsf")
pipeline.modifiers.append(ovito.modifiers.ComputePropertyModifier(operate_on = 'bonds', output_property = 'MyLength',
expressions = ['sqrt((@1.Position.X-@2.Position.X)^2 + (@1.Position.Y-@2.Position.Y)^2 + (@1.Position.Z-@2.Position.Z)^2)']))
pipeline.modifiers.append(ovito.modifiers.ComputePropertyModifier(
expressions = ['sqrt((@1.Position.X-@2.Position.X)^2 + (@1.Position.Y-@2.Position.Y)^2 + (@1.Position.Z-@2.Position.Z)^2)'],
output_property = 'MyLength',
operate_on = 'bonds'))
modifier.output_property = 'Length'
modifier.expressions = ["BondLength"]
data = pipeline.compute()
......