##############################################################################
# ThanCad 0.0.9 "DoesSomething": 2dimensional CAD with raster support for engineers.
#
# Copyright (c) 2001-2009 Thanasis Stamos, August 23, 2009
# URL: http://thancad.sourceforge.net
# e-mail: cyberthanasis@excite.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details (www.gnu.org/licenses/gpl.html).
#
# You should have received a copy of the GNU 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
##############################################################################
"""\
ThanCad 0.0.9 "DoesSomething": 2dimensional CAD with raster support for engineers.
This module defines a hierarchical layer structure (class).
Each layer has a set of attributes. It is very easy to extend this set.
Each attribute has a type which defines the way the attribute is
inherited by a layer's children. Actually the layer does not care
what the attributes mean (i.e. color of elements, line type and
so on).
When an attribute is changed the layer class calls method thanUpdateElements
to do what is needed (e.g. change the color of the elements).
thanUpdateElements is inherited by ThanLayAtts.
"""
import weakref, copy
from Tkinter import *
import p_ggen
from thanvar import ThanId,Canc,ThanLayerError,THANBYPARENT,THANPERSONAL
from thanlaycon import *
import thanlayatts
THANMRECURS = 20 # Maximum layer tree depth
############################################################################
############################################################################
class ThanLayer:
"Class that holds the information of a layer object."
def __init__(self):
"Creates a layer node object."
pass
def thanTkSet(self, than, tstyles):
"Sets the drawing attributes according to layer."
ats = self.thanAtts
than.fill = than.outline = ats["moncolor"].thanTk
if not ats["fill"].thanVal: than.fill = ""
t = ats["textstyle"].thanVal
than.font = tstyles[t].thanFont
print "layer: thanTkSet: hidename=", ats["hidename"]
print "layer: thanTkSet: hideheight=", ats["hideheight"]
print "layer: thanTkSet: frozen=", ats["frozen"]
than.pointPlotname = not ats["hidename"].thanVal
than.pointPlotheight = not ats["hideheight"].thanVal
# than.penthick = ats["penthick"].thanVal # Note that penthickness in mm is irrelevent
# unless we know the dimensions of the screen
than.tkThick, _ = than.ct.global2LocalRel(ats["linethick"].thanVal, 0.0)
for a in thanlayatts.thanLayAttsNames[2:]:
ia = ats[a]
ia.thanAct = ia.thanVal
def thanPilSet(self, than, dpi, tstyles):
"Sets the drawing attributes for a PIL image according to layer."
c = self.thanAtts["moncolor"].thanVal
if c == (255,255,255): c = 0,0,0 # Invert white and ..
elif c == ( 0, 0, 0): c = 255,255,255 # ..black
if than.mode == "1":
c = int(c[0]*0.299+c[1]*0.587+c[2]*0.114+0.49) # Convert to gray
if c < 255: c = 0 # Everything is black except pure white
elif than.mode == "L":
c = int(c[0]*0.299+c[1]*0.587+c[2]*0.114+0.49) # Convert to gray
than.fill = than.outline = c
if not self.thanAtts["fill"].thanVal: than.fill = None
t = self.thanAtts["textstyle"].thanVal
than.thanFont = tstyles[t].thanFont
w1, _ = than.ct.global2LocalRel(self.thanAtts["linethick"].thanVal, 0.0)
w2 = self.thanAtts["penthick"].thanVal*dpi/25.4
than.rwidth = max(w1, w2)
def thanPdfSet(self, than, tstyles):
"Sets the drawing attributes for a PIL image according to layer."
t = self.thanAtts["textstyle"].thanVal
than.thanFont = tstyles[t].thanFont
return
c = self.thanAtts["moncolor"].thanVal
if c == (255,255,255): c = 0,0,0 # Invert white and ..
elif c == ( 0, 0, 0): c = 255,255,255 # ..black
if than.mode == "1":
c = int(c[0]*0.299+c[1]*0.587+c[2]*0.114+0.49) # Convert to gray
if c < 255: c = 0 # Everything is black except pure white
elif than.mode == "L":
c = int(c[0]*0.299+c[1]*0.587+c[2]*0.114+0.49) # Convert to gray
than.fill = than.outline = c
if not self.thanAtts["fill"].thanVal: than.fill = ""
def thanRename(self, name):
"Renames a layer."
name = name.strip()
if name=="" or " " in name or name[0]==".": # Check name
raise ThanLayerError, "Illegal layer name: "+name
if self.thanParent != None: # Root layer has no parent and no siblings
for lay in self.thanParent.thanChildren:
if str(lay.thanAtts[THANNAME]) == name: # Check unique name
raise ThanLayerError, "Duplicate layer name: " + name
self.thanAtts[THANNAME].thanVal = name
def thanUnlink(self):
"""Unlinks the hierarchy beginning with self from the children of self's parent."
This is dangerous because the elements of the self will become
orphants. This may lead the program to crash. So it must be used
only if we know that there are no elements in the hierarchy.
Or, the hierarchy must be inserted somewhere else.
Note that the hierarchy is only unlinked; it is not deleted.
"""
par = self.thanParent
assert par != None, "Well we can't delete root layer. How on earth was root accessed?!"
i = par.thanChildren.index(self)
del par.thanChildren[i]
return self
def thanDestroy(self):
"""Destroys the circular references, so that layer can be recycled.
This is dangerous because the elements of self and of all children
of self, will become orphants. This may lead to program crash.
So it must be used only if we know that there are no elements in
the hierarchy. Or, the hierarchy must be inserted somewhere else.
Or that we have an exact copy so that the elements will have
the copy as a parent."""
for chlay in self.thanChildren: chlay.thanDestroy()
del self.thanChildren
del self.lt, self.thanParent
def thanChildAdd(self, lays):
"""Adds some lays as children of self.
lays are complete layer hierarchies and they are assumed to be ok,
except from their parent.
If a name of lays duplicates one of self's current children, we try
to rename it. If all renames are succesful, we add the children."""
if len(lays) <= 0: return
names = [str(lay.thanAtts[THANNAME]) for lay in self.thanChildren]
namen = {}
for lay in lays:
name1 = name = str(lay.thanAtts[THANNAME])
i = 0
while name1 in names:
name1 = name + str(i)
i += 1
if i > 1000: raise ThanLayerError, "Can not rename duplicate layer: "+name+"; try to rename some layers."
namen[lay] = name1
names.append(name1)
self.thanMove2child() # If childless, move all elements to a new child
for lay in lays:
lay.thanAtts[THANNAME].thanVal = namen[lay]
self.thanChildren.append(lay)
lay.thanParent = self
return self
#===========================================================================
def thanChildNew(self, name=None):
"Creates a new child layer; if something is wrong, an exception is raised and no changes are made."
if self.__getDepth() > THANMRECURS: raise ThanLayerError, "Layer nested too deep."
chlay = self.__newEmptyLeaf(name) # Create child here to check for error early
self.thanMove2child() # If self is leaf, create new child which inherits the elements
self.thanChildren.append(chlay) # Now, add an empty child
return chlay
def __newEmptyLeaf(self, name):
"""Creates a new empty leaf layer, intented to be child of self.
The newly created has self as parent, but it does not belong
to self's children yet.
The attributes of the newly created layer are an exact copy of the
attributes of self.
"""
#-------Check name or try to find a unique name
if name != None:
names = [str(lay.thanAtts[THANNAME]) for lay in self.thanChildren]
name = name.strip()
if name=="" or " " in name or name[0]==".": raise ThanLayerError, "Illegal layer name: " + name
if name in names: raise ThanLayerError, "Duplicate layer name: " + name
else:
name = self.thanChildUniqName()
#-------Create new layer
lay = ThanLayer()
lay.lt = self.lt
lay.thanParent = self
lay.thanChildren = []
lay.thanTag = self.lt.thanIdLay.new() # In order to exploit TK mechanism
lay.thanQuad = set() # This holds the elements of the layer
lay.thanAtts = {}
for a, val in thanlayatts.thanLayAtts.iteritems():
defval = self.thanAtts[a].thanPers
class_ = val[3]
lay.thanAtts[a] = class_(defval)
lay.thanAtts[a].thanValSet(self.thanAtts[a].thanVal)
lay.thanAtts[THANNAME].thanVal = name
return lay
def thanChildUniqName(self):
"Return a uninque child name (not equal to other children)."
names = [str(lay.thanAtts[THANNAME]) for lay in self.thanChildren]
for i in xrange(1000): # Try to create unique name
name = "newlayer" + str(i)
if name not in names: break
else:
raise ThanLayerError, "Can not create unique name 'newlayerxxx'; try to rename some layers."
return name
def thanMove2child(self, name=None, force=False):
"""Move elements to a child.
If self is childless, a child is created which inherits the tag of
self and, thus, its elements. Self gets no tag and, thus, it has
no elements.
If layer has no elements then no child is created.
"""
if len(self.thanChildren) > 0: return
if not force and len(self.thanQuad) == 0: return
if self.__getDepth() > THANMRECURS: raise ThanLayerError, "Layer nested too deep."
if name == None: name = str(self.thanAtts[THANNAME])+"child"
lay = self.__newEmptyLeaf(name) # No errors expected
lay.thanQuad = self.thanQuad # inherit self's elements
lay.thanTag = self.thanTag # inherit self's elements
self.thanQuad = self.thanTag = None # parents do not have elements
self.thanChildren = [lay]
return lay
def __getDepth(self):
"Finds the nested level of layer."
# Note that when ThanCad displays the layer control widget, the parent of layer "0"
# is not self.lt.thanRoot, but another root layer, created temporarily for the widget.
# So we check for root layer with the condition: lay.thanParent == None
par = self
for i in xrange(THANMRECURS):
if par.thanParent == None: return i
par = par.thanParent
return THANMRECURS + 1
def thanClone(self, parent=None):
"""Creates a new layer hierarchy copying self's children.
This routine copies only the hierarchy including tags. It does not
copy elements, it copies only a reference to the elements.
In effect the new layer is created with the same name, parent, tag,
atributes, with the same (cloned) children and references to the
same elements.
"""
lay = ThanLayer()
lay.lt = self.lt
if parent == None: lay.thanParent = self.thanParent
else : lay.thanParent = parent
lay.thanTag = self.thanTag # In order to exploit TK mechanism
lay.thanQuad = self.thanQuad # This holds the elements of the layer
lay.thanAtts = copy.deepcopy(self.thanAtts)
lay.thanChildren = [chlay.thanClone(lay) for chlay in self.thanChildren]
return lay
def __del__(self):
print "Memory of layer", str(self.thanAtts[THANNAME]), " is recycled"
def thanSetAtts(self, leaflayers, attname, newval):
"Sets the attributes of the layer self, propagates the attributes and returns the leaflayers affacted."
for a,val in (attname, newval),:
assert a in thanlayatts.thanLayAtts, "Unknown layer attribute: " + a
ia = self.thanAtts[a]
if val == THANBYPARENT:
if self.thanParent == None: continue # Root element can't inherit
ia.thanInher = True
val = self.thanParent.thanAtts[a].thanVal # The parent's attribute (which ISN'T THANBYPARENT)
elif val == THANPERSONAL:
ia.thanInher = False
val = ia.thanPers
else:
val = val.thanVal
ia.thanInher = False
ia.thanPers = val
if val == ia.thanVal: continue # Happens to have the correct value
ia.thanValSet(val)
if len(self.thanChildren) == 0: # No children - leaf ThanLayer.
leaflayers.setdefault(self, {})[a] = val
else: # Only leaf ThanLayer has elements
if thanlayatts.thanLayAtts[a][0] == 0: # Propagate this value to the children..
self.thanPropAttByParent(leaflayers, a, val) # Propagate only if by parent
else:
self.thanPropAttForce(leaflayers, a, val) # Propagate unconditionally
def thanPropAttByParent(self, leaflayers, a, val):
"Propagates an attribute of type 0 to the layer's children, if by parent."
lay2see = self.thanChildren[:] # These layers (and their children) must be inspected
while len(lay2see) > 0:
lay = lay2see.pop()
ia = lay.thanAtts[a]
if not ia.thanInher: continue # Child Layer does not inherit
if lay.thanAtts[a].thanVal == val: continue # Child Layer happens to have the correct value
ia.thanValSet(val)
if len(lay.thanChildren) == 0:
leaflayers.setdefault(lay, {})[a] = val # only leaf ThanLayer has elements
else:
lay2see.extend(lay.thanChildren)
def thanPropAttForce(self, leaflayers, a, val):
"Propagates an attribute of type 1,2 to the layer's children, while not resetting the values."
lay2see = self.thanChildren[:] # These layers (and their children) must be inspected
while len(lay2see) > 0:
lay = lay2see.pop(0)
if val: val1 = val # If val==False
else: val1 = lay.thanAtts[a].thanPers # Attribute can not be inherited
if lay.thanAtts[a].thanVal == val1: continue # Layer already with this value
lay.thanAtts[a].thanValSet(val1)
if len(lay.thanChildren) == 0:
leaflayers.setdefault(lay, {})[a] = val1 # only leaf ThanLayer has elements
else:
lay2see.extend(lay.thanChildren)
def thanPropAttAll(self, leaflayers, a, val):
"""Sets attribute a (of type 0) to self and all children.
The attribute of a is set as personal. The attributes of the children
are set as inherited."""
assert thanlayatts.thanLayAtts[a][0] == 0, "thanPropAttAll() works for type 0 attributes."
ia = self.thanAtts[a]
ia.thanValSet(val)
ia.thanPers = ia.thanVal
ia.thanInher = False
lay2see = self.thanChildren[:] # These layers (and their children) must be inspected
while len(lay2see) > 0:
lay = lay2see.pop(0)
ia = lay.thanAtts[a]
ia.thanValSet(val)
ia.thanInher = True
if len(lay.thanChildren) == 0:
leaflayers.setdefault(lay, {})[a] = val # only leaf ThanLayer has elements
else:
lay2see.extend(lay.thanChildren)
#===========================================================================
# def thanPropAttRestore(self, a, val):
# "Propagates and restores attribute of the layer's children."
#
# layers = [ ]
# lay2see = self.thanChildren[:]
# while len(lay2see) > 0:
# lay = lay2see.pop()
# if len(lay.thanChildren) == 0:
# if lay.thanAtts[a] != val: layers.append(lay) # only leaf ThanLayer has elements
## else:
# lay2see.extend(lay.thanChildren)
#
##-------Group layers with the same value
#
# while len(layers) > 0:
## lay = layers.pop()
# val = lay.thanAtts[a]
# layersSameVal = [lay]
# layersOtherVal = [ ]
# while len(layers) > 0:
# lay = layers.pop()
# if lay.thanAtts[a] == val:
# layersSameVal.append(lay)
# else:
# layersOtherVal.append(lay)
#
# self.thanUpdateElements(layersSameVal, a, val) # Update all the layers with the same value
# layers = layersOtherVal
#
def thanExpDxf(self, fDxf, pref):
"Exports this layer and its children to dxf."
e = fDxf.thanDxfCrLayer; a = col = None
for lay in self.thanChildren:
a = lay.thanAtts
e(name=pref+str(a[THANNAME]), color=a["moncolor"].thanDxf(), frozen=a["frozen"].thanVal)
del e, a
for lay in self.thanChildren: lay.thanExpDxf(fDxf, pref+str(lay.thanAtts[THANNAME])+THANLC)
def thanGetPathname(self, sep="/"):
"Finds the root layer."
root = self
i = 0; m = THANMRECURS
name = str(self.thanAtts[THANNAME])
while root.thanParent != None:
i += 1
if i > m: break # Recursive limit found; return incomplete name
root = root.thanParent
if root.thanParent == None: break # Avoid "root" in pathname
name = sep.join((str(root.thanAtts[THANNAME]), name))
return name
def thanFind(self, names):
"Finds a layer whose name, parent, grandparent .. are in names."
for lay in self.thanChildren:
if str(lay.thanAtts[THANNAME]) == names[0]:
if len(names) == 1: return lay
return lay.thanFind(names[1:])
return None
def thanFindic(self, names):
"Finds a layer whose name, parent, grandparent .. are in names; ignore case."
name0 = names[0].lower()
for lay in self.thanChildren:
if str(lay.thanAtts[THANNAME]).lower() == name0:
if len(names) == 1: return lay
return lay.thanFindic(names[1:])
return None
def thanIsEmpty(self):
"Returns true if layer has no elements nor children, or no children with elements."
if self.thanParent == None: return False # Root layer is considered non-empty
if len(self.thanChildren) == 0:
return len(self.thanQuad) == 0
for chlay in self.thanChildren:
if not chlay.thanIsEmpty(): return False
return True
#===========================================================================
def pr(self, a=None, b=""):
if a == None:
print b, str(self.thanAtts[THANNAME])
else:
ia = self.thanAtts[a]
print b, str(self.thanAtts[THANNAME]), ia.thanVal, "=", ia.thanAct, ia.thanPers, ia.thanInher
for lay in self.thanChildren:
lay.pr(a, b+" ")
############################################################################
############################################################################
class ThanLayerTree:
"Class that holds a tree of layers."
def __init__(self):
"Creates the root of tree of layers."
self.thanIdLay = ThanId(prefix="L") # Normally there should be a ThanId for each layer tree (that is for each
# drawing). But since we expect neither too many layers nor too many
# drawings we use a common ThanId for all drawings.
self.thanRoot = ThanLayer()
self.thanMakeRoot(self.thanRoot)
self.thanDictRebuild()
def thanDestroy(self):
"Break circular references."
del self.thanRoot, self.dilay
def __getstate__(self):
odict = self.__dict__.copy()
del odict["dilay"] # Do not save weak dictionary (it is redundant after all)
return odict
def __setstate__(self, odict):
self.__dict__.update(odict)
self.thanDictRebuild() # Recreate weak dictionary
def thanDictRebuild(self, lay=None):
"Rebuilds dictionary of tags to layers."
if lay == None:
self.dilay = weakref.WeakValueDictionary()
lay = self.thanRoot
# print "thanDictRebuild(): now doing layer:", dir(lay)
if len(lay.thanChildren) == 0: # Only leaf layers have elements (and tags)
self.dilay[lay.thanTag] = lay
else:
for chlay in lay.thanChildren: self.thanDictRebuild(chlay)
def thanMakeRoot(self, root, name="Root"):
"Makes self the root of new layer tree."
# root.lt = weakref.proxy(self)
root.lt = self
root.thanParent = None
root.thanChildren = []
root.thanTag = root.lt.thanIdLay.new() # In order to exploit TK mechanism
root.thanQuad = set() # This holds the elements of the layer
root.thanAtts = {}
for a,val in thanlayatts.thanLayAtts.iteritems():
defval = val[2]
class_ = val[3]
root.thanAtts[a] = class_(defval, False)
root.thanAtts[THANNAME].thanVal = name
self.thanCur = root.thanChildNew("0")
def thanFind(self, pathname):
"Find the layer object from its pathname."
names = pathname.split("/")
return self.thanRoot.thanFind(names)
def thanFindic(self, pathname):
"Find the layer object from its pathname; ignore case."
names = pathname.split("/")
return self.thanRoot.thanFindic(names)
def thanExpDxf(self, fDxf):
"Exports this layer and its children to dxf."
e = fDxf.thanDxfCrLayer
a = self.thanRoot.thanAtts
e(name=THANLC+"root", color=a["moncolor"].thanDxf(), frozen=a["frozen"].thanVal)
del e, a
self.thanRoot.thanExpDxf(fDxf, "")
#===========================================================================
def __del__(self):
print "Memory of layertree is recycled"
#############################################################################
#############################################################################
#MODULE LEVEL FUNCTIONS
def test():
"Tests creation and alteration of layers."
print __doc__
print "Module thanlayer test"
laytree = testcreate()
laytree.thanDictRebuild()
for key in laytree.dilay:
print key, ':', str(laytree.dilay[key].thanAtts[THANNAME])
# testalter(laytree)
# testgui(laytree)
print "Tests memory leaks."
laytree.thanRoot.thanDestroy()
laytree.thanRoot = 1
print "---------------------------------------------------"
#===========================================================================
def testgui(lay):
"Tests Tkinter layer gui."
# atts = ("expand", "name", "color", "visibility", "plotcolor", "textstyle")
# widths = (1, 40, 15, 2, 15, 15)
win = Tk()
win.title("Test ThanLayers")
li = ThantkClist5(win, objs=[lay], atts=thanlayatts.thanLayAttsNames,
widths=thanlayatts.thanLayAttsWidths, height=15,
vscroll=1, hscroll=1, onclick=onclick)
# li = ThantkClist1(win, atts=atts, widths=widths, height=15,
# vscroll=1, hscroll=1, onclick=onclick)
li.grid()
win.mainloop()
# print li.getResult()
#===========================================================================
def testcreate():
"Creates a layer hierarchy."
laytree = ThanLayerTree()
ch = laytree.thanRoot.thanChildNew("cxxx")
ch_2 = laytree.thanRoot.thanChildNew("cxxx1")
ch1 = ch.thanChildNew("jjjlls")
ch2 = ch.thanChildNew("jjjlls2")
ch3 = ch1.thanChildNew("j1")
ch4 = ch1.thanChildNew("j2")
laytree.thanRoot.pr()
print "-------------------------------"
return laytree
#===========================================================================
def testalter(laytree):
"Tests alteration of layers."
att = "frozen"
val1 = "1"
val2 = "0"
att = "moncolor"
val1 = "2"
val2 = "0"
ch = laytree.thanRoot.thanChildren[2]
ch1 = ch.thanChildren[1]
ch4 = ch1.thanChildren[2]
ch4.thanSetAtts(**{att : 777})
print "------------------------------------", att, "=", val1
ch.thanSetAtts(**{att : val1})
laytree.thanRoot.pr(att)
print "------------------------------------", att, "=", val2
ch.thanSetAtts(**{att : val2})
laytree.thanRoot.pr(att)
#############################################################################
#############################################################################
#MODULE LEVEL CODE. IT IS EXECUTED ONLY ONCE
if __name__ == "__main__":
print __doc__
test()
|