Material: allow to display Drawing Patterns in material editor - issue #2577

This commit is contained in:
Yorik van Havre 2016-06-09 22:19:45 -03:00
parent f0394c7b22
commit 319920c185
3 changed files with 144 additions and 33 deletions

View File

@ -138,9 +138,10 @@ def buildPattern(name,scale=5,thickness=1,color="#000000"):
"""buildPattern(name,scale=5,thickness=1,color="#000000") """buildPattern(name,scale=5,thickness=1,color="#000000")
builds an SVG <pattern> fragment from a name and path data""" builds an SVG <pattern> fragment from a name and path data"""
name,scale,thickness = decodeName(name,scale,thickness)
if not (name in Patterns.keys()): if not (name in Patterns.keys()):
return None return None
pname = name + "_" + str(scale) + "_" + str(thickness) pname = name + "_" + str(scale).replace(".","") + "_" + str(thickness).replace(".","")
data = Patterns[name] data = Patterns[name]
template='''<pattern id="$name" patternUnits="userSpaceOnUse" template='''<pattern id="$name" patternUnits="userSpaceOnUse"
patternTransform="matrix($scale,0,0,$scale,0,0)" x="0" y="0" width="10" height="10"> patternTransform="matrix($scale,0,0,$scale,0,0)" x="0" y="0" width="10" height="10">
@ -155,25 +156,74 @@ def buildPattern(name,scale=5,thickness=1,color="#000000"):
return t return t
def buildTextureImage(name,scale,thickness,color="#000000",size=64): def buildTextureImage(name,scale=5,thickness=1,color="#000000",size=64):
"""buildTextureImage(name,scale,thickness,color="#000000",size=64) """buildTextureImage(name,scale,thickness,color="#000000",size=64)
builds a 64x64 SVG image filled with the given texture""" builds a 64x64 SVG image filled with the given texture"""
name,scale,thickness = decodeName(name,scale,thickness)
if not (name in Patterns.keys()):
return None
s = str(size) s = str(size)
template = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" template = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="'''+s+'''" height="'''+s+'''"><defs>$pattern</defs><rect x="0" width="'''+s+'''" height="'''+s+'''"><defs>$pattern</defs><rect x="0"
y="$0" width="'''+s+'''" height="'''+s+'''" style="fill:url(#$name); y="$0" width="'''+s+'''" height="'''+s+'''" style="fill:url(#$name);
stroke:none; stroke-width:none"/></svg>''' stroke:none; stroke-width:none"/></svg>'''
if not (name in Patterns.keys()):
return None
pat = buildPattern(name,scale,thickness,color) pat = buildPattern(name,scale,thickness,color)
t = template.replace("\n","") t = template.replace("\n","")
t = t.replace("$pattern",pat+"\n") t = t.replace("$pattern",pat+"\n")
t = t.replace("$name",name+"_"+str(scale)+"_"+str(thickness)) t = t.replace("$name",name+"_"+str(scale).replace(".","")+"_"+str(thickness).replace(".",""))
return t return t
def buildSwatch(name,scale=5,thickness=1,color="#000000",size=64):
"""buildSwatch(name,scale,thickness,color="#000000",size=64)
builds a 64x64 SVG image filled with the given texture, a
white background and a border, to serve as a sample"""
name,scale,thickness = decodeName(name,scale,thickness)
if not (name in Patterns.keys()):
return None
s = str(size)
template = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="'''+s+'''" height="'''+s+'''"><defs>$pattern</defs><rect x="0"
y="$0" width="'''+s+'''" height="'''+s+'''" style="fill:#FFFFFF;
stroke:none; stroke-width:none"/><rect x="0" y="$0" width="'''+s+'''"
height="'''+s+'''" style="fill:url(#$name); stroke:#000000;
stroke-width:2"/></svg>'''
pat = buildPattern(name,scale,thickness,color)
t = template.replace("\n","")
t = t.replace("$pattern",pat+"\n")
t = t.replace("$name",name+"_"+str(scale).replace(".","")+"_"+str(thickness).replace(".",""))
return t
def buildFileSwatch(name,scale=5,thickness=1,color="#000000",size=64,png=False):
"""buildFileSwatch(name,scale,thickness,color="#000000",size=64,png=False)
builds a 64x64 SVG image filled with the given texture, a
white background and a border, to serve as a sample. The image
is saved as a temp file, the filepath is returned"""
s = buildSwatch(name,scale,thickness,color,size)
if s:
import tempfile
tf = tempfile.mkstemp(suffix=".svg")[1]
f = open(tf,"wb")
f.write(s)
f.close()
if png:
# we use imagemagick's convert because Qt4 doesn't support SVG patterns...
import os
if os.system("convert -version") == 0:
ptf = os.path.splitext(tf)[0]+".png"
os.system('convert "'+tf+'" "'+ptf+'"')
return ptf
else:
return tf
return None
def saveTestImage(filename,scales=[2.5,5],thicknesses=[0.1,0.2,1]): def saveTestImage(filename,scales=[2.5,5],thicknesses=[0.1,0.2,1]):
"""saveTestImage(filename,scales=[2.5,5],thicknesses=[0.1,0.2,1]) """saveTestImage(filename,scales=[2.5,5],thicknesses=[0.1,0.2,1])
@ -212,6 +262,24 @@ def saveTestImage(filename,scales=[2.5,5],thicknesses=[0.1,0.2,1]):
f = open(filename,"wb") f = open(filename,"wb")
f.write(t) f.write(t)
f.close() f.close()
def decodeName(name,scale,thickness):
"""decodeName(name,scale,thickness) : decodes names written in the form 'name_5_1'"""
name = name.split("_")
if len(name) > 1:
try:
scale = float(name[1])
except:
pass
if len(name) > 2:
try:
thickness = float(name[2])
except:
pass
return name[0],scale,thickness
def getPatternNames(): def getPatternNames():

View File

@ -21,7 +21,7 @@
#*************************************************************************** #***************************************************************************
import FreeCAD, FreeCADGui, os import FreeCAD, FreeCADGui, os
from PySide import QtCore, QtGui, QtUiTools from PySide import QtCore, QtGui, QtUiTools, QtSvg
__title__="FreeCAD material editor" __title__="FreeCAD material editor"
__author__ = "Yorik van Havre" __author__ = "Yorik van Havre"
@ -30,6 +30,7 @@ __url__ = "http://www.freecadweb.org"
class MaterialEditor: class MaterialEditor:
def __init__(self, obj = None, prop = None, material = None): def __init__(self, obj = None, prop = None, material = None):
"""Initializes, optionally with an object name and a material property name to edit, or directly """Initializes, optionally with an object name and a material property name to edit, or directly
with a material dictionary.""" with a material dictionary."""
@ -58,6 +59,7 @@ class MaterialEditor:
QtCore.QObject.connect(self.widget.EditProperty, QtCore.SIGNAL("returnPressed()"), 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.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("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.Editor, QtCore.SIGNAL("currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)"), self.checkDeletable)
QtCore.QObject.connect(self.widget.ButtonOpen, QtCore.SIGNAL("clicked()"), self.openfile) QtCore.QObject.connect(self.widget.ButtonOpen, QtCore.SIGNAL("clicked()"), self.openfile)
QtCore.QObject.connect(self.widget.ButtonSave, QtCore.SIGNAL("clicked()"), self.savefile) QtCore.QObject.connect(self.widget.ButtonSave, QtCore.SIGNAL("clicked()"), self.savefile)
@ -67,8 +69,7 @@ class MaterialEditor:
d = FreeCAD.ActiveDocument.getObject(self.obj).getPropertyByName(self.prop) d = FreeCAD.ActiveDocument.getObject(self.obj).getPropertyByName(self.prop)
elif self.material: elif self.material:
d = self.material d = self.material
if d:
self.updateContents(d)
def updateCards(self): def updateCards(self):
"updates the contents of the materials combo with existing material cards" "updates the contents of the materials combo with existing material cards"
@ -90,6 +91,7 @@ class MaterialEditor:
for k,i in self.cards.items(): for k,i in self.cards.items():
self.widget.ComboMaterial.addItem(k) self.widget.ComboMaterial.addItem(k)
def updateContents(self,data): def updateContents(self,data):
"updates the contents of the editor with the given data (can be the name of a card or a dictionary)" "updates the contents of the editor with the given data (can be the name of a card or a dictionary)"
#print type(data) #print type(data)
@ -112,12 +114,14 @@ class MaterialEditor:
if d: if d:
self.updateContents(d) self.updateContents(d)
def openProductURL(self): def openProductURL(self):
"opens the contents of the ProductURL field in an external browser" "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)) url = str(self.widget.Editor.findItems(translate("Material","Product URL"),QtCore.Qt.MatchRecursive,0)[0].text(1))
if url: if url:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.TolerantMode)) QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.TolerantMode))
def accept(self): def accept(self):
"if we are editing a property, set the property values" "if we are editing a property, set the property values"
if self.prop and self.obj: if self.prop and self.obj:
@ -125,10 +129,12 @@ class MaterialEditor:
o = FreeCAD.ActiveDocument.getObject(self.obj) o = FreeCAD.ActiveDocument.getObject(self.obj)
setattr(o,self.prop,d) setattr(o,self.prop,d)
QtGui.QDialog.accept(self.widget) QtGui.QDialog.accept(self.widget)
def reject(self): def reject(self):
QtGui.QDialog.reject(self.widget) QtGui.QDialog.reject(self.widget)
def expandKey(self, key): def expandKey(self, key):
"adds spaces before caps in a KeyName" "adds spaces before caps in a KeyName"
nk = "" nk = ""
@ -141,6 +147,7 @@ class MaterialEditor:
nk += l nk += l
return nk return nk
def collapseKey(self, key): def collapseKey(self, key):
"removes the spaces in a Key Name" "removes the spaces in a Key Name"
nk = "" nk = ""
@ -148,7 +155,8 @@ class MaterialEditor:
if l != " ": if l != " ":
nk += l nk += l
return nk return nk
def clearEditor(self): def clearEditor(self):
"Clears the contents of the editor" "Clears the contents of the editor"
for i1 in range(self.widget.Editor.topLevelItemCount()): for i1 in range(self.widget.Editor.topLevelItemCount()):
@ -158,7 +166,8 @@ class MaterialEditor:
c.setText(1,"") c.setText(1,"")
for k in self.customprops: for k in self.customprops:
self.deleteCustomProperty(k) self.deleteCustomProperty(k)
def addCustomProperty(self, key = None, value = None): def addCustomProperty(self, key = None, value = None):
"Adds a custom property to the editor, optionally with a value" "Adds a custom property to the editor, optionally with a value"
if not key: if not key:
@ -175,7 +184,8 @@ class MaterialEditor:
self.widget.EditProperty.setText("") self.widget.EditProperty.setText("")
if value: if value:
i.setText(1,value) i.setText(1,value)
def deleteCustomProperty(self, key = None): def deleteCustomProperty(self, key = None):
"Deletes a custom property from the editor" "Deletes a custom property from the editor"
if not key: if not key:
@ -191,19 +201,29 @@ class MaterialEditor:
if ii >= 0: if ii >= 0:
top.takeChild(ii) top.takeChild(ii)
self.customprops.remove(key) self.customprops.remove(key)
def itemClicked(self, item, column): def itemClicked(self, item, column):
"Edits an item if it is not in the first column" "Edits an item if it is not in the first column"
if column > 0: if column > 0:
self.widget.Editor.editItem(item, column) 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): def checkDeletable(self,current,previous):
"Checks if the current item is a custom property, if yes enable the delete button" "Checks if the current item is a custom property, if yes enable the delete button"
if str(current.text(0)) in self.customprops: if str(current.text(0)) in self.customprops:
self.widget.ButtonDeleteProperty.setEnabled(True) self.widget.ButtonDeleteProperty.setEnabled(True)
else: else:
self.widget.ButtonDeleteProperty.setEnabled(False) self.widget.ButtonDeleteProperty.setEnabled(False)
def getDict(self): def getDict(self):
"returns a dictionnary from the contents of the editor" "returns a dictionnary from the contents of the editor"
d = {} d = {}
@ -214,7 +234,28 @@ class MaterialEditor:
# TODO the following should be translated back to english,since text(0) could be translated # 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)) d[self.collapseKey(str(c.text(0)))] = unicode(c.text(1))
return d return d
if d:
self.updateContents(d)
self.widget.Editor.topLevelItem(6).child(4).setToolTip(1,self.getPatternsList())
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): def openfile(self):
"Opens a FCMat file" "Opens a FCMat file"
filename = QtGui.QFileDialog.getOpenFileName(QtGui.qApp.activeWindow(),'Open FreeCAD Material file','*.FCMat') filename = QtGui.QFileDialog.getOpenFileName(QtGui.qApp.activeWindow(),'Open FreeCAD Material file','*.FCMat')
@ -224,7 +265,8 @@ class MaterialEditor:
d = importFCMat.read(filename[0]) d = importFCMat.read(filename[0])
if d: if d:
self.updateContents(d) self.updateContents(d)
def savefile(self): def savefile(self):
"Saves a FCMat file" "Saves a FCMat file"
name = str(self.widget.Editor.findItems(translate("Material","Name"),QtCore.Qt.MatchRecursive,0)[0].text(1)) name = str(self.widget.Editor.findItems(translate("Material","Name"),QtCore.Qt.MatchRecursive,0)[0].text(1))
@ -237,22 +279,29 @@ class MaterialEditor:
import importFCMat import importFCMat
importFCMat.write(filename,d) importFCMat.write(filename,d)
def show(self): def show(self):
return self.widget.show() return self.widget.show()
def exec_(self): def exec_(self):
return self.widget.exec_() return self.widget.exec_()
def translate(context,text): def translate(context,text):
"translates text" "translates text"
return text #TODO use Qt translation mechanism here return text #TODO use Qt translation mechanism here
def openEditor(obj = None, prop = None): def openEditor(obj = None, prop = None):
"""openEditor([obj,prop]): opens the editor, optionally with """openEditor([obj,prop]): opens the editor, optionally with
an object name and material property name to edit""" an object name and material property name to edit"""
editor = MaterialEditor(obj,prop) editor = MaterialEditor(obj,prop)
editor.show() editor.show()
def editMaterial(material): def editMaterial(material):
"""editMaterial(material): opens the editor to edit the contents """editMaterial(material): opens the editor to edit the contents
of the given material dictionary. Returns the modified material.""" of the given material dictionary. Returns the modified material."""

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>MaterialEditor</class> <class>MaterialEditor</class>
<widget class="QDialog" name="MaterialEditor"> <widget class="QDialog" name="MaterialEditor">
@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>450</width> <width>441</width>
<height>604</height> <height>604</height>
</rect> </rect>
</property> </property>
@ -109,34 +109,28 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QGraphicsView" name="PreviewRendered"> <widget class="QLabel" name="PreviewRender">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>96</width> <width>96</width>
<height>96</height> <height>96</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="text">
<size> <string/>
<width>96</width>
<height>96</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGraphicsView" name="PreviewVector"> <widget class="QLabel" name="PreviewVector">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>96</width> <width>96</width>
<height>96</height> <height>96</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="text">
<size> <string/>
<width>96</width>
<height>96</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>