FreeCAD / src /Mod /BIM /ArchSchedule.py
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net> *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD 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 *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__title__ = "Arch Schedule"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecad.org"
## @package ArchSchedule
# \ingroup ARCH
# \brief The Schedule object and tools
#
# This module provides tools to build Schedule objects.
# Schedules are objects that can count and gather information
# about objects in the document, and fill a spreadsheet with the result
import FreeCAD
from draftutils import params
if FreeCAD.GuiUp:
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui
from draftutils.translate import translate
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
PARAMS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM")
VERBOSE = True # change this for silent recomputes
class _ArchScheduleDocObserver:
"doc observer to monitor all recomputes"
# https://forum.freecad.org/viewtopic.php?style=3&p=553377#p553377
def __init__(self, doc, schedule):
self.doc = doc
self.schedule = schedule
def slotRecomputedDocument(self, doc):
if doc != self.doc:
return
try:
self.schedule.Proxy.execute(self.schedule)
except:
pass
class _ArchSchedule:
"the Arch Schedule object"
def __init__(self, obj):
self.setProperties(obj)
obj.Proxy = self
self.Type = "Schedule"
def onDocumentRestored(self, obj):
self.setProperties(obj)
if hasattr(obj, "Result"):
self.update_properties_0v21(obj)
if hasattr(obj, "Description"):
self.update_properties_1v1(obj)
def update_properties_0v21(self, obj):
from draftutils.messages import _log
sp = obj.Result
if sp is not None:
self.setSchedulePropertySpreadsheet(sp, obj)
obj.removeProperty("Result")
_log("v0.21, " + obj.Name + ", removed property 'Result', and added property 'AutoUpdate'")
if sp is not None:
_log("v0.21, " + sp.Name + ", added property 'Schedule'")
def update_properties_1v1(self, obj):
from draftutils.messages import _log
if obj.getTypeIdOfProperty("Description") == "App::PropertyStringList":
obj.Operation = obj.Description
obj.removeProperty("Description")
_log("v1.1, " + obj.Name + ", renamed property 'Description' to 'Operation'")
for prop in (
"Operation",
"Value",
"Unit",
"Objects",
"Filter",
"CreateSpreadsheet",
"DetailedResults",
):
obj.setGroupOfProperty(prop, "Schedule")
def setProperties(self, obj):
if not "Operation" in obj.PropertiesList:
obj.addProperty(
"App::PropertyStringList",
"Operation",
"Schedule",
QT_TRANSLATE_NOOP("App::Property", "The operation column"),
locked=True,
)
if not "Value" in obj.PropertiesList:
obj.addProperty(
"App::PropertyStringList",
"Value",
"Schedule",
QT_TRANSLATE_NOOP("App::Property", "The values column"),
locked=True,
)
if not "Unit" in obj.PropertiesList:
obj.addProperty(
"App::PropertyStringList",
"Unit",
"Schedule",
QT_TRANSLATE_NOOP("App::Property", "The units column"),
locked=True,
)
if not "Objects" in obj.PropertiesList:
obj.addProperty(
"App::PropertyStringList",
"Objects",
"Schedule",
QT_TRANSLATE_NOOP("App::Property", "The objects column"),
locked=True,
)
if not "Filter" in obj.PropertiesList:
obj.addProperty(
"App::PropertyStringList",
"Filter",
"Schedule",
QT_TRANSLATE_NOOP("App::Property", "The filter column"),
locked=True,
)
if not "CreateSpreadsheet" in obj.PropertiesList:
obj.addProperty(
"App::PropertyBool",
"CreateSpreadsheet",
"Schedule",
QT_TRANSLATE_NOOP(
"App::Property",
"If True, a spreadsheet containing the results is recreated when needed",
),
locked=True,
)
if not "DetailedResults" in obj.PropertiesList:
obj.addProperty(
"App::PropertyBool",
"DetailedResults",
"Schedule",
QT_TRANSLATE_NOOP(
"App::Property",
"If True, additional lines with each individual object are added to the results",
),
locked=True,
)
if not "AutoUpdate" in obj.PropertiesList:
obj.addProperty(
"App::PropertyBool",
"AutoUpdate",
"Schedule",
QT_TRANSLATE_NOOP(
"App::Property",
"If True, the schedule and the associated spreadsheet are updated whenever the document is recomputed",
),
locked=True,
)
obj.AutoUpdate = True
# To add the doc observer:
self.onChanged(obj, "AutoUpdate")
def setSchedulePropertySpreadsheet(self, sp, obj):
if not hasattr(sp, "Schedule"):
sp.addProperty(
"App::PropertyLink",
"Schedule",
"Arch",
QT_TRANSLATE_NOOP("App::Property", "The BIM Schedule that uses this spreadsheet"),
locked=True,
)
sp.Schedule = obj
def getSpreadSheet(self, obj, force=False):
"""Get the spreadsheet and store it in self.spreadsheet.
If force is True the spreadsheet is created if required.
"""
try: # Required as self.spreadsheet may get deleted.
if (
getattr(self, "spreadsheet", None) is not None
and getattr(self.spreadsheet, "Schedule", None) == obj
):
return self.spreadsheet
except:
pass
else:
for o in FreeCAD.ActiveDocument.Objects:
if o.TypeId == "Spreadsheet::Sheet" and getattr(o, "Schedule", None) == obj:
self.spreadsheet = o
return self.spreadsheet
if force:
self.spreadsheet = FreeCAD.ActiveDocument.addObject("Spreadsheet::Sheet", "Result")
self.setSchedulePropertySpreadsheet(self.spreadsheet, obj)
return self.spreadsheet
else:
return None
def onChanged(self, obj, prop):
if prop == "CreateSpreadsheet":
if obj.CreateSpreadsheet:
self.getSpreadSheet(obj, force=True)
else:
sp = self.getSpreadSheet(obj)
if sp is not None:
FreeCAD.ActiveDocument.removeObject(sp.Name)
self.spreadsheet = None
elif prop == "AutoUpdate":
if obj.AutoUpdate:
if getattr(self, "docObserver", None) is None:
self.docObserver = _ArchScheduleDocObserver(FreeCAD.ActiveDocument, obj)
FreeCAD.addDocumentObserver(self.docObserver)
elif getattr(self, "docObserver", None) is not None:
FreeCAD.removeDocumentObserver(self.docObserver)
self.docObserver = None
def setSpreadsheetData(self, obj, force=False):
"""Fills a spreadsheet with the stored data"""
if not hasattr(self, "data"):
self.execute(obj)
if not hasattr(self, "data"):
return
if not self.data:
return
if not (obj.CreateSpreadsheet or force):
return
sp = self.getSpreadSheet(obj, force=True)
widths = [sp.getColumnWidth(col) for col in ("A", "B", "C")]
sp.clearAll()
# clearAll resets the column widths:
for col, width in zip(("A", "B", "C"), widths):
sp.setColumnWidth(col, width)
# set headers
sp.set("A1", "Operation")
sp.set("B1", "Value")
sp.set("C1", "Unit")
sp.setStyle("A1:C1", "bold", "add")
# write contents
for k, v in self.data.items():
sp.set(k, v)
# recompute
sp.recompute()
sp.purgeTouched() # Remove the confusing blue checkmark from the spreadsheet.
for o in sp.InList: # Also recompute TechDraw views.
o.TypeId == "TechDraw::DrawViewSpreadsheet"
o.recompute()
def execute(self, obj):
# verify the data
if not obj.Operation:
# 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.Operation) != len(p):
return
self.data = {} # store all results in self.data, so it lives even without spreadsheet
self.li = 1 # row index - starts at 2 to leave 2 blank rows for the title
for i in range(len(obj.Operation)):
self.li += 1
if not obj.Operation[i]:
# blank line
continue
# write description
self.data["A" + str(self.li)] = obj.Operation[i]
if VERBOSE:
l = "OPERATION: " + obj.Operation[i]
print("")
print(l)
print(len(l) * "=")
# build set of valid objects
objs = obj.Objects[i]
val = obj.Value[i]
unit = obj.Unit[i]
details = obj.DetailedResults
ifcfile = None
elts = None
if val:
import Draft
import Arch
if objs:
objs = objs.split(";")
objs = [FreeCAD.ActiveDocument.getObject(o) for o in objs]
objs = [o for o in objs if o is not None]
else:
if hasattr(getattr(FreeCAD.ActiveDocument, "Proxy", None), "ifcfile"):
ifcfile = FreeCAD.ActiveDocument.Proxy.ifcfile
objs = FreeCAD.ActiveDocument.Objects
if len(objs) == 1:
if hasattr(objs[0], "StepId"):
from nativeifc import ifc_tools
ifcfile = ifc_tools.get_ifcfile(objs[0])
# remove object itself if the object is a group
if objs[0].isDerivedFrom("App::DocumentObjectGroup"):
objs = objs[0].Group
objs = Draft.get_group_contents(objs)
objs = self.expandArrays(objs)
# Remove included objects (e.g. walls that are part of another wall,
# base geometry, etc)
objs = Arch.pruneIncluded(objs, strict=True, silent=True)
# Remove all schedules and spreadsheets:
objs = [
o for o in objs if Draft.get_type(o) not in ["Schedule", "Spreadsheet::Sheet"]
]
# filter elements
if obj.Filter[i]:
if ifcfile:
elts = self.get_ifc_elements(ifcfile, obj.Filter[i])
else:
objs = self.apply_filter(objs, obj.Filter[i])
# perform operation: count or retrieve property
if ifcfile:
if elts:
self.update_from_elts(elts, val, unit, details)
elif objs:
self.update_from_objs(objs, val, unit, details)
self.setSpreadsheetData(obj)
self.save_ifc_props(obj)
def apply_filter(self, objs, filters):
"""Applies the given filters to the given list of objects"""
nobjs = []
for o in objs:
props = [p.upper() for p in o.PropertiesList]
ok = True
for f in filters.split(";"):
args = [a.strip() for a in f.strip().split(":")]
if args[0][0] == "!":
inv = True
prop = args[0][1:].upper()
else:
inv = False
prop = args[0].upper()
fval = args[1].upper()
if prop == "TYPE":
prop = "IFCTYPE"
if inv:
if prop in props:
csprop = o.PropertiesList[props.index(prop)]
if fval in getattr(o, csprop).upper():
ok = False
else:
if not (prop in props):
ok = False
else:
csprop = o.PropertiesList[props.index(prop)]
if not (fval in getattr(o, csprop).upper()):
ok = False
if ok:
nobjs.append(o)
return nobjs
def get_ifc_elements(self, ifcfile, filters):
"""Retrieves IFC elements corresponding to the given filters"""
elts = []
for el in ifcfile.by_type("IfcProduct"):
ok = True
for f in filters.split(";"):
args = [a.strip() for a in f.strip().split(":")]
if args[0][0] == "!":
inv = True
prop = args[0][1:]
else:
inv = False
prop = args[0]
fval = args[1]
if prop.upper() in ["CLASS", "IFCCLASS", "IFCTYPE"]:
prop = "is_a"
if inv:
if prop == "is_a":
if not fval.upper().startswith("IFC"):
fval = "Ifc" + fval
fval = fval.replace(" ", "")
if el.is_a(fval):
ok = False
else:
if prop in dir(el):
rval = getattr(el, prop)
if hasattr(rval, "id"):
if fval.startswith("#"):
fval = int(fval[1:])
if rval == fval:
ok = False
else:
if prop == "is_a":
if not fval.upper().startswith("IFC"):
fval = "Ifc" + fval
fval = fval.replace(" ", "")
if not el.is_a(fval):
ok = False
else:
if prop in dir(el):
rval = getattr(el, prop)
if hasattr(rval, "id"):
if fval.startswith("#"):
fval = int(fval[1:])
if rval != fval:
ok = False
else:
ok = False
if ok:
elts.append(el)
return elts
def update_from_objs(self, objs, val, unit, details):
"""Updates the spreadsheet data from FreeCAD objects"""
if val.upper() == "COUNT":
val = len(objs)
if VERBOSE:
print(val, ",".join([o.Label for o in objs]))
self.data["B" + str(self.li)] = str(val)
if details:
# additional blank line...
self.li += 1
self.data["A" + str(self.li)] = " "
else:
vals = val.split(".")
if vals[0][0].islower():
# old-style: first member is not a property
vals = vals[1:]
sumval = 0
# get unit
tp = None
unit = None
q = None
if unit:
unit = unit.replace("^", "") # get rid of existing power symbol
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
elif "3" in unit:
tp = FreeCAD.Units.Volume
elif "deg" in unit:
tp = FreeCAD.Units.Angle
else:
tp = FreeCAD.Units.Length
# format value
dv = params.get_param("Decimals", path="Units")
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
except Exception:
t = translate("Arch", "Unable to retrieve value from object")
FreeCAD.Console.PrintWarning(t + ": " + o.Name + "." + ".".join(vals) + "\n")
else:
if VERBOSE:
if tp and unit:
v = fs.format(FreeCAD.Units.Quantity(d, tp).getValueAs(unit).Value)
print(v, unit)
elif isinstance(d, str):
if d.replace(".", "", 1).isdigit():
print(fs.format(d))
else:
print(d)
else:
print(fs.format(d))
if details:
self.li += 1
self.data["A" + str(self.li)] = o.Name + " (" + o.Label + ")"
if tp and unit:
q = FreeCAD.Units.Quantity(d, tp)
self.data["B" + str(self.li)] = str(q.getValueAs(unit).Value)
self.data["C" + str(self.li)] = unit
else:
self.data["B" + str(self.li)] = str(d)
if sumval:
sumval += d
else:
sumval = d
val = sumval
if tp:
q = FreeCAD.Units.Quantity(val, tp)
# write data
if details:
self.li += 1
self.data["A" + str(self.li)] = "TOTAL"
if q and unit:
self.data["B" + str(self.li)] = str(q.getValueAs(unit).Value)
self.data["C" + str(self.li)] = unit
else:
self.data["B" + str(self.li)] = str(val)
if VERBOSE:
if tp and unit:
v = fs.format(FreeCAD.Units.Quantity(val, tp).getValueAs(unit).Value)
print("TOTAL:" + 34 * " " + v + " " + unit)
elif isinstance(val, str):
if val.replace(".", "", 1).isdigit():
v = fs.format(val)
print("TOTAL:" + 34 * " " + v)
else:
print("TOTAL:" + 34 * " " + val)
else:
v = fs.format(val)
print("TOTAL:" + 34 * " " + v)
def update_from_elts(self, elts, val, unit, details):
"""Updates the spreadsheet data from IFC elements"""
if val.upper() == "COUNT":
val = len(elts)
if VERBOSE:
print("COUNT:", val, "(", ",".join(["#" + str(e.id()) for e in elts]), ")")
self.data["B" + str(self.li)] = str(val)
if details:
# additional blank line...
self.li += 1
self.data["A" + str(self.li)] = " "
else:
total = 0
for el in elts:
if val in dir(el):
elval = getattr(el, val, "")
if isinstance(elval, tuple):
if len(elval) == 1:
elval = elval[0]
elif len(elval) == 0:
elval = ""
if hasattr(elval, "is_a") and elval.is_a("IfcRelationship"):
for att in dir(elval):
if att.startswith("Relating"):
targ = getattr(elval, att)
if targ != el:
elval = targ
break
elif att.startswith("Related"):
if not elval in getattr(elval, att):
elval = str(getattr(elval, att))
break
if details:
self.li += 1
name = el.Name if el.Name else ""
self.data["A" + str(self.li)] = "#" + str(el.id()) + name
self.data["B" + str(self.li)] = str(elval)
if VERBOSE:
print("#" + str(el.id()) + "." + val + " = " + str(elval))
if isinstance(elval, str) and elval.replace(".", "", 1).isdigit():
total += float(elval)
elif isinstance(elval, (int, float)):
total += elval
if total:
if details:
self.li += 1
self.data["A" + str(self.li)] = "TOTAL"
self.data["B" + str(self.li)] = str(total)
if VERBOSE:
print("TOTAL:", str(total))
def create_ifc(self, obj, ifcfile, export=False):
"""Creates an IFC element for this object"""
from nativeifc import ifc_tools # lazy loading
proj = ifcfile.by_type("IfcProject")[0]
elt = ifc_tools.api_run("root.create_entity", ifcfile, ifc_class="IfcControl")
ifc_tools.set_attribute(ifcfile, elt, "Name", obj.Label)
ifc_tools.api_run(
"project.assign_declaration", ifcfile, definitions=[elt], relating_context=proj
)
if not export:
ifc_tools.add_properties(obj, ifcfile, elt)
return elt
def save_ifc_props(self, obj, ifcfile=None, elt=None):
"""Saves the object data to IFC"""
from nativeifc import ifc_psets # lazy loading
ifc_psets.edit_pset(
obj, "Operation", "::".join(obj.Operation), ifcfile=ifcfile, element=elt
)
ifc_psets.edit_pset(obj, "Value", "::".join(obj.Value), ifcfile=ifcfile, element=elt)
ifc_psets.edit_pset(obj, "Unit", "::".join(obj.Unit), ifcfile=ifcfile, element=elt)
ifc_psets.edit_pset(obj, "Objects", "::".join(obj.Objects), ifcfile=ifcfile, element=elt)
ifc_psets.edit_pset(obj, "Filter", "::".join(obj.Filter), ifcfile=ifcfile, element=elt)
def export_ifc(self, obj, ifcfile):
"""Exports the object to IFC (does not modify the FreeCAD object)."""
elt = self.create_ifc(obj, ifcfile, export=True)
self.save_ifc_props(obj, ifcfile, elt)
return elt
def dumps(self):
return self.Type
def loads(self, state):
if state:
self.Type = state
def getIfcClass(self, obj):
"""gets the IFC class of this object"""
if hasattr(obj, "IfcType"):
return obj.IfcType
elif hasattr(obj, "IfcRole"):
return obj.IfcRole
elif hasattr(obj, "IfcClass"):
return obj.IfcClass
else:
return None
def getArray(self, obj):
"returns a count number if this object needs to be duplicated"
import Draft
elementCount = 0
# The given object can belong to multiple arrays
# o is a potential parent array of the given object
for o in obj.InList:
if Draft.getType(o) == "Array":
elementCount += o.Count
return elementCount
def expandArrays(self, objs):
"""Expands array elements in the given list of objects"""
expandedobjs = []
for obj in objs:
ifcClass = self.getIfcClass(obj)
# This filters out the array object itself, which has no IFC class,
# but leaves the array elements, which do have an IFC class.
if ifcClass:
expandedobjs.append(obj)
# If the object is in an array, add it and the rest of its elements
# to the list.
array = self.getArray(obj)
for i in range(1, array): # The first element (0) was already added
expandedobjs.append(obj)
return expandedobjs
class _ViewProviderArchSchedule:
"A View Provider for Schedules"
def __init__(self, vobj):
vobj.Proxy = self
def getIcon(self):
if self.Object.AutoUpdate is False:
import TechDrawGui
return ":/icons/TechDraw_TreePageUnsync.svg"
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=0):
if mode != 0:
return None
self.taskd = ArchScheduleTaskPanel(vobj.Object)
if not self.taskd.form.isVisible():
from PySide import QtCore
QtCore.QTimer.singleShot(100, self.showEditor)
return True
def showEditor(self):
if hasattr(self, "taskd"):
self.taskd.form.show()
def unsetEdit(self, vobj, mode):
if mode != 0:
return None
return True
def doubleClicked(self, vobj):
self.edit()
return True
def setupContextMenu(self, vobj, menu):
if FreeCADGui.activeWorkbench().name() != "BIMWorkbench":
return
actionEdit = QtGui.QAction(translate("Arch", "Edit"), menu)
QtCore.QObject.connect(actionEdit, QtCore.SIGNAL("triggered()"), self.edit)
menu.addAction(actionEdit)
if self.Object.CreateSpreadsheet is True:
msg = translate("Arch", "Remove spreadsheet")
else:
msg = translate("Arch", "Attach spreadsheet")
actionToggleSpreadsheet = QtGui.QAction(QtGui.QIcon(":/icons/Arch_Schedule.svg"), msg, menu)
QtCore.QObject.connect(
actionToggleSpreadsheet, QtCore.SIGNAL("triggered()"), self.toggleSpreadsheet
)
menu.addAction(actionToggleSpreadsheet)
def edit(self):
FreeCADGui.ActiveDocument.setEdit(self.Object, 0)
def toggleSpreadsheet(self):
self.Object.CreateSpreadsheet = not self.Object.CreateSpreadsheet
def claimChildren(self):
if hasattr(self, "Object"):
return [self.Object.Proxy.getSpreadSheet(self.Object)]
def dumps(self):
return None
def loads(self, state):
return None
def getDisplayModes(self, vobj):
return ["Default"]
def getDefaultDisplayMode(self):
return "Default"
def setDisplayMode(self, mode):
return mode
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
self.form.list.setColumnWidth(0, params.get_param_arch("ScheduleColumnWidth0"))
self.form.list.setColumnWidth(1, params.get_param_arch("ScheduleColumnWidth1"))
self.form.list.setColumnWidth(2, params.get_param_arch("ScheduleColumnWidth2"))
self.form.list.setColumnWidth(3, params.get_param_arch("ScheduleColumnWidth3"))
w = params.get_param_arch("ScheduleDialogWidth")
h = params.get_param_arch("ScheduleDialogHeight")
self.form.resize(w, h)
# restore default states
self.form.checkAutoUpdate.setChecked(PARAMS.GetBool("ScheduleAutoUpdate", False))
# set delegate - Not using custom delegates for now...
# self.form.list.setItemDelegate(ScheduleDelegate())
# self.form.list.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked)
# connect slots
self.form.buttonAdd.clicked.connect(self.add)
self.form.buttonDel.clicked.connect(self.remove)
self.form.buttonClear.clicked.connect(self.clear)
self.form.buttonImport.clicked.connect(self.importCSV)
self.form.buttonExport.clicked.connect(self.export)
self.form.buttonSelect.clicked.connect(self.select)
self.form.buttonBox.accepted.connect(self.accept)
self.form.buttonBox.rejected.connect(self.reject)
self.form.rejected.connect(self.reject)
self.form.list.clearContents()
if self.obj:
# for p in [obj.Value,obj.Unit,obj.Objects,obj.Filter]:
# if len(obj.Operation) != len(p):
# return
self.form.list.setRowCount(len(obj.Operation))
for i in range(5):
for j in range(len(obj.Operation)):
try:
text = [obj.Operation, obj.Value, obj.Unit, obj.Objects, obj.Filter][i][j]
except:
text = ""
item = QtGui.QTableWidgetItem(text)
self.form.list.setItem(j, i, item)
self.form.lineEditName.setText(self.obj.Label)
self.form.checkSpreadsheet.setChecked(self.obj.CreateSpreadsheet)
self.form.checkDetailed.setChecked(self.obj.DetailedResults)
self.form.checkAutoUpdate.setChecked(self.obj.AutoUpdate)
# center over FreeCAD window
mw = FreeCADGui.getMainWindow()
self.form.move(
mw.frameGeometry().topLeft() + mw.rect().center() - self.form.rect().center()
)
self.form.show()
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 files (*.csv *.CSV)",
)
if filename:
filename = filename[0]
self.form.list.clearContents()
import csv
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")
self.form.list.setItem(r, i, QtGui.QTableWidgetItem(t))
r += 1
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,
"Comma-separated values (*.csv);;TAB-separated values (*.tsv);;Markdown (*.md)",
)
if filename:
filt = filename[1]
filename = filename[0]
# add missing extension
if (
(not filename.lower().endswith(".csv"))
and (not filename.lower().endswith(".tsv"))
and (not filename.lower().endswith(".md"))
):
if "csv" in filt:
filename += ".csv"
elif "tsv" in filt:
filename += ".tsv"
else:
filename += ".md"
if filename.lower().endswith(".csv"):
self.exportCSV(filename, delimiter=",")
elif filename.lower().endswith(".tsv"):
self.exportCSV(filename, delimiter="\t")
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, delimiter="\t"):
"""Exports the results as a CSV/TSV file"""
import csv
with open(filename, "w") as csvfile:
csvfile = csv.writer(csvfile, delimiter=delimiter)
csvfile.writerow(
[
translate("Arch", "Operation"),
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", "Operation")
+ " | "
+ 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():
if o != self.obj:
if sel:
sel += ";"
sel += o.Name
if sel:
self.form.list.setItem(self.form.list.currentRow(), 3, QtGui.QTableWidgetItem(sel))
def accept(self):
"""Saves the changes and closes the dialog"""
# store widths
params.set_param_arch("ScheduleColumnWidth0", self.form.list.columnWidth(0))
params.set_param_arch("ScheduleColumnWidth1", self.form.list.columnWidth(1))
params.set_param_arch("ScheduleColumnWidth2", self.form.list.columnWidth(2))
params.set_param_arch("ScheduleColumnWidth3", self.form.list.columnWidth(3))
params.set_param_arch("ScheduleDialogWidth", self.form.width())
params.set_param_arch("ScheduleDialogHeight", self.form.height())
# store default states
PARAMS.SetBool("ScheduleAutoUpdate", self.form.checkAutoUpdate.isChecked())
# commit values
self.writeValues()
self.form.hide()
FreeCADGui.ActiveDocument.resetEdit()
return True
def reject(self):
"""Close dialog without saving"""
self.form.hide()
FreeCADGui.ActiveDocument.resetEdit()
return True
def writeValues(self):
"""commits values and recalculate"""
if not self.obj:
import Arch
self.obj = Arch.makeSchedule()
lists = [[], [], [], [], []]
for i in range(self.form.list.rowCount()):
for j in range(5):
cell = self.form.list.item(i, j)
if cell:
lists[j].append(cell.text())
else:
lists[j].append("")
FreeCAD.ActiveDocument.openTransaction("Edited Schedule")
self.obj.Operation = 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()
self.obj.AutoUpdate = self.form.checkAutoUpdate.isChecked()
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()