Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F25243854
svgTreeManager.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
svgTreeManager.py
View Options
#import xml.etree.cElementTree as ET
from
lxml
import
etree
import
re
from
math
import
*
SVG_NS
=
"http://www.w3.org/2000/svg"
# Class to hanle XML trees in SVG files.
class
SvgTreeManager
:
def
__init__
(
self
,
arg
):
self
.
svg
=
arg
.
encode
(
'utf-8'
)
parser
=
etree
.
XMLParser
(
ns_clean
=
True
,
recover
=
True
,
encoding
=
'utf-8'
)
self
.
tree
=
etree
.
fromstring
(
self
.
svg
,
parser
=
parser
)
# ----------------------------------------
# Attempts to return dimensions of the viewport
def
characteristicDimensions
(
self
):
root
=
self
.
tree
.
getroottree
()
.
getroot
()
viewBox
=
root
.
get
(
'viewBox'
)
if
viewBox
:
return
max
([
float
(
x
)
for
x
in
viewBox
.
split
()])
width
=
root
.
get
(
'width'
)
height
=
root
.
get
(
'height'
)
if
(
width
and
height
):
return
max
([
float
(
width
),
float
(
height
)])
return
(
600
)
# ----------------------------------------
# returns a list of SVG Paths with that name. Default is void, which
# returns all SVG:Paths
def
findPath
(
self
,
name
=
""
):
work
=
[]
for
node
in
self
.
tree
.
findall
(
'.//{
%s
}path'
%
SVG_NS
):
if
(
name
in
self
.
getNodeID
(
node
)):
work
.
append
(
node
)
return
work
# ----------------------------------------
# returns parent node of current XML node
def
findParent
(
self
,
node
):
return
node
.
find
(
".."
)
# ----------------------------------------
# returns a list of parent nodes of current XML node in ascending order
# (root will bit last in the list)
def
findParents
(
self
,
node
):
currentNode
=
node
ancestry
=
[]
while
True
:
currentNode
=
self
.
findParent
(
currentNode
)
if
currentNode
is
None
:
break
else
:
ancestry
.
append
(
currentNode
)
return
ancestry
#-----------------------------------------
# Return node ID
def
getNodeID
(
self
,
node
):
nodeId
=
node
.
get
(
'id'
)
if
not
node
.
get
(
'id'
):
nodeId
=
node
.
find
(
".."
)
.
get
(
'id'
)
return
nodeId
# ----------------------------------------
# returns transformation (as in
# transform="matrix(15.278846,0,0,15.278846,3144.1413,1135.7902)"
# notation)
def
getTransform
(
self
,
node
):
return
node
.
get
(
'transform'
)
# ----------------------------------------
# returns local transformation matrix (tranf. mat. defined in the element
# itself)
def
getTransformMatrix
(
self
,
node
):
return
toMatrix
(
self
.
getTransform
(
node
))
# ----------------------------------------
# returns overall tranformation matrix of parent node (even if nested)
def
getParentsTransformMatrix
(
self
,
node
):
matrices
=
[]
for
j
in
self
.
findParents
(
node
):
n
=
toMatrix
(
self
.
getTransform
(
j
))
matrices
.
append
(
n
)
matrices
.
reverse
()
m
=
[
1
,
0
,
0
,
1
,
0
,
0
]
for
j
in
matrices
:
m
=
multiplySvgMatrices
(
m
,
j
)
return
m
#m = [1,0,0,1,0,0]
# for j in self.findParents(node):
#n = toMatrix( self.getTransform(j) )
##m = multiplySvgMatrices (n, m)
#m = multiplySvgMatrices (m, n)
# return m
# ----------------------------------------
# returns total transformation matrix, applying parent transformations
# (even if nested).
def
getTotalTransformMatrix
(
self
,
node
):
a
=
self
.
getParentsTransformMatrix
(
node
)
b
=
self
.
getTransformMatrix
(
node
)
# return multiplySvgMatrices(b, a)
return
multiplySvgMatrices
(
a
,
b
)
# return [1,0,0,1,0,0]
# ----------------------------------------
# computes the barycentre of the points of a path.
def
centreOfMass
(
self
,
path
):
coordinatesX
=
(
self
.
extractPoints
(
path
))[
0
]
coordinatesY
=
(
self
.
extractPoints
(
path
))[
1
]
# barycentreX = sum(coordinatesX)/len(coordinatesX) # + offsetX
# barycentreY = sum(coordinatesY)/len(coordinatesY) # + offsetY
# barycentreX = ( max(coordinatesX) - min(coordinatesX) )/2.0 + min(coordinatesX) # + offsetX
# barycentreY = ( max(coordinatesY) - min(coordinatesY) )/2.0 +
# min(coordinatesY) # + offsetY
barycentreX
=
median
(
coordinatesX
)
barycentreY
=
median
(
coordinatesY
)
#................................
barycentre
=
[
barycentreX
,
barycentreY
]
return
barycentre
# ----------------------------------------
# Extracts significant points from a path, applying transforms for
# Translation and Matrix (removes control points from path).
def
extractPoints
(
self
,
path
):
from
itertools
import
islice
import
re
# This will extract the endpoints of the segments forming the path
# (removing control points)
coordinatesX
=
[]
coordinatesY
=
[]
pathData
=
path
.
get
(
'd'
)
pathData
=
re
.
findall
(
'[a-df-zA-DF-Z]+|[
\\
d.eE\-]+'
,
pathData
)
# To separate lettres from numbers. E is special case (exponent)
path_iter
=
iter
(
pathData
)
#................................
state
=
"start"
typeOfSegment
=
""
# can take values MLHVCSQTAZmlhvcsqtaz
previousX
=
0.0
previousY
=
0.0
for
i
in
path_iter
:
i
=
i
.
replace
(
','
,
''
)
if
(
state
==
"start"
):
# if (typeOfSegment in "MLHVCSQTAZ"): # Absolute coordinates
#previousX = 0.0
#previousY = 0.0
# Do NOT break here! It's very wrong for countries with non-continuguous land masses (islands).
# Might look all right for the USSR or if France is represented by her metropolitan area,
# but it will represent Great Britain with Ootsta and Germany with List in Schleswig-Holstein.
# if (i in "Zz"): # We have encountered a STOP signal. Exit loop.
# break
if
(
i
in
"Zz"
):
continue
# We have encountered a new segment-type. Set typeOfSegment
# accordingly.
if
(
i
in
"MLHVCSQTAmlhvcsqta"
):
typeOfSegment
=
i
continue
# Setting states to perform operations
# Commands that take 2 arguments
if
(
typeOfSegment
in
"MmLlHhVvTt"
):
state
=
"recieveX"
# Commands that take 4 arguments
if
(
typeOfSegment
in
"SsQq"
):
state
=
"wait2"
# Commands that take 6 arguments
if
(
typeOfSegment
in
"Cc"
):
state
=
"wait4"
# Commands that take 7 arguments
if
(
typeOfSegment
in
"Aa"
):
state
=
"wait5"
if
(
state
==
"recieveX"
):
if
(
typeOfSegment
in
"MLHVCSQTAZ"
):
previousX
=
0.0
newX
=
float
(
i
)
+
previousX
coordinatesX
.
append
(
newX
)
previousX
=
newX
state
=
"recieveY"
continue
if
(
state
==
"recieveY"
):
if
(
typeOfSegment
in
"MLHVCSQTAZ"
):
previousY
=
0.0
newY
=
float
(
i
)
+
previousY
coordinatesY
.
append
(
newY
)
# if not (coordinatesX == [] or coordinatesY == []):
#print ("\tSegType=", typeOfSegment, "\tCoordinates: ", coordinatesX[-1], coordinatesY[-1], "\tPreviousY: ", previousY, "\tnewY", newY, "\tfloat(i)", float(i))
previousY
=
newY
state
=
"start"
continue
if
(
state
==
"wait5"
):
next
(
islice
(
path_iter
,
3
,
4
),
''
)
state
=
"recieveX"
continue
if
(
state
==
"wait4"
):
next
(
islice
(
path_iter
,
2
,
3
),
''
)
state
=
"recieveX"
continue
if
(
state
==
"wait2"
):
next
(
islice
(
path_iter
,
0
,
1
),
''
)
state
=
"recieveX"
continue
#................................
# if (transformData):
#newCoordinatesX = []
#newCoordinatesY = []
#transformDataValues = re.findall(r"[-+]?\d*\.\d+|\d+", transformData)
# if "translate" in transformData:
# for coordinateX, coordinateY in zip(coordinatesX, coordinatesY):
#newCoordinatesX.append( coordinateX + float(transformDataValues[0]) )
#newCoordinatesY.append( coordinateY + float(transformDataValues[1]) )
# if "matrix" in transformData:
# for coordinateX, coordinateY in zip(coordinatesX, coordinatesY):
#newCoordinatesX.append( float(transformDataValues[0])*coordinateX + float(transformDataValues[2])*coordinateY + float(transformDataValues[4]) )
#newCoordinatesY.append( float(transformDataValues[1])*coordinateX + float(transformDataValues[3])*coordinateY + float(transformDataValues[5]) )
#coordinatesX = newCoordinatesX
#coordinatesY = newCoordinatesY
M
=
self
.
getTotalTransformMatrix
(
path
)
newCoordinatesX
=
[]
newCoordinatesY
=
[]
for
coordinateX
,
coordinateY
in
zip
(
coordinatesX
,
coordinatesY
):
v
=
[
coordinateX
,
coordinateY
]
v
=
multiplySvgMatrixVector
(
M
,
v
)
newCoordinatesX
.
append
(
v
[
0
])
newCoordinatesY
.
append
(
v
[
1
])
return
[
newCoordinatesX
,
newCoordinatesY
]
# return [coordinatesX, coordinatesY]
#===============================
# Stand-alone functions
#
# This is a procedural marxist utopia:
# classless subroutines.
#===============================
# ---------------------
# detects if a string is a float
def
isFloat
(
str
):
try
:
float
(
str
)
except
ValueError
:
return
False
return
True
# matrice-vector multiplication
# matrix = [a,b,c,d,e,f]
# vector = [x, y]
def
multiplySvgMatrixVector
(
M
,
V
):
a
=
M
[
0
]
b
=
M
[
1
]
c
=
M
[
2
]
d
=
M
[
3
]
e
=
M
[
4
]
f
=
M
[
5
]
x
=
V
[
0
]
y
=
V
[
1
]
x_out
=
a
*
x
+
c
*
y
+
e
y_out
=
b
*
x
+
d
*
y
+
f
return
[
x_out
,
y_out
]
# transform="matrix(a,b,c,d,e,f)"
def
multiplySvgMatrices
(
M1
,
M2
):
a1
=
M1
[
0
]
b1
=
M1
[
1
]
c1
=
M1
[
2
]
d1
=
M1
[
3
]
e1
=
M1
[
4
]
f1
=
M1
[
5
]
a2
=
M2
[
0
]
b2
=
M2
[
1
]
c2
=
M2
[
2
]
d2
=
M2
[
3
]
e2
=
M2
[
4
]
f2
=
M2
[
5
]
a3
=
a1
*
a2
+
c1
*
b2
b3
=
b1
*
a2
+
d1
*
b2
c3
=
a1
*
c2
+
c2
*
d2
d3
=
b1
*
c2
+
d1
*
d2
e3
=
a1
*
e2
+
c1
*
f2
+
e1
f3
=
b1
*
e2
+
d1
*
f2
+
f1
return
[
a3
,
b3
,
c3
,
d3
,
e3
,
f3
]
# transform="matrix(a,b,c,d,e,f)"
def
addSvgMatrices
(
M1
,
M2
):
a1
=
M1
[
0
]
b1
=
M1
[
1
]
c1
=
M1
[
2
]
d1
=
M1
[
3
]
e1
=
M1
[
4
]
f1
=
M1
[
5
]
a2
=
M2
[
0
]
b2
=
M2
[
1
]
c2
=
M2
[
2
]
d2
=
M2
[
3
]
e2
=
M2
[
4
]
f2
=
M2
[
5
]
a3
=
a1
+
a2
b3
=
b1
+
b2
c3
=
c1
+
c2
d3
=
d1
+
d2
e3
=
e1
+
e2
f3
=
f1
+
f2
return
[
a3
,
b3
,
c3
,
d3
,
e3
,
f3
]
# ----------------------------------------
# Converts SVG code describing transformations into 6-element matrices
def
toMatrix
(
string
):
work
=
[
1
,
0
,
0
,
1
,
0
,
0
]
# identity in this algebra
# cf http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Matrix_Algebra
# Matrix (3x3, only 6 variable componants):
# ( a c e )
# ( b d f )
# ( 0 0 1 )
# useful testing tool: https://petercollingridge.appspot.com/svg-transforms
if
string
is
None
:
return
work
regex
=
re
.
compile
(
'[\),\(, ,]'
)
string
=
list
(
filter
(
None
,
re
.
split
(
regex
,
string
)))
#print (string)
state
=
""
items
=
iter
(
string
)
for
item
in
items
:
#a = b = c = d = e = f = 0
# State machine
if
(
"translate"
in
item
):
state
=
"translate"
if
(
"rotate"
in
item
):
state
=
"rotate"
if
(
"scale"
in
item
):
state
=
"scale"
if
(
"skewX"
in
item
):
state
=
"skewX"
if
(
"skewY"
in
item
):
state
=
"skewY"
if
(
"matrix"
in
item
):
state
=
"matrix"
# Translation: two attributes
if
(
state
==
"translate"
):
e
=
float
(
next
(
items
))
f
=
float
(
next
(
items
))
work
=
multiplySvgMatrices
(
work
,
[
1
,
0
,
0
,
1
,
e
,
f
])
#work = multiplySvgMatrices ( [1, 0, 0 , 1, e , f], work )
# Rotation: one or three attributes
# transform="rotate(15)" --> rotation of 15 degrees clockwise around (0,0)
# transform="rotate(15, 40, 40)" --> rotation of 15 degrees clockwise
# around (40,40)
if
(
state
==
"rotate"
):
angle
=
radians
(
float
(
next
(
items
)))
try
:
x
=
next
(
items
)
except
StopIteration
:
work
=
multiplySvgMatrices
(
work
,
[
cos
(
angle
),
sin
(
angle
),
-
sin
(
angle
),
cos
(
angle
),
0
,
0
])
#work = multiplySvgMatrices ( [cos(angle), sin(angle),-sin(angle),cos(angle),0,0], work )
break
if
not
isFloat
(
x
):
work
=
multiplySvgMatrices
(
work
,
[
cos
(
angle
),
sin
(
angle
),
-
sin
(
angle
),
cos
(
angle
),
0
,
0
])
#work = multiplySvgMatrices ( [cos(angle), sin(angle),-sin(angle),cos(angle),0,0], work )
state
=
x
else
:
x
=
float
(
x
)
y
=
float
(
next
(
items
))
e
=
-
x
*
cos
(
angle
)
+
y
*
sin
(
angle
)
+
x
f
=
-
x
*
sin
(
angle
)
-
y
*
cos
(
angle
)
+
y
work
=
multiplySvgMatrices
(
work
,
[
cos
(
angle
),
sin
(
angle
),
-
sin
(
angle
),
cos
(
angle
),
e
,
f
])
#work = multiplySvgMatrices ( [cos(angle), sin(angle),-sin(angle),cos(angle), e, f], work )
# Scale: one or two arguments
# transform="scale(2)" --> Homotethy factor 2 (multiplies coordinates, heigth and width
# transform="scale(2,3)" --> Homotethy factor 2 on X axis and
# 3 on Y axis
if
(
state
==
"scale"
):
a
=
float
(
next
(
items
))
try
:
b
=
next
(
items
)
except
StopIteration
:
work
=
multiplySvgMatrices
(
work
,
[
a
,
0
,
0
,
a
,
0
,
0
])
#work = multiplySvgMatrices ( [ a, 0, 0, a, 0, 0 ], work )
break
if
not
isFloat
(
b
):
work
=
multiplySvgMatrices
(
work
,
[
a
,
0
,
0
,
a
,
0
,
0
])
#work = multiplySvgMatrices ( [ a, 0, 0, a, 0, 0 ], work )
state
=
b
else
:
b
=
float
(
b
)
work
=
multiplySvgMatrices
(
work
,
[
a
,
0
,
0
,
b
,
0
,
0
])
#work = multiplySvgMatrices ( [ a, 0, 0, b, 0, 0 ], work )
# Skew: one attribute. Exists in "SkewX" and in "SkewY" flavours.
if
(
state
==
"skewX"
):
a
=
radians
(
float
(
next
(
items
)))
work
=
multiplySvgMatrices
(
work
,
[
1
,
0
,
tan
(
a
),
1
,
0
,
0
])
#work = multiplySvgMatrices ( [1, 0, tan(a), 1, 0 , 0], work )
if
(
state
==
"skewY"
):
a
=
radians
(
float
(
next
(
items
)))
work
=
multiplySvgMatrices
(
work
,
[
1
,
tan
(
a
),
0
,
1
,
0
,
0
])
#work = multiplySvgMatrices ( [1, tan(a), 0 , 1, 0 , 0], work )
# General matrix: only six attributes
# NB: in case you were wondering: this allows parsing lines such as
# transform "translate(-1539.5729,-974.79019) matrix(1, 3.14, 23, 6, 0.7462, 42) rotate(50, -1000, 314) scale(2, 3)"
# It should not happen in an sane environment, but we want to be
# belt-and-braces.
if
(
state
==
"matrix"
):
a
=
float
(
next
(
items
))
b
=
float
(
next
(
items
))
c
=
float
(
next
(
items
))
d
=
float
(
next
(
items
))
e
=
float
(
next
(
items
))
f
=
float
(
next
(
items
))
work
=
multiplySvgMatrices
(
work
,
[
a
,
b
,
c
,
d
,
e
,
f
])
#work = multiplySvgMatrices ( [a, b, c, d, e, f], work )
return
work
#--------------------------
# median
def
median
(
mylist
):
sorts
=
sorted
(
mylist
)
length
=
len
(
sorts
)
# if not length % 2:
# return (sorts[int(length / 2)] + sorts[int(length / 2 - 1)]) / 2.0
indice
=
int
(
length
/
2
)
return
sorts
[
int
(
length
/
2
)]
File Metadata
Details
Attached
Mime Type
text/x-objective-c
Expires
Thu, Apr 9, 02:12 (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3603767
Default Alt Text
svgTreeManager.py (16 KB)
Attached To
Mode
rTASACORAEXP Tasacora experimental code
Attached
Detach File
Event Timeline
Log In to Comment