2020-07-30 01:16:18 +02:00
#!/usr/bin/env python3
#
# License: GPL2
# Copyright Mark "Klowner" Riedesel
# https://github.com/Klowner/inkscape-applytransforms
#
2021-04-12 13:37:55 +02:00
import copy
2020-07-30 01:16:18 +02:00
import math
2021-04-12 13:37:55 +02:00
from lxml import etree
import inkex
2020-07-30 01:16:18 +02:00
from inkex . paths import CubicSuperPath , Path
from inkex . transforms import Transform
from inkex . styles import Style
NULL_TRANSFORM = Transform ( [ [ 1.0 , 0.0 , 0.0 ] , [ 0.0 , 1.0 , 0.0 ] ] )
2021-01-12 10:28:05 +01:00
class ApplyTransform ( inkex . EffectExtension ) :
2020-07-30 01:16:18 +02:00
def __init__ ( self ) :
2021-01-12 10:28:05 +01:00
super ( ApplyTransform , self ) . __init__ ( )
2020-07-30 01:16:18 +02:00
def effect ( self ) :
if self . svg . selected :
for id , shape in self . svg . selected . items ( ) :
self . recursiveFuseTransform ( shape )
else :
self . recursiveFuseTransform ( self . document . getroot ( ) )
@staticmethod
2021-04-18 18:21:23 +02:00
def objectToPath ( element ) :
if element . tag == inkex . addNS ( ' g ' , ' svg ' ) :
return element
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
if element . tag == inkex . addNS ( ' path ' , ' svg ' ) or element . tag == ' path ' :
for attName in element . attrib . keys ( ) :
2020-07-30 01:16:18 +02:00
if ( " sodipodi " in attName ) or ( " inkscape " in attName ) :
2021-04-18 18:21:23 +02:00
del element . attrib [ attName ]
return element
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
return element
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
def scaleStrokeWidth ( self , element , transf ) :
if ' style ' in element . attrib :
style = element . attrib . get ( ' style ' )
2020-07-30 01:16:18 +02:00
style = dict ( Style . parse_str ( style ) )
update = False
if ' stroke-width ' in style :
try :
stroke_width = float ( style . get ( ' stroke-width ' ) . strip ( ) . replace ( " px " , " " ) )
stroke_width * = math . sqrt ( abs ( transf . a * transf . d ) )
style [ ' stroke-width ' ] = str ( stroke_width )
update = True
except AttributeError :
pass
if update :
2021-04-18 18:21:23 +02:00
element . attrib [ ' style ' ] = Style ( style ) . to_str ( )
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
def recursiveFuseTransform ( self , element , transf = [ [ 1.0 , 0.0 , 0.0 ] , [ 0.0 , 1.0 , 0.0 ] ] ) :
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
transf = Transform ( transf ) * Transform ( element . get ( " transform " , None ) ) #a, b, c, d = linear transformations / e, f = translations
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
if ' transform ' in element . attrib :
del element . attrib [ ' transform ' ]
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
element = ApplyTransform . objectToPath ( element )
2020-07-30 01:16:18 +02:00
if transf == NULL_TRANSFORM :
# Don't do anything if there is effectively no transform applied
2021-04-18 18:21:23 +02:00
# reduces alerts for unsupported elements
2020-07-30 01:16:18 +02:00
pass
2021-04-18 18:21:23 +02:00
elif ' d ' in element . attrib :
d = element . get ( ' d ' )
2020-07-30 01:16:18 +02:00
p = CubicSuperPath ( d )
p = Path ( p ) . to_absolute ( ) . transform ( transf , True )
2021-04-18 18:21:23 +02:00
element . set ( ' d ' , str ( Path ( CubicSuperPath ( p ) . to_path ( ) ) ) )
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
self . scaleStrokeWidth ( element , transf )
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
elif element . tag in [ inkex . addNS ( ' polygon ' , ' svg ' ) ,
2020-07-30 01:16:18 +02:00
inkex . addNS ( ' polyline ' , ' svg ' ) ] :
2021-04-18 18:21:23 +02:00
points = element . get ( ' points ' )
2020-07-30 01:16:18 +02:00
points = points . strip ( ) . split ( ' ' )
for k , p in enumerate ( points ) :
if ' , ' in p :
p = p . split ( ' , ' )
p = [ float ( p [ 0 ] ) , float ( p [ 1 ] ) ]
p = transf . apply_to_point ( p )
p = [ str ( p [ 0 ] ) , str ( p [ 1 ] ) ]
p = ' , ' . join ( p )
points [ k ] = p
points = ' ' . join ( points )
2021-04-18 18:21:23 +02:00
element . set ( ' points ' , points )
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
self . scaleStrokeWidth ( element , transf )
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
elif element . tag in [ inkex . addNS ( " ellipse " , " svg " ) , inkex . addNS ( " circle " , " svg " ) ] :
2020-07-30 01:16:18 +02:00
def isequal ( a , b ) :
return abs ( a - b ) < = transf . absolute_tolerance
2021-04-18 18:21:23 +02:00
if element . TAG == " ellipse " :
rx = float ( element . get ( " rx " ) )
ry = float ( element . get ( " ry " ) )
2020-07-30 01:16:18 +02:00
else :
2021-04-18 18:21:23 +02:00
rx = float ( element . get ( " r " ) )
2020-07-30 01:16:18 +02:00
ry = rx
2021-04-18 18:21:23 +02:00
cx = float ( element . get ( " cx " ) )
cy = float ( element . get ( " cy " ) )
2020-07-30 01:16:18 +02:00
sqxy1 = ( cx - rx , cy - ry )
sqxy2 = ( cx + rx , cy - ry )
sqxy3 = ( cx + rx , cy + ry )
newxy1 = transf . apply_to_point ( sqxy1 )
newxy2 = transf . apply_to_point ( sqxy2 )
newxy3 = transf . apply_to_point ( sqxy3 )
2021-04-18 18:21:23 +02:00
element . set ( " cx " , ( newxy1 [ 0 ] + newxy3 [ 0 ] ) / 2 )
element . set ( " cy " , ( newxy1 [ 1 ] + newxy3 [ 1 ] ) / 2 )
2020-07-30 01:16:18 +02:00
edgex = math . sqrt (
abs ( newxy1 [ 0 ] - newxy2 [ 0 ] ) * * 2 + abs ( newxy1 [ 1 ] - newxy2 [ 1 ] ) * * 2
)
edgey = math . sqrt (
abs ( newxy2 [ 0 ] - newxy3 [ 0 ] ) * * 2 + abs ( newxy2 [ 1 ] - newxy3 [ 1 ] ) * * 2
)
if not isequal ( edgex , edgey ) and (
2021-04-18 18:21:23 +02:00
element . TAG == " circle "
2020-07-30 01:16:18 +02:00
or not isequal ( newxy2 [ 0 ] , newxy3 [ 0 ] )
2020-08-09 01:56:01 +02:00
or not isequal ( newxy1 [ 1 ] , newxy2 [ 1 ] )
2020-07-30 01:16:18 +02:00
) :
inkex . utils . errormsg (
" Warning: Shape %s ( %s ) is approximate only, try Object to path first for better results "
2021-04-18 18:21:23 +02:00
% ( element . TAG , element . get ( " id " ) )
2020-07-30 01:16:18 +02:00
)
2021-04-18 18:21:23 +02:00
if element . TAG == " ellipse " :
element . set ( " rx " , edgex / 2 )
element . set ( " ry " , edgey / 2 )
2020-07-30 01:16:18 +02:00
else :
2021-04-18 18:21:23 +02:00
element . set ( " r " , edgex / 2 )
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
# this is unstable at the moment
elif element . tag == inkex . addNS ( " use " , " svg " ) :
2021-04-12 13:37:55 +02:00
href = None
old_href_key = ' { http://www.w3.org/1999/xlink}href '
new_href_key = ' href '
2021-04-18 18:21:23 +02:00
if element . attrib . has_key ( old_href_key ) is True : # {http://www.w3.org/1999/xlink}href (which gets displayed as 'xlink:href') attribute is deprecated. the newer attribute is just 'href'
href = element . attrib . get ( old_href_key )
#element.attrib.pop(old_href_key)
if element . attrib . has_key ( new_href_key ) is True :
href = element . attrib . get ( new_href_key ) #we might overwrite the previous deprecated xlink:href but it's okay
#element.attrib.pop(new_href_key)
2021-04-12 13:37:55 +02:00
#get the linked object from href attribute
linkedObject = self . document . getroot ( ) . xpath ( " //*[@id = ' %s ' ] " % href . lstrip ( ' # ' ) ) #we must remove hashtag symbol
linkedObjectCopy = copy . copy ( linkedObject [ 0 ] )
objectType = linkedObject [ 0 ] . tag
if objectType == inkex . addNS ( " image " , " svg " ) :
mask = None #image might have an alpha channel
new_mask_id = self . svg . get_unique_id ( " mask " )
newMask = None
2021-04-18 18:21:23 +02:00
if element . attrib . has_key ( ' mask ' ) is True :
mask = element . attrib . get ( ' mask ' )
#element.attrib.pop('mask')
2021-04-12 13:37:55 +02:00
#get the linked mask from mask attribute. We remove the old and create a new
if mask is not None :
linkedMask = self . document . getroot ( ) . xpath ( " //*[@id = ' %s ' ] " % mask . lstrip ( ' url(# ' ) . rstrip ( ' ) ' ) ) #we must remove hashtag symbol
2021-04-18 18:21:23 +02:00
linkedMask [ 0 ] . delete ( )
2021-04-12 13:37:55 +02:00
maskAttributes = { ' id ' : new_mask_id }
newMask = etree . SubElement ( self . document . getroot ( ) , inkex . addNS ( ' mask ' , ' svg ' ) , maskAttributes )
width = float ( linkedObjectCopy . get ( ' width ' ) ) * transf . a
height = float ( linkedObjectCopy . get ( ' height ' ) ) * transf . d
linkedObjectCopy . set ( ' width ' , ' {:1.6f} ' . format ( width ) )
linkedObjectCopy . set ( ' height ' , ' {:1.6f} ' . format ( height ) )
linkedObjectCopy . set ( ' x ' , ' {:1.6f} ' . format ( transf . e ) )
linkedObjectCopy . set ( ' y ' , ' {:1.6f} ' . format ( transf . f ) )
if newMask is not None :
linkedObjectCopy . set ( ' mask ' , ' url(# ' + new_mask_id + ' ) ' )
maskRectAttributes = { ' x ' : ' {:1.6f} ' . format ( transf . e ) , ' y ' : ' {:1.6f} ' . format ( transf . f ) , ' width ' : ' {:1.6f} ' . format ( width ) , ' height ' : ' {:1.6f} ' . format ( height ) , ' style ' : ' fill:#ffffff; ' }
maskRect = etree . SubElement ( newMask , inkex . addNS ( ' rect ' , ' svg ' ) , maskRectAttributes )
2021-04-18 18:21:23 +02:00
self . document . getroot ( ) . append ( linkedObjectCopy ) #for each svg:use we append a copy to the document root
element . delete ( ) #then we remove the use object
2021-04-12 13:37:55 +02:00
else :
2021-04-18 18:21:23 +02:00
#self.recursiveFuseTransform(linkedObjectCopy, transf)
self . recursiveFuseTransform ( element . unlink ( ) , transf )
2021-04-12 13:37:55 +02:00
2021-04-18 18:21:23 +02:00
elif element . tag in [ inkex . addNS ( ' rect ' , ' svg ' ) ,
2020-07-30 01:16:18 +02:00
inkex . addNS ( ' text ' , ' svg ' ) ,
2021-04-12 13:37:55 +02:00
inkex . addNS ( ' image ' , ' svg ' ) ] :
2020-07-30 01:16:18 +02:00
inkex . utils . errormsg (
" Shape %s ( %s ) not yet supported, try Object to path first "
2021-04-18 18:21:23 +02:00
% ( element . TAG , element . get ( " id " ) )
2020-07-30 01:16:18 +02:00
)
else :
# e.g. <g style="...">
2021-04-18 18:21:23 +02:00
self . scaleStrokeWidth ( element , transf )
2020-07-30 01:16:18 +02:00
2021-04-18 18:21:23 +02:00
for child in element . getchildren ( ) :
2020-07-30 01:16:18 +02:00
self . recursiveFuseTransform ( child , transf )
if __name__ == ' __main__ ' :
ApplyTransform ( ) . run ( )