FreeCAD/src/Mod/Spreadsheet/Spreadsheet.py
2014-01-09 18:23:39 -02:00

874 lines
31 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 *
#* *
#***************************************************************************
import re, math, FreeCAD, FreeCADGui
from PySide import QtCore,QtGui
DEBUG = True # set to True to show debug messages
if open.__module__ == '__builtin__':
pyopen = open # because we'll redefine open below
class MathParser:
"A math expression parser"
# code adapted from http://www.nerdparadise.com/tech/python/parsemath/
def __init__(self, string, vars={}):
self.string = string
self.index = 0
self.vars = {
'pi' : math.pi,
'e' : math.e
}
for var in vars.keys():
if self.vars.get(var) != None:
raise Exception("Cannot redefine the value of " + var)
self.vars[var] = vars[var]
def getValue(self):
value = self.parseExpression()
self.skipWhitespace()
if self.hasNext():
raise Exception(
"Unexpected character found: '" +
self.peek() +
"' at index " +
str(self.index))
return value
def peek(self):
return self.string[self.index:self.index + 1]
def hasNext(self):
return self.index < len(self.string)
def skipWhitespace(self):
while self.hasNext():
if self.peek() in ' \t\n\r':
self.index += 1
else:
return
def parseExpression(self):
return self.parseAddition()
def parseAddition(self):
values = [self.parseMultiplication()]
while True:
self.skipWhitespace()
char = self.peek()
if char == '+':
self.index += 1
values.append(self.parseMultiplication())
elif char == '-':
self.index += 1
values.append(-1 * self.parseMultiplication())
else:
break
return sum(values)
def parseMultiplication(self):
values = [self.parseParenthesis()]
while True:
self.skipWhitespace()
char = self.peek()
if char == '*':
self.index += 1
values.append(self.parseParenthesis())
elif char == '/':
div_index = self.index
self.index += 1
denominator = self.parseParenthesis()
if denominator == 0:
raise Exception(
"Division by 0 kills baby whales (occured at index " +
str(div_index) +
")")
values.append(1.0 / denominator)
else:
break
value = 1.0
for factor in values:
value *= factor
return value
def parseParenthesis(self):
self.skipWhitespace()
char = self.peek()
if char == '(':
self.index += 1
value = self.parseExpression()
self.skipWhitespace()
if self.peek() != ')':
raise Exception(
"No closing parenthesis found at character "
+ str(self.index))
self.index += 1
return value
else:
return self.parseNegative()
def parseNegative(self):
self.skipWhitespace()
char = self.peek()
if char == '-':
self.index += 1
return -1 * self.parseParenthesis()
else:
return self.parseValue()
def parseValue(self):
self.skipWhitespace()
char = self.peek()
if char in '0123456789.':
return self.parseNumber()
else:
return self.parseVariable()
def parseVariable(self):
self.skipWhitespace()
var = ''
while self.hasNext():
char = self.peek()
if char.lower() in '_abcdefghijklmnopqrstuvwxyz0123456789':
var += char
self.index += 1
else:
break
value = self.vars.get(var, None)
if value == None:
raise Exception(
"Unrecognized variable: '" +
var +
"'")
return float(value)
def parseNumber(self):
self.skipWhitespace()
strValue = ''
decimal_found = False
char = ''
while self.hasNext():
char = self.peek()
if char == '.':
if decimal_found:
raise Exception(
"Found an extra period in a number at character " +
str(self.index) +
". Are you European?")
decimal_found = True
strValue += '.'
elif char in '0123456789':
strValue += char
else:
break
self.index += 1
if len(strValue) == 0:
if char == '':
raise Exception("Unexpected end found")
else:
raise Exception(
"I was expecting to find a number at character " +
str(self.index) +
" but instead I found a '" +
char +
"'. What's up with that?")
return float(strValue)
class Spreadsheet:
"""An object representing a spreadsheet. Can be used as a
FreeCAD object or as a standalone python object.
Cells of the spreadsheet can be got/set as arguments, as:
myspreadsheet = Spreadsheet()
myspreadsheet.a1 = 54
print(myspreadsheet.a1)
myspreadsheet.a2 = "My text"
myspreadsheet.b1 = "=a1*3"
print(myspreadsheet.b1)
The cell names are case-insensitive (a1 = A1)
"""
def __init__(self,obj=None):
if obj:
obj.Proxy = self
obj.addProperty("App::PropertyLinkList","Controllers","Base","Cell controllers of this object")
self._cells = {} # this stores cell contents
self._relations = {} # this stores relations - currently not used
self.cols = [] # this stores filled columns
self.rows = [] # this stores filed rows
self.Type = "Spreadsheet"
def __repr__(self):
return "Spreadsheet object containing " + str(len(self._cells)) + " cells"
def __setattr__(self, key, value):
if self.isKey(key):
key = key.lower()
if DEBUG: print "Setting key ",key," to value ",value
if (value == "") or (value == None):
# remove cell
if key in self._cells.keys():
del self._cells[key]
else:
# add cell
self._cells[key] = value
if value:
if self.isFunction(value):
self._updateDependencies(key,value)
c,r = self.splitKey(key)
if not c in self.cols:
self.cols.append(c)
self.cols.sort()
if not r in self.rows:
self.rows.append(r)
self.rows.sort()
else:
self.__dict__.__setitem__(key,value)
def __getattr__(self, key):
if key.lower() in self._cells:
key = key.lower()
if self.isFunction(self._cells[key]):
try:
e = self.evaluate(key)
except:
print "Spreadsheet: Error evaluating formula"
return None
else:
return e
else:
return self._cells[key]
else:
return self.__dict__.__getitem__(key)
def __getstate__(self):
self._cells["Type"] = self.Type
return self._cells
def __setstate__(self,state):
if state:
self._cells = state
# extracting Type
if "Type" in self._cells.keys():
self.Type = self._cells["Type"]
del self._cells["Type"]
# updating relation tables
self.rows = []
self.cols = []
self._relations = {}
for key in self._cells.keys():
c,r = self.splitKey(key)
if not r in self.rows:
self.rows.append(r)
self.rows.sort()
if not c in self.cols:
self.cols.append(c)
self.cols.sort()
if self.isFunction(key):
self._updateDependencies(key)
def _updateDependencies(self,key,value=None):
"search for ancestors in the value and updates the table"
ancestors = []
if not value:
value = self._cells[key]
for v in re.findall(r"[\w']+",value):
if self.isKey(v):
ancestors.append(v)
for a in ancestors:
if a in self._relations:
if not key in self._relations[a]:
self._relations[a].append(key)
else:
self._relations[a] = [key]
def execute(self,obj):
self.setControlledCells(obj)
def isFunction(self,key):
"isFunction(cell): returns True if the given cell or value is a function"
if str(key).lower() in self._cells:
key = key.lower()
if str(self._cells[key])[0] == "=":
return True
elif str(key)[0] == "=":
return True
else:
return False
def isNumeric(self,key):
"isNumeric(cell): returns True if the given cell returns a number"
key = key.lower()
if self.isFunction(key):
res = self.evaluate(key)
else:
res = self._cells[key]
if isinstance(res,float) or isinstance(res,int):
return True
else:
return False
def isKey(self,value):
"isKey(val): returns True if the given value is a valid cell number"
allowMoreThanOneLetter = False
al = False
nu = False
for v in value:
if not v.isalnum():
return False
elif not al:
if v.isalpha():
al = True
else:
return False
else:
if not nu:
# forbidden to set items at row 0
if v == "0":
return False
if v.isalpha():
if not allowMoreThanOneLetter:
return False
elif nu:
return False
elif v.isdigit():
nu = True
if not nu:
return False
return True
def splitKey(self,key):
"splitKey(cell): splits a key between column and row"
c = ''
r = ''
for ch in key:
if ch.isalpha():
c += ch
else:
r += ch
return c,r
def getFunction(self,key):
"getFunction(cell): returns the function contained in the given cell, instead of the value"
key = key.lower()
if key in self._cells:
return self._cells[key]
else:
return None
def getSize(self):
"getSize(): returns a tuple with number of columns and rows of this spreadsheet"
return (len(self.columns),len(self.rows))
def getCells(self,index):
"getCells(index): returns the cells from the given column of row number"
cells = {}
for k in self._cells.keys():
c,r = self.splitKey(k)
if index in [c,r]:
cells[k] = self._cells[k]
return cells
def evaluate(self,key):
"evaluate(key): evaluates the given formula"
key = key.lower()
elts = re.split(r'(\W+)',self._cells[key][1:])
result = ""
for e in elts:
if self.isKey(e):
if self.isFunction(e):
if self.isNumeric(e):
result += str(self.evaluate(e))
else:
print "Spreadsheet: Error evaluating formula"
return
elif self.isNumeric(e):
result += str(self._cells[e.lower()])
else:
result += e
if DEBUG: print "Evaluating ",result
try:
p = MathParser(result)
result = p.getValue()
except Exception as (ex):
msg = ex.message
raise Exception(msg)
return result
def setControlledCells(self,obj):
"Fills the cells that are controlled by a controller"
if obj:
if hasattr(obj,"Controllers"):
for co in obj.Controllers:
co.Proxy.setCells(co,obj)
def getControlledCells(self,obj):
"returns a list of cells managed by controllers"
cells = []
if hasattr(obj,"Controllers"):
for c in obj.Controllers:
cells.extend(c.Proxy.getCells(c,obj))
return cells
class ViewProviderSpreadsheet(object):
def __init__(self, vobj):
vobj.Proxy = self
def getIcon(self):
import Spreadsheet_rc
return ":/icons/Spreadsheet.svg"
def attach(self,vobj):
self.Object = vobj.Object
def setEdit(self,vobj,mode):
if hasattr(self,"editor"):
pass
else:
self.editor = SpreadsheetView(vobj.Object)
addSpreadsheetView(self.editor)
return True
def unsetEdit(self,vobj,mode):
return False
def claimChildren(self):
if hasattr(self,"Object"):
if hasattr(self.Object,"Controllers"):
return self.Object.Controllers
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class SpreadsheetController:
"A spreadsheet cell controller object"
def __init__(self,obj):
obj.Proxy = self
self.Type = "SpreadsheetController"
obj.addProperty("App::PropertyEnumeration","FilterType","Filter","The type of filter to apply to the scene objects")
obj.addProperty("App::PropertyString","Filter","Filter","The filter to apply to the scene objects")
obj.addProperty("App::PropertyEnumeration","DataType","Data","The type of data to extract from the objects")
obj.addProperty("App::PropertyString","Data","Data","The data to extract from the objects")
obj.addProperty("App::PropertyString","BaseCell","Base","The starting cell of this controller")
obj.addProperty("App::PropertyEnumeration","Direction","Base","The cells direction of this controller")
obj.FilterType = ["Object Type","Object Name"]
obj.DataType = ["Get Property","Count"]
obj.Direction = ["Horizontal","Vertical"]
def execute(self,obj):
pass
def __getstate__(self):
return self.Type
def __setstate__(self,state):
if state:
self.Type = state
def onChanged(self,obj,prop):
if prop == "DataType":
if obj.DataType == "Count":
obj.setEditorMode('Data',1)
else:
obj.setEditorMode('Data',0)
def getDataSet(self,obj):
"returns a list of objects to be considered by this controller"
result = []
if hasattr(obj,"FilterType"):
import Draft
baseset = FreeCAD.ActiveDocument.Objects
if obj.FilterType == "Object Type":
for o in baseset:
t = Draft.getType(o)
if t == "Part":
t = obj.TypeId
if obj.Filter:
if obj.Filter in t:
result.append(obj)
else:
result.append(obj)
elif obj.FilterType == "Object Name":
for o in baseset:
if obj.Filter:
if obj.Filter in obl.Label:
result.append(obj)
else:
result.append(obj)
return result
def getCells(self,obj,spreadsheet):
"returns a list of cells controlled by this controller"
cells = []
if obj.BaseCell:
if obj.DataType == "Count":
return obj.BaseCell
for i in range(len(self.getDataSet())):
# get the correct cell key
c,r = spreadsheet.Proxy.splitKey(obj.BaseCell)
if obj.Direction == "Horizontal":
c = "abcdefghijklmnopqrstuvwxyz".index(c)
c += i
c = "abcdefghijklmnopqrstuvwxyz"[c]
else:
r = int(r) + i
cells.append(c+str(r))
return cells
def setCells(self,obj,spreadsheet):
"Fills the controlled cells of the given spreadsheet"
if obj.BaseCell:
dataset = self.getDataSet()
if obj.DataType == "Count":
if spreadsheet.Proxy.isKey(obj.BaseCell):
try:
setattr(spreadsheet.Proxy,obj.BaseCell,len(dataset))
except:
print "Spreadsheet: Error counting objects"
elif obj.Data:
for i in range(len(dataset)):
# get the correct cell key
c,r = spreadsheet.Proxy.splitKey(obj.BaseCell)
if obj.Direction == "Horizontal":
c = "abcdefghijklmnopqrstuvwxyz".index(c)
c += i
c = "abcdefghijklmnopqrstuvwxyz"[c]
else:
r = int(r) + i
cell = c+str(r)
if DEBUG: print "auto setting cell ",cell
if spreadsheet.Proxy.isKey(cell):
# get the contents
args = obj.Data.split(".")
value = dataset[i]
for arg in args:
if hasattr(value,arg):
value = getattr(value,arg)
try:
setattr(spreadsheet.Proxy,cell,value)
if DEBUG: print "setting cell ",cell," to value ",value
except:
print "Spreadsheet: Error retrieving property "+obj.Data+" from object "+dataset[i].Name
class ViewProviderSpreadsheetController:
"A view provider for the spreadsheet cell controller"
def __init__(self,vobj):
vobj.Proxy = self
def getIcon(self):
import Spreadsheet_rc
return ":/icons/SpreadsheetController.svg"
class SpreadsheetView(QtGui.QWidget):
"A spreadsheet viewer for FreeCAD"
def __init__(self,spreadsheet=None):
from DraftTools import translate
QtGui.QWidget.__init__(self)
self.setWindowTitle(str(translate("Spreadsheet","Spreadsheet")))
self.setObjectName("Spreadsheet viewer")
self.verticalLayout = QtGui.QVBoxLayout(self)
self.doNotChange = False
# add editor line
self.horizontalLayout = QtGui.QHBoxLayout()
self.label = QtGui.QLabel(self)
self.label.setMinimumSize(QtCore.QSize(82, 0))
self.label.setText(str(translate("Spreadsheet","Cell"))+" A1 :")
self.horizontalLayout.addWidget(self.label)
self.lineEdit = QtGui.QLineEdit(self)
self.horizontalLayout.addWidget(self.lineEdit)
self.verticalLayout.addLayout(self.horizontalLayout)
# add table
self.table = QtGui.QTableWidget(30,26,self)
for i in range(26):
ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i]
self.table.setHorizontalHeaderItem(i, QtGui.QTableWidgetItem(ch))
self.verticalLayout.addWidget(self.table)
self.table.setCurrentCell(0,0)
self.spreadsheet = spreadsheet
self.update()
QtCore.QObject.connect(self.table, QtCore.SIGNAL("cellChanged(int,int)"), self.changeCell)
QtCore.QObject.connect(self.table, QtCore.SIGNAL("currentCellChanged(int,int,int,int)"), self.setEditLine)
QtCore.QObject.connect(self.lineEdit, QtCore.SIGNAL("returnPressed()"), self.getEditLine)
QtCore.QObject.connect(self, QtCore.SIGNAL("destroyed()"), self.destroy)
def destroy(self):
if DEBUG: print "Closing spreadsheet view"
if self.spreadsheet:
# before deleting this view, we remove the reference to it in the object
if hasattr(self.spreadsheet,"ViewObject"):
if self.spreadsheet.ViewObject:
if hasattr(self.spreadsheet.ViewObject.Proxy,"editor"):
del self.spreadsheet.ViewObject.Proxy.editor
if FreeCADGui:
if FreeCADGui.ActiveDocument:
FreeCADGui.ActiveDocument.resetEdit()
def update(self):
"updates the cells with the contents of the spreadsheet"
if self.spreadsheet:
controlled = self.spreadsheet.Proxy.getControlledCells(self.spreadsheet)
for cell in self.spreadsheet.Proxy._cells.keys():
if cell != "Type":
c,r = self.spreadsheet.Proxy.splitKey(cell)
c = "abcdefghijklmnopqrstuvwxyz".index(c)
r = int(str(r))-1
content = getattr(self.spreadsheet.Proxy,cell)
if self.spreadsheet.Proxy.isFunction(cell):
self.doNotChange = True
if content == None:
content = ""
if DEBUG: print "Updating ",cell," to ",content
if self.table.item(r,c):
self.table.item(r,c).setText(str(content))
else:
self.table.setItem(r,c,QtGui.QTableWidgetItem(str(content)))
if cell in controlled:
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
brush.setStyle(QtCore.Qt.Dense6Pattern)
self.table.item(r,c).setBackground(brush)
def changeCell(self,r,c,value=None):
"changes the contens of a cell"
if self.doNotChange:
if DEBUG: print "DoNotChange flag is set"
self.doNotChange = False
elif self.spreadsheet:
key = "abcdefghijklmnopqrstuvwxyz"[c]+str(r+1)
if not value:
value = self.table.item(r,c).text()
if DEBUG: print "Changing "+key+" to "+value
# store the entry as best as possible
try:
v = int(value)
except:
try:
v = float(value)
except:
try:
v = v = str(value)
except:
v = value
setattr(self.spreadsheet.Proxy,key,v)
self.update()
# TODO do not update the whole spreadsheet when only one cell has changed:
# use the _relations table and recursively update only cells based on this one
self.setEditLine(r,c)
def setEditLine(self,r,c,orr=None,orc=None):
"copies the contents of the active cell to the edit line"
if self.spreadsheet:
c = "abcdefghijklmnopqrstuvwxyz"[c]
r = r+1
if DEBUG: print "Active cell "+c+str(r)
from DraftTools import translate
self.label.setText(str(translate("Spreadsheet","Cell"))+" "+c.upper()+str(r)+" :")
content = self.spreadsheet.Proxy.getFunction(c+str(r))
if content == None:
content = ""
self.lineEdit.setText(str(content))
def getEditLine(self):
"called when something has been entered in the edit line"
txt = str(self.lineEdit.text())
if DEBUG: print "Text edited ",txt
if txt:
r = self.table.currentRow()
c = self.table.currentColumn()
self.changeCell(r,c,txt)
class _Command_Spreadsheet_Create:
"the Spreadsheet_Create FreeCAD command"
def GetResources(self):
return {'Pixmap' : 'Spreadsheet',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Spreadsheet_Create","Spreadsheet"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Spreadsheet_Create","Adds a spreadsheet object to the active document")}
def Activated(self):
from DraftTools import translate
FreeCAD.ActiveDocument.openTransaction(str(translate("Spreadsheet","Create Spreadsheet")))
FreeCADGui.doCommand("import Spreadsheet")
FreeCADGui.doCommand("Spreadsheet.makeSpreadsheet()")
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
class _Command_Spreadsheet_Controller:
"the Spreadsheet_Controller FreeCAD command"
def GetResources(self):
return {'Pixmap' : 'SpreadsheetController',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Spreadsheet_Controller","Add controller"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Spreadsheet_Controller","Adds a cell controller to a selected spreadsheet")}
def IsActive(self):
if FreeCADGui.Selection.getSelection():
return True
else:
return False
def Activated(self):
import Draft
if Draft.getType(FreeCADGui.Selection.getSelection()[0]) == "Spreadsheet":
from DraftTools import translate
n = FreeCADGui.Selection.getSelection()[0].Name
FreeCAD.ActiveDocument.openTransaction(str(translate("Spreadsheet","Add controller")))
FreeCADGui.doCommand("import Spreadsheet")
FreeCADGui.doCommand("Spreadsheet.makeSpreadsheetController(FreeCAD.ActiveDocument."+n+")")
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
def makeSpreadsheet():
"makeSpreadsheet(): adds a spreadsheet object to the active document"
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython","Spreadsheet")
Spreadsheet(obj)
if FreeCAD.GuiUp:
ViewProviderSpreadsheet(obj.ViewObject)
return obj
def makeSpreadsheetController(spreadsheet,cell=None,direction=None):
"""makeSpreadsheetController(spreadsheet,[cell,direction]): adds a
controller to the given spreadsheet. Call can be a starting cell such as "A5",
and direction can be "Horizontal" or "Vertical"."""
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython","CellController")
SpreadsheetController(obj)
if FreeCAD.GuiUp:
ViewProviderSpreadsheetController(obj.ViewObject)
conts = spreadsheet.Controllers
conts.append(obj)
spreadsheet.Controllers = conts
if cell:
obj.BaseCell = cell
if direction:
obj.Direction = direction
return obj
def addSpreadsheetView(view):
"addSpreadsheetView(view): adds the given spreadsheet view to the FreeCAD MDI area"
if FreeCAD.GuiUp:
import Spreadsheet_rc
mw = FreeCADGui.getMainWindow()
mdi = mw.findChild(QtGui.QMdiArea)
sw = mdi.addSubWindow(view)
sw.setWindowIcon(QtGui.QIcon(":/icons/Spreadsheet.svg"))
sw.show()
mdi.setActiveSubWindow(sw)
def open(filename):
"called when freecad opens a csv file"
import os
docname = os.path.splitext(os.path.basename(filename))[0]
doc = FreeCAD.newDocument(docname)
FreeCAD.ActiveDocument = doc
read(filename)
doc.recompute()
return doc
def insert(filename,docname):
"called when freecad wants to import a csv file"
try:
doc = FreeCAD.getDocument(docname)
except:
doc = FreeCAD.newDocument(docname)
FreeCAD.ActiveDocument = doc
read(filename)
doc.recompute()
return doc
def read(filename):
"creates a spreadsheet with the contents of a csv file"
sp = makeSpreadsheet()
import csv
with pyopen(filename, 'rb') as csvfile:
csvfile = csv.reader(csvfile)
rn = 1
for row in csvfile:
cn = 0
for c in row[:26]:
cl = "abcdefghijklmnopqrstuvwxyz"[cn]
#print "setting ",cl+str(rn)," ",c
try:
c = int(c)
except:
try:
c = float(c)
except:
c = str(c)
setattr(sp.Proxy,cl+str(rn),c)
cn += 1
rn += 1
print "successfully imported ",filename
def export(exportList,filename):
"called when freecad exports a csv file"
import csv, Draft
if not exportList:
print "Spreadsheet: Nothing to export"
return
obj = exportList[0]
if Draft.getType(obj) != "Spreadsheet":
print "Spreadhseet: The selected object is not a spreadsheet"
return
if not obj.Proxy._cells:
print "Spreadsheet: The selected spreadsheet contains no cell"
return
numcols = ("abcdefghijklmnopqrstuvwxyz".index(str(obj.Proxy.cols[-1])))+1
numrows = int(obj.Proxy.rows[-1])
with pyopen(filename, 'wb') as csvfile:
csvfile = csv.writer(csvfile)
for i in range(numrows):
r = []
for j in range(numcols):
key = "abcdefghijklmnopqrstuvwxyz"[j]+str(i+1)
if key in obj.Proxy._cells.keys():
r.append(str(obj.Proxy.getFunction(key)))
else:
r.append("")
csvfile.writerow(r)
print "successfully exported ",filename
FreeCADGui.addCommand('Spreadsheet_Create',_Command_Spreadsheet_Create())
FreeCADGui.addCommand('Spreadsheet_Controller',_Command_Spreadsheet_Controller())