Maybe there is a way to do this that wouldn't need any changes to other elements of FreeCAD and accomplishes the following:
relate object parameters to user create model parameters
user created parameters and relations saved and retrieved with the model file
some helpful gui component
A python feature object with two dictionaries, one for parameters, one to hold the relationships that bind these parameters to other object parameters/dimensions. The keys in the relationship dictionary could be the parameters pulled from all the other model objects, no? The keys for the parameters dictionary would just be new parameters that I want to create. A script opens a gui interface that allows for friendly editing of these dictionaries as well as initially creating the object if it isn't already found in the model.
So I likely can do this myself since FreeCAD has been designed to be so extensible through Python! Thanks (Any tips?...)
Relate parameter values between objects
Forum rules
Be nice to others! Read the FreeCAD code of conduct!
Be nice to others! Read the FreeCAD code of conduct!
- tanderson69
- Veteran
- Posts: 1626
- Joined: Thu Feb 18, 2010 1:07 am
Re: Relate parameter values between objects
Coming late to the party on this one, but I was hoping this would come up. In UG it was called expressions. Expressions could be used in multiple features and had the ability to be interpart(linked between files). I wrote a lot of customization code to automatically setup these links between assembly components. I agree with Jreigel that they can and do get abused, but I think they are a great feature when used with restraint.
Re: Relate parameter values between objects
What I thought I would try, to start with, was creating an object that stores these parameters and relationships. Any ideas on anything, including naming, would be appreciated. Storing the user created parameters is done in a python dictionary, mparameters, which is an App::PropertyPythonObject of the main ModelParameters object, which is an "App::FeaturePython". What I am stuck on is how to do the relations. Particularly, how to hold the reference to the parameter we want to affect (FreeCAD.ActiveDocument.Sphere.Radius for example) in the ModelParameters object. Initially I thought I could use that actual parameter as a key in the dictionary, relations, with the value being the code to "evaluate" but keys must be immutable types like strings or numbers. Anyway, basically I think I need to create a reference to an object and store that reference in my ModelParameters object. Maybe I just do this in a string...
Code: Select all
import FreeCAD
class ModelParameters:
def __init__(self, obj):
obj.addProperty("App::PropertyPythonObject","Parameters").Parameters = {}
obj.addProperty("App::PropertyPythonObject","Relations").Relations = {} //not sure what to do here...
obj.Proxy = self
def onChanged(self, fp, prop):
return
def execute(self, fp):
return
def getModelParameters():
doc = FreeCAD.ActiveDocument
if hasattr(doc,'ModelParameters'):
mp = doc.ModelParameters
else:
mp = doc.addObject("App::FeaturePython","ModelParameters")
ModelParameters(mp)
return mp
Re: Relate parameter values between objects
I think the relations could be stored as tuples in a list. The first item in the tuple would be the expression as a string. The next item is the object that the parameter belongs to. Third would be that object's parameter that is to be altered, as a string. If this parameter is not a float, such as Placement, then it gets more complex... but maybe can still be handled in a special way using additional items in the tuple.What I am stuck on is how to do the relations.
Re: Relate parameter values between objects
Well, the following has been an interesting experience for me. This script doesn't yet support many of the things it should support, such as deleting or renaming a "model parameter" via the gui, or interacting with object properties besides those that are directly float values or Placement.Base.x/y/z. As simple as it looks I still had a bit of a time figuring out why something was not working the way I wanted. This seemed much more complicated when writing something with a gui. But I think it was still a good learning experience, hey.
I call the script ModelParameters.py so if it's in my Mod/Scripts file then I can use it with the following code in the FreeCAD python interpreter:
I call the script ModelParameters.py so if it's in my Mod/Scripts file then I can use it with the following code in the FreeCAD python interpreter:
Code: Select all
import ModelParameters
d=QtGui.QWidget()
d.ui=ModelParameters.Ui_Dialog()
d.ui.setupUi(d)
d.show()
Code: Select all
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'NewModelParameters.ui'
#
# Created: Sun Dec 05 21:12:07 2010
# by: PyQt4 UI code generator 4.8.1
#
# WARNING! All changes made in this file will be lost!
import FreeCAD, string
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class ModelParameters:
def __init__(self, obj):
obj.addProperty("App::PropertyPythonObject","Parameters").Parameters = {}
obj.addProperty("App::PropertyPythonObject","Relations").Relations = {}
obj.Proxy = self
def onChanged(self, fp, prop):
return
def execute(self, fp):
return
class Ui_Dialog(object):
def get_model_parameters():
if FreeCAD.ActiveDocument == None:
FreeCAD.newDocument()
doc = FreeCAD.ActiveDocument
if hasattr(doc,'ModelParameters'):
mp = doc.ModelParameters
else:
mp = doc.addObject("App::FeaturePython","ModelParameters")
ModelParameters(mp)
return mp
mp = get_model_parameters()
relations_tree_o_items = {} # map tree top level item to model object
relations_tree_p_items = {} # map tree child item to object property tuple (object,property)
def setupUi(self, Dialog):
Dialog.setObjectName(_fromUtf8("Dialog"))
Dialog.resize(631, 541)
self.verticalLayoutWidget = QtGui.QWidget(Dialog)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(9, 9, 611, 521))
self.verticalLayoutWidget.setObjectName(_fromUtf8("verticalLayoutWidget"))
self.vertical_layout = QtGui.QVBoxLayout(self.verticalLayoutWidget)
self.vertical_layout.setObjectName(_fromUtf8("vertical_layout"))
self.label_model_parameters = QtGui.QLabel(self.verticalLayoutWidget)
self.label_model_parameters.setObjectName(_fromUtf8("label_model_parameters"))
self.vertical_layout.addWidget(self.label_model_parameters)
self.table_model_parameters = QtGui.QTableWidget(self.verticalLayoutWidget)
self.table_model_parameters.setObjectName(_fromUtf8("table_model_parameters"))
self.table_model_parameters.setColumnCount(2)
self.table_model_parameters.setRowCount(0)
self.header_item_name = QtGui.QTableWidgetItem()
self.table_model_parameters.setHorizontalHeaderItem(0, self.header_item_name)
self.header_item_value = QtGui.QTableWidgetItem()
self.table_model_parameters.setHorizontalHeaderItem(1, self.header_item_value)
self.vertical_layout.addWidget(self.table_model_parameters)
self.label_relations = QtGui.QLabel(self.verticalLayoutWidget)
self.label_relations.setObjectName(_fromUtf8("label_relations"))
self.vertical_layout.addWidget(self.label_relations)
self.tree_relations = QtGui.QTreeWidget(self.verticalLayoutWidget)
self.tree_relations.setObjectName(_fromUtf8("tree_relations"))
self.vertical_layout.addWidget(self.tree_relations)
self.retranslateUi(Dialog)
self.refresh_mp_table()
self.refresh_relations_tree()
QtCore.QObject.connect(self.table_model_parameters,QtCore.SIGNAL("cellChanged(int,int)"),self.model_parameters_changed)
self.tree_relations.itemChanged.connect(self.relation_changed)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
self.label_model_parameters.setText(QtGui.QApplication.translate("Dialog", "Model Parameters", None, QtGui.QApplication.UnicodeUTF8))
self.table_model_parameters.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("Dialog", "Name", None, QtGui.QApplication.UnicodeUTF8))
self.table_model_parameters.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("Dialog", "Value", None, QtGui.QApplication.UnicodeUTF8))
self.label_relations.setText(QtGui.QApplication.translate("Dialog", "Relations", None, QtGui.QApplication.UnicodeUTF8))
self.tree_relations.headerItem().setText(0, QtGui.QApplication.translate("Dialog", "Object Parameter", None, QtGui.QApplication.UnicodeUTF8))
self.tree_relations.headerItem().setText(1, QtGui.QApplication.translate("Dialog", "Expression", None, QtGui.QApplication.UnicodeUTF8))
self.tree_relations.headerItem().setText(2, QtGui.QApplication.translate("Dialog", "Evaluates to:", None, QtGui.QApplication.UnicodeUTF8))
def mp_name_ok(self,text):
if text == "":
return False
l_count = 0
for l in text:
if l in string.letters:
l_count = l_count+1
if l_count == 0:
QtGui.QMessageBox.critical(None,"Problem with name","Name should contain a letter")
return False
else:
return True
def mp_value_ok(self,value):
try:
value = float(value)
except ValueError:
QtGui.QMessageBox.critical(None,"Problem with value","Value should be a number")
return False
return True
def model_parameters_changed(self):
item = self.table_model_parameters.currentItem()
if item.row() == 0 and item.column() == 0: # new model parameter name, wait for value before saving
return
if item.row() == 0 and item.column() == 1: # got value, save new model parameter and add new empty row
name_item = self.table_model_parameters.item(item.row(),0)
name = str(name_item.text())
value = item.text()
if self.mp_name_ok(name) == True & self.mp_value_ok(value) == True:
value = float(value)
self.mp.Parameters[name]=value
self.refresh_mp_table()
return
if item.column() == 0: # rename or delete model parameter
name = str(item.text())
if name == "":
# delete that model parameter
# use dialog to confirm
return
elif self.mp_name_ok(name) == True:
# rename that model parameter
return
elif item.column() == 1: # update model parameter value
value = str(item.text())
if self.mp_value_ok(value) == True:
name_item = self.table_model_parameters.item(item.row(),0)
name = str(name_item.text())
self.mp.Parameters[name]=value
self.refresh_relations_tree()
return
def relation_changed(self):
item = self.tree_relations.currentItem()
expression = str(item.text(1))
if expression == "":
return
if expression.startswith("="):
expression=expression.replace("=","")
op_tuple = self.relations_tree_p_items[item]
self.mp.Relations[self.relations_tree_p_items[item]] = expression
evaluates_to = self.evaluate_expression(expression)
self.execute_expression(evaluates_to,op_tuple,item)
def by_length(self, word1, word2): # for sorting parameter names from longest to shortest to help prevent
return len(word2) - len(word1) # errors when replacing names with their values in the expression
def evaluate_expression(self,expression):
mp_names = self.mp.Parameters.keys()
mp_names.sort(cmp=self.by_length)
for mp_name in mp_names:
expression = expression.replace(mp_name,str(self.mp.Parameters[mp_name]))
evaluates_to = float(eval(expression))
return evaluates_to
def execute_expression(self,evaluates_to,op_tuple,item):
object = op_tuple[0]
property = op_tuple[1]
prefix = "FreeCAD.ActiveDocument."
if property == "Placement Base x":
axis = "x"
self.change_placement(object,axis,evaluates_to)
return
elif property == "Placement Base y":
axis = "y"
self.change_placement(object,axis,evaluates_to)
return
elif property == "Placement Base z":
axis = "z"
self.change_placement(object,axis,evaluates_to)
return
try:
exec(prefix + object.Name + "." + property + "=" + str(evaluates_to))
except:
QtGui.QMessageBox.critical(None,"Some Problem","Expression: "+expression)
raise
return
item.setText(2,QtCore.QString(str(evaluates_to)))
def refresh_mp_table(self):
self.table_model_parameters.clear()
self.mp.Parameters['hello'] = 50.0
self.mp.Parameters['peach'] = 1028.08
self.mp.Parameters['bananas'] = 43.5
row_count = len(self.mp.Parameters.items())+1
self.table_model_parameters.setRowCount(row_count)
empty_item_name = QtGui.QTableWidgetItem(QtCore.QString(""))
empty_item_value = QtGui.QTableWidgetItem(QtCore.QString(""))
self.table_model_parameters.setItem(0,0,empty_item_name)
self.table_model_parameters.setItem(0,1,empty_item_value)
for row_i,(name,value) in enumerate(self.mp.Parameters.items()):
item_name = QtGui.QTableWidgetItem(QtCore.QString(name))
item_value = QtGui.QTableWidgetItem(QtCore.QString(str(value)))
self.table_model_parameters.setItem(row_i+1,0,item_name)
self.table_model_parameters.setItem(row_i+1,1,item_value)
def refresh_relations_tree(self):
self.tree_relations.clear()
self.relations_tree_o_items.clear()
self.relations_tree_p_items.clear()
for obj in self.mp.Document.Objects:
op_name_list = []
for op_name in obj.PropertiesList:
if type(obj.getPropertyByName(op_name)) == type(float()):
op_name_list.append(op_name)
elif op_name == "Placement":
op_name_list.append("Placement Base x")
op_name_list.append("Placement Base y")
op_name_list.append("Placement Base z")
if op_name_list != []:
new_o_item = QtGui.QTreeWidgetItem(self.tree_relations)
new_o_item.setText(0,QtCore.QString(obj.Label))
new_o_item.setFlags(QtCore.Qt.ItemIsEnabled)
self.relations_tree_o_items[new_o_item] = obj
for op_name in op_name_list:
new_p_item = QtGui.QTreeWidgetItem(new_o_item)
new_p_item.setText(0,QtCore.QString(op_name))
new_p_item.setFlags(new_p_item.flags() | QtCore.Qt.ItemIsEditable)
if (obj,op_name) in self.mp.Relations:
expression = self.mp.Relations[(obj,op_name)]
new_p_item.setText(1,QtCore.QString(expression))
new_p_item.setText(2,QtCore.QString(str(self.evaluate_expression(expression))))
self.relations_tree_p_items[new_p_item] = (obj,op_name)
def change_placement(self,object,axis,value):
current_matrix = object.Placement.toMatrix()
new_matrix = current_matrix
if axis == "x":
new_matrix.A14 = value
elif axis == "y":
new_matrix.A24 = value
elif axis == "z":
new_matrix.A34 = value
object.Placement = new_matrix