Files
create/src/Mod/Material/MaterialEditor.py
2018-10-17 11:23:04 +02:00

376 lines
17 KiB
Python

#***************************************************************************
#* *
#* 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 *
#* *
#***************************************************************************
from __future__ import print_function
import FreeCAD
import FreeCADGui
from Material import getMaterialAttributeStructure
import os
from PySide import QtCore, QtGui
# from PySide import QtUiTools, QtSvg
import sys
if sys.version_info.major >= 3:
unicode = str
__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("itemChanged(QTreeWidgetItem*,int)"), self.itemChanged)
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)
# add material properties (the keys) to the editor
for group in getMaterialAttributeStructure(True): # get the mat file structure from material module, use Spaces for better ui
# print(group)
self.addPropertiesToGroup(group)
# 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 addPropertiesToGroup(self, propertygroup=None):
"Adds property to a known group in Tree widges"
if propertygroup:
groupname = propertygroup[0]
groupproperties = propertygroup[1]
else:
return
# parent
self.widget.Editor.addTopLevelItem(QtGui.QTreeWidgetItem([groupname, ]))
# how to expand it ?
# childs
for key in groupproperties:
if not self.widget.Editor.findItems(key, QtCore.Qt.MatchRecursive, 0):
top = self.widget.Editor.findItems(translate("Material", groupname), 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)
def getMaterialResources(self):
self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Material/Resources")
use_built_in_materials = self.fem_prefs.GetBool("UseBuiltInMaterials", True)
use_mat_from_config_dir = self.fem_prefs.GetBool("UseMaterialsFromConfigDir", True)
use_mat_from_custom_dir = self.fem_prefs.GetBool("UseMaterialsFromCustomDir", True)
if use_mat_from_custom_dir:
custom_mat_dir = self.fem_prefs.GetString("CustomMaterialsDir", "")
# later found cards with same name will override cards
# FreeCAD returns paths with / at the end, thus not os.sep is needed on first +
self.resources = []
if use_built_in_materials:
self.resources.append(FreeCAD.getResourceDir() + "Mod" + os.sep + "Material" + os.sep + "StandardMaterial")
if use_mat_from_config_dir:
self.resources.append(FreeCAD.ConfigGet("UserAppData") + "Material")
if use_mat_from_custom_dir:
custom_mat_dir = self.fem_prefs.GetString("CustomMaterialsDir", "")
if os.path.exists(custom_mat_dir):
self.resources.append(custom_mat_dir)
self.outputResources()
def outputResources(self):
print('locations to look for material cards:')
for path in self.resources:
print(' ' + path)
print('\n')
def outputCards(self):
print('material cards:')
for card in self.cards:
print(' ' + card + ': ' + self.cards[card])
print('\n')
def updateCards(self):
"updates the contents of the materials combo with existing material cards"
self.getMaterialResources()
self.cards = {}
for p in self.resources:
if os.path.exists(p):
for f in os.listdir(p):
b, e = os.path.splitext(f)
if e.upper() == ".FCMAT":
self.cards[b] = p + os.sep + f
# self.outputCards()
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, if material is changed in editors combo box
- a dictionary, if the editor was called with data'''
# print type(data)
if isinstance(data, dict):
self.clearEditor()
for k, i in data.items():
k = self.expandKey(k)
# most material dict keys are added with addPropertiesToGroup, see tuple with all these properties at module end
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 key not 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 itemChanged(self, item, column):
"Handles text changes"
if item.text(0) == "Section Fill Pattern":
if column == 1:
self.setTexture(item.text(1))
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 dictionary 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
matkey = self.collapseKey(str(c.text(0)))
matvalue = unicode(c.text(1))
if matvalue or (matkey == 'Name'):
# use only keys which are not empty and the name even if empty
d[matkey] = matvalue
# self.outputDict(d)
return d
# ??? after return ???
if d:
self.updateContents(d)
self.widget.Editor.topLevelItem(6).child(4).setToolTip(1, self.getPatternsList())
def outputDict(self, d):
print('MaterialEditor dictionary')
for param in d:
print(' ' + param + ' : ' + d[param])
def setTexture(self, pattern):
"displays a texture preview if needed"
self.widget.PreviewVector.hide()
if pattern:
try:
import DrawingPatterns
except:
print("DrawingPatterns not found")
else:
pattern = DrawingPatterns.buildFileSwatch(pattern, size=96, png=True)
if pattern:
self.widget.PreviewVector.setPixmap(QtGui.QPixmap(pattern))
self.widget.PreviewVector.show()
def openfile(self):
"Opens a FCMat file"
filetuple = QtGui.QFileDialog.getOpenFileName(QtGui.QApplication.activeWindow(), 'Open FreeCAD Material file', '*.FCMat')
filename = filetuple[0] # a tuple of two empty strings returns True, so use the filename directly
if filename:
self.clearEditor()
import importFCMat
d = importFCMat.read(filename)
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"
filetuple = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), 'Save FreeCAD Material file', name + '.FCMat')
filename = filetuple[0] # a tuple of two empty strings returns True, so use the filename directly
if filename:
d = self.getDict()
# self.outputDict(d)
if d:
import importFCMat
importFCMat.write(filename, d)
def show(self):
return self.widget.show()
def exec_(self):
return 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.exec_()
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