/
MaterialEditor.py
265 lines (239 loc) · 12.2 KB
/
MaterialEditor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
#***************************************************************************
#* *
#* Copyright (c) 2013 - Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD, FreeCADGui, os
from PySide import QtCore, QtGui, QtUiTools
__title__="FreeCAD material editor"
__author__ = "Yorik van Havre"
__url__ = "http://www.freecadweb.org"
class MaterialEditor:
def __init__(self, obj = None, prop = None, material = None):
"""Initializes, optionally with an object name and a material property name to edit, or directly
with a material dictionary."""
self.obj = obj
self.prop = prop
self.material = material
self.customprops = []
# load the UI file from the same directory as this script
self.widget = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__)+os.sep+"materials-editor.ui")
# additional UI fixes and tweaks
self.widget.ButtonURL.setIcon(QtGui.QIcon(":/icons/internet-web-browser.svg"))
self.widget.ButtonDeleteProperty.setEnabled(False)
self.widget.standardButtons.button(QtGui.QDialogButtonBox.Ok).setAutoDefault(False)
self.widget.standardButtons.button(QtGui.QDialogButtonBox.Cancel).setAutoDefault(False)
self.updateCards()
self.widget.Editor.header().resizeSection(0,200)
self.widget.Editor.expandAll()
self.widget.Editor.setFocus()
# TODO allow to enter a custom property by pressing Enter in the lineedit (currently closes the dialog)
self.widget.Editor.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
QtCore.QObject.connect(self.widget.ComboMaterial, QtCore.SIGNAL("currentIndexChanged(QString)"), self.updateContents)
QtCore.QObject.connect(self.widget.ButtonURL, QtCore.SIGNAL("clicked()"), self.openProductURL)
QtCore.QObject.connect(self.widget.standardButtons, QtCore.SIGNAL("accepted()"), self.accept)
QtCore.QObject.connect(self.widget.standardButtons, QtCore.SIGNAL("rejected()"), self.reject)
QtCore.QObject.connect(self.widget.ButtonAddProperty, QtCore.SIGNAL("clicked()"), self.addCustomProperty)
QtCore.QObject.connect(self.widget.EditProperty, QtCore.SIGNAL("returnPressed()"), self.addCustomProperty)
QtCore.QObject.connect(self.widget.ButtonDeleteProperty, QtCore.SIGNAL("clicked()"), self.deleteCustomProperty)
QtCore.QObject.connect(self.widget.Editor, QtCore.SIGNAL("itemDoubleClicked(QTreeWidgetItem*,int)"), self.itemClicked)
QtCore.QObject.connect(self.widget.Editor, QtCore.SIGNAL("currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)"), self.checkDeletable)
QtCore.QObject.connect(self.widget.ButtonOpen, QtCore.SIGNAL("clicked()"), self.openfile)
QtCore.QObject.connect(self.widget.ButtonSave, QtCore.SIGNAL("clicked()"), self.savefile)
# update the editor with the contents of the property, if we have one
d = None
if self.prop and self.obj:
d = FreeCAD.ActiveDocument.getObject(self.obj).getPropertyByName(self.prop)
elif self.material:
d = self.material
if d:
self.updateContents(d)
def updateCards(self):
"updates the contents of the materials combo with existing material cards"
# look for cards in both resources dir and a Materials sub-folder in the user folder.
# User cards with same name will override system cards
paths = [FreeCAD.getResourceDir() + os.sep + "Mod" + os.sep + "Material" + os.sep + "StandardMaterial"]
ap = FreeCAD.ConfigGet("UserAppData") + os.sep + "Materials"
if os.path.exists(ap):
paths.append(ap)
self.cards = {}
for p in paths:
for f in os.listdir(p):
b,e = os.path.splitext(f)
if e.upper() == ".FCMAT":
self.cards[b] = p + os.sep + f
if self.cards:
self.widget.ComboMaterial.clear()
self.widget.ComboMaterial.addItem("") # add a blank item first
for k,i in self.cards.items():
self.widget.ComboMaterial.addItem(k)
def updateContents(self,data):
"updates the contents of the editor with the given data (can be the name of a card or a dictionary)"
#print type(data)
if isinstance(data,dict):
self.clearEditor()
for k,i in data.items():
k = self.expandKey(k)
slot = self.widget.Editor.findItems(k,QtCore.Qt.MatchRecursive,0)
if len(slot) == 1:
slot = slot[0]
slot.setText(1,i)
else:
self.addCustomProperty(k,i)
elif isinstance(data,unicode):
k = str(data)
if k:
if k in self.cards:
import importFCMat
d = importFCMat.read(self.cards[k])
if d:
self.updateContents(d)
def openProductURL(self):
"opens the contents of the ProductURL field in an external browser"
url = str(self.widget.Editor.findItems(translate("Material","Product URL"),QtCore.Qt.MatchRecursive,0)[0].text(1))
if url:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.TolerantMode))
def accept(self):
"if we are editing a property, set the property values"
if self.prop and self.obj:
d = self.getDict()
o = FreeCAD.ActiveDocument.getObject(self.obj)
setattr(o,self.prop,d)
QtGui.QDialog.accept(self.widget)
def reject(self):
QtGui.QDialog.reject(self.widget)
def expandKey(self, key):
"adds spaces before caps in a KeyName"
nk = ""
for l in key:
if l.isupper():
if nk:
# this allows for series of caps, such as ProductURL
if not nk[-1].isupper():
nk += " "
nk += l
return nk
def collapseKey(self, key):
"removes the spaces in a Key Name"
nk = ""
for l in key:
if l != " ":
nk += l
return nk
def clearEditor(self):
"Clears the contents of the editor"
for i1 in range(self.widget.Editor.topLevelItemCount()):
w = self.widget.Editor.topLevelItem(i1)
for i2 in range(w.childCount()):
c = w.child(i2)
c.setText(1,"")
for k in self.customprops:
self.deleteCustomProperty(k)
def addCustomProperty(self, key = None, value = None):
"Adds a custom property to the editor, optionally with a value"
if not key:
key = str(self.widget.EditProperty.text())
if key:
if not key in self.customprops:
if not self.widget.Editor.findItems(key,QtCore.Qt.MatchRecursive,0):
top = self.widget.Editor.findItems(translate("Material","User defined"),QtCore.Qt.MatchExactly,0)
if top:
i = QtGui.QTreeWidgetItem(top[0])
i.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEditable|QtCore.Qt.ItemIsDragEnabled|QtCore.Qt.ItemIsUserCheckable|QtCore.Qt.ItemIsEnabled)
i.setText(0,key)
self.customprops.append(key)
self.widget.EditProperty.setText("")
if value:
i.setText(1,value)
def deleteCustomProperty(self, key = None):
"Deletes a custom property from the editor"
if not key:
key = str(self.widget.Editor.currentItem().text(0))
if key:
if key in self.customprops:
i = self.widget.Editor.findItems(key,QtCore.Qt.MatchRecursive,0)
if i:
top = self.widget.Editor.findItems(translate("Material","User defined"),QtCore.Qt.MatchExactly,0)
if top:
top = top[0]
ii = top.indexOfChild(i[0])
if ii >= 0:
top.takeChild(ii)
self.customprops.remove(key)
def itemClicked(self, item, column):
"Edits an item if it is not in the first column"
if column > 0:
self.widget.Editor.editItem(item, column)
def checkDeletable(self,current,previous):
"Checks if the current item is a custom property, if yes enable the delete button"
if str(current.text(0)) in self.customprops:
self.widget.ButtonDeleteProperty.setEnabled(True)
else:
self.widget.ButtonDeleteProperty.setEnabled(False)
def getDict(self):
"returns a dictionnary from the contents of the editor"
d = {}
for i1 in range(self.widget.Editor.topLevelItemCount()):
w = self.widget.Editor.topLevelItem(i1)
for i2 in range(w.childCount()):
c = w.child(i2)
# TODO the following should be translated back to english,since text(0) could be translated
d[self.collapseKey(str(c.text(0)))] = unicode(c.text(1))
return d
def openfile(self):
"Opens a FCMat file"
filename = QtGui.QFileDialog.getOpenFileName(QtGui.qApp.activeWindow(),'Open FreeCAD Material file','*.FCMat')
if filename:
self.clearEditor()
import importFCMat
d = importFCMat.read(filename[0])
if d:
self.updateContents(d)
def savefile(self):
"Saves a FCMat file"
name = str(self.widget.Editor.findItems(translate("Material","Name"),QtCore.Qt.MatchRecursive,0)[0].text(1))
if not name:
name = "Material"
filename = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.activeWindow(),'Save FreeCAD Material file',name+'.FCMat')
if filename:
d = self.getDict()
if d:
import importFCMat
importFCMat.write(filename,d)
def show(self):
self.widget.show()
def exec_(self):
self.widget.exec_()
def translate(context,text):
"translates text"
return text #TODO use Qt translation mechanism here
def openEditor(obj = None, prop = None):
"""openEditor([obj,prop]): opens the editor, optionally with
an object name and material property name to edit"""
editor = MaterialEditor(obj,prop)
editor.show()
def editMaterial(material):
"""editMaterial(material): opens the editor to edit the contents
of the given material dictionary. Returns the modified material."""
editor = MaterialEditor(material=material)
result = editor.exec_()
if result:
return editor.getDict()
else:
return material