Arch: Misc improvements to Schedule tool

This commit is contained in:
Yorik van Havre
2019-07-17 18:00:29 -03:00
parent 42a5eb138e
commit daa0a2fa9c
3 changed files with 404 additions and 103 deletions

View File

@@ -765,7 +765,7 @@ def pruneIncluded(objectslist,strict=False):
if toplevel:
newlist.append(obj)
else:
FreeCAD.Console.PrintLog("pruning "+obj.Label+"n")
FreeCAD.Console.PrintLog("pruning "+obj.Label+"\n")
return newlist
def getAllChildren(objectlist):

View File

@@ -36,7 +36,7 @@ else:
def QT_TRANSLATE_NOOP(ctxt,txt):
return txt
# \endcond
## @package ArchSchedule
# \ingroup ARCH
# \brief The Schedule object and tools
@@ -53,7 +53,8 @@ __url__ = "http://www.freecadweb.org"
verbose = True # change this for silent recomputes
class _CommandArchSchedule:
class CommandArchSchedule:
"the Arch Schedule command definition"
@@ -63,7 +64,7 @@ class _CommandArchSchedule:
'ToolTip': QT_TRANSLATE_NOOP("Arch_Schedule","Creates a schedule to collect data from the model")}
def Activated(self):
taskd = _ArchScheduleTaskPanel()
taskd = ArchScheduleTaskPanel()
FreeCADGui.Control.showDialog(taskd)
def IsActive(self):
@@ -73,53 +74,114 @@ class _CommandArchSchedule:
return False
class _ArchSchedule:
"the Arch Schedule object"
def __init__(self,obj):
obj.addProperty("App::PropertyStringList","Description","Arch",QT_TRANSLATE_NOOP("App::Property","The description column"))
obj.addProperty("App::PropertyStringList","Value", "Arch",QT_TRANSLATE_NOOP("App::Property","The values column"))
obj.addProperty("App::PropertyStringList","Unit", "Arch",QT_TRANSLATE_NOOP("App::Property","The units column"))
obj.addProperty("App::PropertyStringList","Objects", "Arch",QT_TRANSLATE_NOOP("App::Property","The objects column"))
obj.addProperty("App::PropertyStringList","Filter", "Arch",QT_TRANSLATE_NOOP("App::Property","The filter column"))
obj.addProperty("App::PropertyLink", "Result", "Arch",QT_TRANSLATE_NOOP("App::Property","The spreadsheet to print the results to"))
self.setProperties(obj)
obj.Proxy = self
self.Type = "Schedule"
def onDocumentRestored(self,obj):
self.setProperties(obj)
def setProperties(self,obj):
if not "Description" in obj.PropertiesList:
obj.addProperty("App::PropertyStringList","Description", "Arch",QT_TRANSLATE_NOOP("App::Property","The description column"))
if not "Value" in obj.PropertiesList:
obj.addProperty("App::PropertyStringList","Value", "Arch",QT_TRANSLATE_NOOP("App::Property","The values column"))
if not "Unit" in obj.PropertiesList:
obj.addProperty("App::PropertyStringList","Unit", "Arch",QT_TRANSLATE_NOOP("App::Property","The units column"))
if not "Objects" in obj.PropertiesList:
obj.addProperty("App::PropertyStringList","Objects", "Arch",QT_TRANSLATE_NOOP("App::Property","The objects column"))
if not "Filter" in obj.PropertiesList:
obj.addProperty("App::PropertyStringList","Filter", "Arch",QT_TRANSLATE_NOOP("App::Property","The filter column"))
if not "CreateSpreadsheet" in obj.PropertiesList:
obj.addProperty("App::PropertyBool", "CreateSpreadsheet", "Arch",QT_TRANSLATE_NOOP("App::Property","If True, a spreadsheet containing the results is recreated when needed"))
if not "Result" in obj.PropertiesList:
obj.addProperty("App::PropertyLink", "Result", "Arch",QT_TRANSLATE_NOOP("App::Property","The spreadsheet to print the results to"))
if not "DetailedResults" in obj.PropertiesList:
obj.addProperty("App::PropertyBool", "DetailedResults", "Arch",QT_TRANSLATE_NOOP("App::Property","If True, additional lines with each individual object are added to the results"))
def onChanged(self,obj,prop):
if (prop == "CreateSpreadsheet"):
if hasattr(obj,"CreateSpreadsheet") and obj.CreateSpreadsheet:
if not obj.Result:
import Spreadsheet
sp = FreeCAD.ActiveDocument.addObject("Spreadsheet::Sheet","Result")
obj.Result = sp
def setSpreadsheetData(self,obj):
"""Fills a spreadsheet with the stored data"""
if not hasattr(self,"data"):
return
if not self.data:
return
if not obj.Result:
if obj.CreateSpreadsheet:
import Spreadsheet
sp = FreeCAD.ActiveDocument.addObject("Spreadsheet::Sheet","Result")
obj.Result = sp
else:
return
# clear spreadsheet
obj.Result.clearAll()
# set headers
obj.Result.set("A1","Description")
obj.Result.set("B1","Value")
obj.Result.set("C1","Unit")
obj.Result.setStyle('A1:C1', 'bold', 'add')
# write contents
for k,v in self.data.items():
obj.Result.set(k,v)
# recompute
obj.Result.recompute()
def execute(self,obj):
# fills columns A, B and C of the spreadsheet
# verify the data
if not obj.Description:
# empty description column
return
for p in [obj.Value,obj.Unit,obj.Objects,obj.Filter]:
# different number of items in each column
if len(obj.Description) != len(p):
return
if not hasattr(obj,"Result"):
# silently fail on old schedule objects
return
if not obj.Result:
FreeCAD.Console.PrintError(translate("Arch","No spreadsheet attached to this schedule")+"\n")
return
obj.Result.clearAll()
obj.Result.set("A1","Description")
obj.Result.set("B1","Value")
obj.Result.set("C1","Unit")
obj.Result.setStyle('A1:C1', 'bold', 'add')
self.data = {} # store all results in self.data, so it lives even without spreadsheet
li = 1 # row index - starts at 2 to leave 2 blank rows for the title
for i in range(len(obj.Description)):
li += 1
if not obj.Description[i]:
# blank line
continue
# write description
if sys.version_info.major >= 3:
# use unicode for python3
obj.Result.set("A"+str(i+2), obj.Description[i])
self.data["A"+str(li)] = obj.Description[i]
else:
obj.Result.set("A"+str(i+2), obj.Description[i].encode("utf8"))
self.data["A"+str(li)] = obj.Description[i].encode("utf8")
if verbose:
l= "OPERATION: "+obj.Description[i]
print("")
print (l)
print (len(l)*"=")
# get list of objects
# build set of valid objects
objs = obj.Objects[i]
val = obj.Value[i]
if val:
@@ -134,7 +196,7 @@ class _ArchSchedule:
# remove object itself if the object is a group
if objs[0].isDerivedFrom("App::DocumentObjectGroup"):
objs = objs[0].Group
objs = Draft.getGroupContents(objs,walls=True,addgroups=True)
objs = Draft.getGroupContents(objs)
objs = Arch.pruneIncluded(objs,strict=True)
# remove the schedule object and its result from the list
objs = [o for o in objs if not o == obj]
@@ -159,59 +221,48 @@ class _ArchSchedule:
if args[1].upper() in o.Label.upper():
ok = False
elif args[0].upper() == "TYPE":
if Draft.getType(o).upper() != args[1].upper():
ok = False
elif args[0].upper() == "!TYPE":
if Draft.getType(o).upper() == args[1].upper():
ok = False
elif args[0].upper() == "IFCTYPE":
if hasattr(o,"IfcType"):
if o.IfcType.upper() != args[1].upper():
ok = False
else:
ok = False
elif args[0].upper() == "!IFCTYPE":
elif args[0].upper() == "!TYPE":
if hasattr(o,"IfcType"):
if o.IfcType.upper() == args[1].upper():
ok = False
if ok:
nobjs.append(o)
objs = nobjs
# perform operation
# perform operation: count or retrieve property
if val.upper() == "COUNT":
val = len(objs)
if verbose:
print (val, ",".join([o.Label for o in objs]))
obj.Result.set("B"+str(i+2),str(val))
self.data["B"+str(li)] = str(val)
if obj.DetailedResults:
# additional blank line...
li += 1
self.data["A"+str(li)] = " "
else:
vals = val.split(".")
if vals[0][0].islower():
# old-style: first member is not a property
vals = vals[1:]
sumval = 0
for o in objs:
if verbose:
l = o.Name+" ("+o.Label+"):"
print (l+(40-len(l))*" ",)
try:
d = o
for v in vals[1:]:
d = getattr(d,v)
if verbose:
print (d)
if hasattr(d,"Value"):
d = d.Value
except:
FreeCAD.Console.PrintWarning(translate("Arch","Unable to retrieve value from object")+": "+o.Name+"."+".".join(vals)+"\n")
else:
if not sumval:
sumval = d
else:
sumval += d
val = sumval
# get unit
tp = None
unit = None
q = None
if obj.Unit[i]:
ustr = obj.Unit[i]
unit = obj.Unit[i]
if sys.version_info.major < 3:
ustr = ustr.encode("utf8")
unit = ustr.replace("²","^2")
unit = unit.encode("utf8")
unit = unit.replace("2","^2")
unit = unit.replace("3","^3")
unit = unit.replace("²","^2")
unit = unit.replace("³","^3")
if "2" in unit:
tp = FreeCAD.Units.Area
@@ -221,19 +272,74 @@ class _ArchSchedule:
tp = FreeCAD.Units.Angle
else:
tp = FreeCAD.Units.Length
# format value
dv = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("Decimals",2)
fs = "{:."+str(dv)+"f}" # format string
for o in objs:
if verbose:
l = o.Name+" ("+o.Label+"):"
print (l+(40-len(l))*" ",end="")
try:
d = o
for v in vals:
d = getattr(d,v)
if hasattr(d,"Value"):
d = d.Value
if verbose:
if tp and unit:
v = fs.format(FreeCAD.Units.Quantity(d,tp).getValueAs(unit).Value)
print(v,unit)
else:
print(fs.format(d))
if obj.DetailedResults:
li += 1
self.data["A"+str(li)] = o.Name+" ("+o.Label+")"
if tp and unit:
q = FreeCAD.Units.Quantity(d,tp)
self.data["B"+str(li)] = str(q.getValueAs(unit).Value)
self.data["C"+str(li)] = unit
else:
self.data["B"+str(li)] = str(d)
except:
FreeCAD.Console.PrintWarning(translate("Arch","Unable to retrieve value from object")+": "+o.Name+"."+".".join(vals)+"\n")
else:
if not sumval:
sumval = d
else:
sumval += d
val = sumval
if tp:
q = FreeCAD.Units.Quantity(val,tp)
obj.Result.set("B"+str(i+2),str(q.getValueAs(unit).Value))
obj.Result.set("C"+str(i+2),ustr)
# write data
if obj.DetailedResults:
li += 1
self.data["A"+str(li)] = "TOTAL"
if q and unit:
self.data["B"+str(li)] = str(q.getValueAs(unit).Value)
self.data["C"+str(li)] = unit
else:
obj.Result.set("B"+str(i+2),str(val))
self.data["B"+str(li)] = str(val)
if obj.DetailedResults:
# additional blank line...
li += 1
self.data["A"+str(li)] = " "
if verbose:
print ("TOTAL:"+34*" "+str(val))
obj.Result.recompute()
if tp and unit:
v = fs.format(FreeCAD.Units.Quantity(val,tp).getValueAs(unit).Value)
print("TOTAL:"+34*" "+v+" "+unit)
else:
v = fs.format(val)
print("TOTAL:"+34*" "+v)
self.setSpreadsheetData(obj)
def __getstate__(self):
return self.Type
def __setstate__(self,state):
if state:
self.Type = state
@@ -249,16 +355,19 @@ class _ViewProviderArchSchedule:
import Arch_rc
return ":/icons/Arch_Schedule.svg"
def isShow(self):
return True
def attach(self, vobj):
self.Object = vobj.Object
def setEdit(self,vobj,mode):
taskd = _ArchScheduleTaskPanel(vobj.Object)
taskd = ArchScheduleTaskPanel(vobj.Object)
FreeCADGui.Control.showDialog(taskd)
return True
def doubleClicked(self,vobj):
taskd = _ArchScheduleTaskPanel(vobj.Object)
taskd = ArchScheduleTaskPanel(vobj.Object)
FreeCADGui.Control.showDialog(taskd)
return True
@@ -286,19 +395,43 @@ class _ViewProviderArchSchedule:
return mode
class _ArchScheduleTaskPanel:
class ArchScheduleTaskPanel:
'''The editmode TaskPanel for Schedules'''
def __init__(self,obj=None):
"""Sets the panel up"""
self.obj = obj
self.form = FreeCADGui.PySideUic.loadUi(":/ui/ArchSchedule.ui")
self.form.setWindowIcon(QtGui.QIcon(":/icons/Arch_Schedule.svg"))
# set icons
self.form.buttonAdd.setIcon(QtGui.QIcon(":/icons/list-add.svg"))
self.form.buttonDel.setIcon(QtGui.QIcon(":/icons/list-remove.svg"))
self.form.buttonClear.setIcon(QtGui.QIcon(":/icons/delete.svg"))
self.form.buttonImport.setIcon(QtGui.QIcon(":/icons/document-open.svg"))
self.form.buttonExport.setIcon(QtGui.QIcon(":/icons/document-save.svg"))
self.form.buttonSelect.setIcon(QtGui.QIcon(":/icons/edit-select-all.svg"))
# restore widths
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
self.form.list.setColumnWidth(0,p.GetInt("ScheduleColumnWidth0",100))
self.form.list.setColumnWidth(1,p.GetInt("ScheduleColumnWidth1",100))
self.form.list.setColumnWidth(2,p.GetInt("ScheduleColumnWidth2",50))
self.form.list.setColumnWidth(3,p.GetInt("ScheduleColumnWidth3",100))
# set delegate - Not using custom delegates for now...
#self.form.list.setItemDelegate(ScheduleDelegate())
#self.form.list.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked)
# connect slots
QtCore.QObject.connect(self.form.buttonAdd, QtCore.SIGNAL("clicked()"), self.add)
QtCore.QObject.connect(self.form.buttonDel, QtCore.SIGNAL("clicked()"), self.remove)
QtCore.QObject.connect(self.form.buttonClear, QtCore.SIGNAL("clicked()"), self.clear)
QtCore.QObject.connect(self.form.buttonImport, QtCore.SIGNAL("clicked()"), self.importCSV)
QtCore.QObject.connect(self.form.buttonExport, QtCore.SIGNAL("clicked()"), self.exportCSV)
QtCore.QObject.connect(self.form.buttonExport, QtCore.SIGNAL("clicked()"), self.export)
QtCore.QObject.connect(self.form.buttonSelect, QtCore.SIGNAL("clicked()"), self.select)
self.form.list.clearContents()
@@ -313,56 +446,146 @@ class _ArchScheduleTaskPanel:
for j in range(len(obj.Description)):
item = QtGui.QTableWidgetItem([obj.Description,obj.Value,obj.Unit,obj.Objects,obj.Filter][i][j])
self.form.list.setItem(j,i,item)
self.form.lineEditName.setText(self.obj.Label)
self.form.checkDetailed.setChecked(self.obj.DetailedResults)
self.form.checkSpreadsheet.setChecked(self.obj.CreateSpreadsheet)
def add(self):
"""Adds a new row below the last one"""
self.form.list.insertRow(self.form.list.currentRow()+1)
def remove(self):
"""Removes the current row"""
if self.form.list.currentRow() >= 0:
self.form.list.removeRow(self.form.list.currentRow())
def clear(self):
"""Clears the list"""
self.form.list.clearContents()
self.form.list.setRowCount(0)
def importCSV(self):
"""Imports a CSV file"""
filename = QtGui.QFileDialog.getOpenFileName(QtGui.QApplication.activeWindow(), translate("Arch","Import CSV File"), None, "CSV file (*.csv)");
if filename:
filename = filename[0]
if sys.version_info.major < 3:
filename = filename.encode("utf8")
self.form.list.clearContents()
import csv
with open(filename[0], 'rb') as csvfile:
with open(filename,'r') as csvfile:
r = 0
for row in csv.reader(csvfile):
self.form.list.insertRow(r)
for i in range(5):
if len(row) > i:
t = row[i]
t = t.replace("²","^2")
t = t.replace("³","^3")
#t = t.replace("²","^2")
#t = t.replace("³","^3")
self.form.list.setItem(r,i,QtGui.QTableWidgetItem(t))
r += 1
def exportCSV(self):
if self.obj:
if self.obj.Result:
filename = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), translate("Arch","Export CSV File"), None, "CSV file (*.csv)");
if filename:
# the following line crashes, couldn't fnid out why
# self.obj.Result.exportFile(str(filename[0].encode("utf8")))
import csv
if not("Up-to-date" in self.obj.State):
self.obj.Proxy.execute(self.obj)
numrows = len(self.obj.Description)+1
with open(filename[0].encode("utf8"), 'wb') as csvfile:
csvfile = csv.writer(csvfile,delimiter="\t")
for i in range(numrows):
r = []
for j in ["A","B","C"]:
r.append(self.obj.Result.getContents(j+str(i+1)))
csvfile.writerow(r)
print("successfully exported ",filename[0])
def export(self):
"""Exports the results as MD or CSV"""
# commit latest changes
self.writeValues()
# tests
if not("Up-to-date" in self.obj.State):
self.obj.Proxy.execute(self.obj)
if not hasattr(self.obj.Proxy,"data"):
return
if not self.obj.Proxy.data:
return
filename = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), translate("Arch","Export CSV File"), None, "CSV (*.csv);;Markdown (*.md)");
if filename:
filt = filename[1]
filename = filename[0]
if sys.version_info.major < 3:
filename = filename.encode("utf8")
# add missing extension
if (not filename.lower().endswith(".csv")) and (not filename.lower().endswith(".md")):
if "csv" in filt:
filename += ".csv"
else:
filename += ".md"
if filename.lower().endswith(".csv"):
self.exportCSV(filename)
elif filename.lower().endswith(".md"):
self.exportMD(filename)
else:
FreeCAD.Console.PrintError(translate("Arch","Unable to recognize that file type")+":"+filename+"\n")
def getRows(self):
"""get the rows that contain data"""
rows = []
if hasattr(self.obj.Proxy,"data") and self.obj.Proxy.data:
for key in self.obj.Proxy.data.keys():
n = key[1:]
if not n in rows:
rows.append(n)
rows.sort(key=int)
return rows
def exportCSV(self,filename):
"""Exports the results as a CSV file"""
# use TAB to separate values
DELIMITER = "\t"
import csv
with open(filename, 'w') as csvfile:
csvfile = csv.writer(csvfile,delimiter=DELIMITER)
csvfile.writerow([translate("Arch","Description"),translate("Arch","Value"),translate("Arch","Unit")])
if self.obj.DetailedResults:
csvfile.writerow(["","",""])
for i in self.getRows():
r = []
for j in ["A","B","C"]:
if j+i in self.obj.Proxy.data:
r.append(str(self.obj.Proxy.data[j+i]))
else:
r.append("")
csvfile.writerow(r)
print("successfully exported ",filename)
def exportMD(self,filename):
"""Exports the results as a Markdown file"""
with open(filename, 'w') as mdfile:
mdfile.write("| "+translate("Arch","Description")+" | "+translate("Arch","Value")+" | "+translate("Arch","Unit")+" |\n")
mdfile.write("| --- | --- | --- |\n")
if self.obj.DetailedResults:
mdfile.write("| | | |\n")
for i in self.getRows():
r = []
for j in ["A","B","C"]:
if j+i in self.obj.Proxy.data:
r.append(str(self.obj.Proxy.data[j+i]))
else:
r.append("")
mdfile.write("| "+" | ".join(r)+" |\n")
print("successfully exported ",filename)
def select(self):
"""Adds selected objects to current row"""
if self.form.list.currentRow() >= 0:
sel = ""
for o in FreeCADGui.Selection.getSelection():
@@ -374,15 +597,34 @@ class _ArchScheduleTaskPanel:
self.form.list.setItem(self.form.list.currentRow(),3,QtGui.QTableWidgetItem(sel))
def accept(self):
"""executes when OK button has been pressed"""
# store widths
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
p.SetInt("ScheduleColumnWidth0",self.form.list.columnWidth(0))
p.SetInt("ScheduleColumnWidth1",self.form.list.columnWidth(1))
p.SetInt("ScheduleColumnWidth2",self.form.list.columnWidth(2))
p.SetInt("ScheduleColumnWidth3",self.form.list.columnWidth(3))
# commit values
self.writeValues()
return True
def writeValues(self):
"""commits values and recalculate"""
if not self.obj:
import Spreadsheet
self.obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython","Schedule")
self.obj.Label = translate("Arch","Schedule")
_ArchSchedule(self.obj)
sp = FreeCAD.ActiveDocument.addObject("Spreadsheet::Sheet","Result")
self.obj.Result = sp
if FreeCAD.GuiUp:
_ViewProviderArchSchedule(self.obj.ViewObject)
if hasattr(self.obj,"CreateSpreadsheet") and self.obj.CreateSpreadsheet:
import Spreadsheet
sp = FreeCAD.ActiveDocument.addObject("Spreadsheet::Sheet","Result")
self.obj.Result = sp
lists = [ [], [], [], [], [] ]
for i in range(self.form.list.rowCount()):
for j in range(5):
@@ -391,14 +633,18 @@ class _ArchScheduleTaskPanel:
lists[j].append(cell.text())
else:
lists[j].append("")
FreeCAD.ActiveDocument.openTransaction("Edited Schedule")
self.obj.Description = lists[0]
self.obj.Value = lists[1]
self.obj.Unit = lists[2]
self.obj.Objects = lists[3]
self.obj.Filter = lists[4]
self.obj.Label = self.form.lineEditName.text()
self.obj.DetailedResults = self.form.checkDetailed.isChecked()
self.obj.CreateSpreadsheet = self.form.checkSpreadsheet.isChecked()
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
return True
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Arch_Schedule',_CommandArchSchedule())
FreeCADGui.addCommand('Arch_Schedule',CommandArchSchedule())

File diff suppressed because one or more lines are too long