From 71f62f910aba6c5c0849ac37d197169f2460779c Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 1 Sep 2024 20:52:05 +0200 Subject: [PATCH] fix bugs --- README.md | 27 ++++ example.svg | 385 ++++++++++++++++++++++++++++++++++++++++++++ example.xlsx | Bin 6214 -> 6321 bytes inkscape_uprint.inx | 80 +++------ inkscape_uprint.py | 232 +++++++++++++------------- 5 files changed, 549 insertions(+), 175 deletions(-) create mode 100644 example.svg diff --git a/README.md b/README.md index 5954e09..7966918 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,30 @@ # inkscape_uprint Inkscape plugin to create multiple clones of the same object with pattern substitution + +## Install + +copy the source content into your inkscape extension directory: + + ${HOME}/.config/inkscape/extensions/inkscape_uprint + +or equivalently get it using git clone: + + cd ${HOME}/.config/inkscape/extensions/ + git clone[https://gitea.mildstone.org/YFinGames/inkscape_uprint.git]() + +reload inkscape + +find the extension in **Extensions -> Renderg -> InkscapeUprint** + +## INSTRUCTIONS: + +1. Create any shape with text wherever you like; + place text elements in a frame as much as possible ( multiline substitution works in framed text only ) +2. place {{tags}} where you like, each tag is eventually searched in the first line of the xlsx file ( the header ) and a set of replicas of the selection will be generatend in a separate page, one for each line of the xlsx. Any keyword can be used as a tag and it will be substituted unless wrapped between double curly braces. +3. apply any transform to the shape lou like +4. group anything together but do not apply any transform to the group afterwards. If you need to ungroup make what you like ( translate, rotate, scale ) and then regroup everything together. The changes applied to the toplevel group are affecting the position of the shape after the placement on the output pages. +5. Select the group and apply InkscapeUprint. The parameters are: + - padding: padding space among placed shapes + - page margins: From the page to the page collection + - The size of the output pages is copied from the model one diff --git a/example.svg b/example.svg new file mode 100644 index 0000000..ab9c204 --- /dev/null +++ b/example.svg @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + InkscapeUprint + + + INSTRUCTIONS: + +1. Create any shape with text whwere you like, put text in a frame as much as possible ( multiline substitution works in framed text only ) + +2. place {{tags}} where you like in the text + +3. apply any transform to the shape + +4. group anything together but do not apply any transform to the group afterwards. If you need to ungroup make what you like ( translate, rotate, scale ) and then regroup everything together. The changes applied to the toplevel group are affecting the position of the shape after the placement on the output pages. + +5. Select the group and apply InkscapeUprint. The parameters are: + padding: padding space among placed shapes + page margins: From the page to the page collection + The size of the output pages is copied from the model one + + + + + + + {{nome}}{{cognome}} + {{ruolo}} + + + {{affiliazione}} + + + + + {{nome}}{{cognome}} + {{ruolo}} + + + {{affiliazione}} + + + diff --git a/example.xlsx b/example.xlsx index b070051599de456c1e9ecd5514c61d06c592aa0f..5fa1ff9200d89207fb5ab4b32859823c61b3d966 100644 GIT binary patch delta 2608 zcmZ8jcQ_kd7mrw}NX^*0LWxyhw6%&?L&T;=YL8kkwOX4{RjMcw+Gr_?*6K^CRWWLp z8nI{XJ^NafmJjdqy#4y!Kkhlt@1Aq-bI$MHb5E;G=@p29HYF7+00aU7%$x;FAutNc zQ&}cQaTR#3+@-YpMeIJO{iGZBm7Om>#zS z)H44@go`H_7BD~uAWELIY%%x>wW@``8f`He%(YG(Sq-@e0Ul#v!O$ce8sXBjQn+oD zpr`tw;|*?hY`r`n4AB5beInQr?lGGVOV(I)5k?AO6HLbX&|@@!p1zVv^;KhJ(yS&5ThE+z&_!9EvXv&pL>}` zxqulC&vniA0J~>sJ-WmqBSF*WM#%mO<^{6ow7$@IkX_M;NX!G&ar0Wy38{^23R|L7 zD2q&`_ER1)@gGVk6}@9rl_8$&lr>+RSOZ>@a0f^1#jLr{ZjSMMXs*u_*vYk~s|udP zFIxN4<7-TTw+{7;umtIK!rb@cfqFt~v5o=0$6V_oCi{bXyDmcop`OF6Fj7QV2CC;GQy=HZeX;Tfcys}&l^1dSfnGDR!S?q`v zzTGXah_NK6HRK(66lpu`CCf~ytRT`y!}&@~X9jrT_mI;ifHm)xFO#p)CHqy}L5IV9 zg;|M_f4~wJ0T^2`;`peOEEX|%@RC)>D)4Q_yPO>y7<~)@?CA8(VVPmfFF7el0 zJMl^!)qQ$`?R6NM^WK zCuG*njmTEV?M>K6#6)xLrnxFVkX>_~_T!6%isnw-3QGIn^E|*7NYp`*JMRrtHsFVD zlyizo7vW>x+1_le{W~UF-X__xY9`Ha3p^VPjL64kWGQJ9H>(XtBy87v9sP73939CH zI}m(gLQh%`hypLEBG`33;0`(op4A&Jfp2ZI^JTs!v;RHIxVM`lBB<;$BrRNTU7;TB zAr=D}?lbli=l8X|8!os@A*yPCWsz8q_3mJbz_@A}e%sC0sb9KTVO&IQ14w^-C8EQ% zg>KK;TX3M)%=$vjJJ5hd?$G7HQsE*Y^I@(LPLs_K08{jP`T?m0<(XOL0y$68?!jDc zc+FtZz=}bw4l6)9SKM(0ALaV0Q^>taJmo?EMC`h zK!)+r){oevq^czMVAZ$6`KgkfKx9FU#ooFC?h7hGG~i{__8MUxaBW^a$MHviHSzDH z)&1e=nFoe3>K^Z6<0Tl6K){_YF26cmuim2))|J*SI_ zUGpw6+Ky4vS4fD|J>u-ijbrkFW0mc>LIR1SwQySP#_&0@oH5J7Ro3OUhac-B@|fKU zOy)H-)^1spfm*?7BHQ66gtP+7%#-khp3$NWg>R17KVjdbnjb4WC(&|Wx$RBjS5-;_wMR6DBaKfu%qkg@6!CXbKk&&4RXk}bnkSPt zb`p_ceWp$kJ(tD6th>C^GC>)oR`DSVVgrMQl$AhB5^|6W!iE zTnXqBs_)BAGAnH-9M?IhYU?TbwCTkc(fFJGv_kB2QcCp)+j442F2HKZ(J4o8x8qaN zXhC>sdx0zp0HFPp$lpjTo4F`_{;A+VjKWwe{ZBHip$BFXq5K3l4=9PJctU46A74I4(&?b<^FpUvs!2{Xz{^G1b zPosxcd~fENz8dx7i&II*?ffm3Ea?OPryFleNp>d8s2lQ#Pr1|)1{zc0Tb=j2eEmX= zyRw`$L8iYF8(%1288w^iANr%25KJzLaZ+k zEy*oQbaUg`DZ2*OU12;^vn&VoCF5bM^C>NN&1GCicOFD zD;`_=*3qiLzprWy4cau`^F%`Gld(B-q zI&!Q@K%MOlcS?7BWV3mK;%ZIz<$(KcT-XL7Ne;MJ7sxbH?A~~4?h0j0t$pCFcXHVMx@%~A1k%0h0JOCvd5@4XDhp@hc6d|$ zv*2?@h|VU>*kKF90ta2OPM-UGEl70x9k+l_O1lkNJ&)7}X4tCHbj*{<`S zLI3+MSWi#+HE!c(*e+qtY@*T-X`81D-KXq;e;J%=$_xO2jn5r-Uumhc*+82D$O`zk zUBmp#>!~BtZ;p delta 2497 zcmZ8j2{6?C8{b{m=3L8Kp?TL9U&gis^o|zbXS$@fbY?((FZq)kHYr#&dzRxTNYu&a!8s{CaR=8Ovf9-O|;gd z=)t)k-WG}-cvGFH;meoG))MANW;>oylyRGVUs+Y`$Na!VYplL6fg8}%LCQMP zMPrY;LT)jo6f4nOPu{5t6Kc%vBWs_Gowajhe3~xluB~!(pyT5Oy5Scq zOjv?+CHo)?c?W-!cRo!1O#nGQnWU&}u)v>Qh*4CVbqUC7c8{-SK8l$<+V<+yS_btf z+LwukuwGGhAXDMXCee?r8dJ&a)G^BJLB0C13XRFA;=`JxPIjvF5C=j0u9Z~K#K-l< z$wiR>aLDincBWBdr}Xnj=lId28qdSYkzO|BD$-m1W*vE6qo%3TKvy7N_PErZ+x-fL z+WRHegz-08ZMrSNIc~ZgaHp!k2XlnvSaLA?nQ3e0Q+e4CaNzhCEa!Ew?E2 z44>i8ec%dlW}D*y7v_gbWYRd6as84fwOF1;zbaLqsed2qTP%LL>?+Nx2>nyo>8IAp znGjf8|3d92Rbp|aI&klSsEgxZEw`f}`eCW5hw0A=HL>%VC!KKbEj_y)Gkox|Z4y)7Y_1UJe zWbsfxu-Jf*e!2eU{5jxV@XY}jmOo4>dp(FNhY<8|X(Rfn?2o`~evPjh{WIJzZ&xIK zY=J2nT^n{L=7jUIho3G+Pz9IrUlfNW?kamozb~y?s9>6pLmlgD8TIRQ!R`rVM>1C~ zb=)xY3;L4U_L`Rkfbf9~hMAdj+e$hPs8oAUlJ`fH?a z#{S75qy3n*?`)aEw--i7z{K}esuOhICbaipA54;P(|^4K#mCfagRL6EqyxT;w|J3D zIAw5hv+`Cn1Ny9&k+a?t9o$A_d6{T=imWQjPleU?w1(5ig>$CbDL9wm?&vFgDo9}V zuA*%)<{ah9L7sb5Pm#TA>kco60E12TIL9P~5|4yCm|@f+H7t7i+@ zTk}9olEt}{I6&vA^64*1juxnvv|?fkz_0qU~1xQv)Zih6*xCFPG7JlNSwF>zCM>(v=fd7WM8vpQ$nf6s1@F{*c^H-FB1VdYWf1(g7drw(QwD+4|}>S|m=oNVzQL5Ga z`MjuQZYe=3xv%jOMfoA~=Auf)<4r*C$!KWAICs^9`0wtRuTX?|O|o%ocRLRNdRt5N z4w)U}OC0x^R=`ZUdLFnS6uDvz9*FSQu=6uH*#lmCd@p>&?ONwa^&ZEynoLhg&N!lg z*LW!XFq{^#=rcrI*aS3f)TFzr@HQE#MjLoj(a>+z-pJq1PF;=C0>t%SYkT z(i%=R^ta;Da=YoWHFNipwB)+Lz$uvs7Y9fxr)#3(1|n^L$7ch5UKhI?KQgW_eLaxX zAjTbL3h8~W65<0dAK)PA-f1AI3}q~vTVJV{i>n48afSKWI_1%s-(Gl*%)g3P4X~hP zv?lI9G|z_@_O$i22yv#iEYn5KhO1pa;(z}o8O5GcJH3#%wUZjjU+QA8)$N8Y+|JM6 zyTeOlVCPObAlmS6|4t*!cFq(vG2I;+DrT@lYsIt$x6c=k|F0nmOH3R6TlD9Ui2gAa zAdn%&28ogWjY%$0FZi*o>xF@UkZ!W15H)DCF;E$CeSwsL;^i~I$efK$xG_wIxcC@u+U6``bv%W{VyeuMr2!A?Fc diff --git a/inkscape_uprint.inx b/inkscape_uprint.inx index 75b09b8..b4884b2 100644 --- a/inkscape_uprint.inx +++ b/inkscape_uprint.inx @@ -7,79 +7,35 @@ - 0 - 0 + + 2 px mm - - - 14 - 0 - 0 - 0 - 0 - 0 + + + 10 + 0 + 0 + 0 + 0 - - - - 9 - 0 - 0 - 0 - 0 - 0 - - - - 21 - 0 - 0 - 0 - 0 - 0 - - --> diff --git a/inkscape_uprint.py b/inkscape_uprint.py index a934ee0..647fae1 100755 --- a/inkscape_uprint.py +++ b/inkscape_uprint.py @@ -37,6 +37,7 @@ dec = decimal.Decimal + # ['NAMESPACE', 'PARSER', 'TAG', 'WRAPPED_ATTRS', '__abstractmethods__', '__bool__', # '__class__', '__contains__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__', # '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', @@ -71,13 +72,16 @@ dec = decimal.Decimal # |_| |_| -class _Spacing(): - size = 0. - padding = 0. - margin_top = 0. - margin_bottom = 0. - margin_left = 0. - margin_right = 0. +# class _Spacing(): +# size = 0. +# padding = 0. +# margin_top = 0. +# margin_bottom = 0. +# margin_left = 0. +# margin_right = 0. + + + class InkscapeUprint(inkex.Effect): def __init__(self): @@ -105,79 +109,54 @@ class InkscapeUprint(inkex.Effect): type=str, dest="subst_file", default='', help="xlsx file to load substitutions") - # self.arg_parser.add_argument("--font_family", - # type=str, - # dest="font_family", default="Apple Garamond", - # help="Font family to use for days and labels") - # self.arg_parser.add_argument("--year", - # type=int, - # dest="year", default=1970, - # help="Calendar Year") - # self.arg_parser.add_argument("-m", "--month", - # type=str, - # dest="month", default="Gennaio", - # help="month to render") - # self.arg_parser.add_argument("--dayfrom", - # type=int, - # dest="dayfrom", default=1, - # help="Range to render day from") - # self.arg_parser.add_argument("--dayto", - # type=int, - # dest="dayto", default=31, - # help="Range to render day to") - # self.arg_parser.add_argument("--day_text_size", - # type=float, - # dest="day_text_size", default=12, - # help="Text size of day") - # self.arg_parser.add_argument("--saint_text_size", - # type=float, - # dest="saint_text_size", default=8, - # help="Text size of saint") - # spacing - # self.__elements = ['day', 'wday', 'saint', 'moon'] self.__element_spacing = { 'size' : 0, 'padding' : 0, - 'margin-top' : 5, - 'margin-bottom' : 5, - 'margin-left' : 5, - 'margin-right' : 5 + 'margin-top' : 0, + 'margin-bottom' : 0, + 'margin-left' : 0, + 'margin-right' : 0 } self.__page_spacing = { 'size' : 0, 'padding' : 0, - 'margin-top' : 5, - 'margin-bottom' : 5, - 'margin-left' : 5, - 'margin-right' : 5 + 'margin-top' : 0, + 'margin-bottom' : 0, + 'margin-left' : 0, + 'margin-right' : 0 } - # for el in self.__elements: - # for sk in self.__spacing.keys(): - # sk = sk.replace('-','_') - # self.arg_parser.add_argument("--" + el + "_" + sk, - # type=float, - # dest=el + "_" + sk, - # help="Spacing " + el + " " + sk) - self.pages = [] + for sk in self.__element_spacing.keys(): + sk = sk.replace('-','_') + self.arg_parser.add_argument("--element_" + sk, + type=float, + dest="element_" + sk, + help="Spacing element" + " " + sk) + for sk in self.__page_spacing.keys(): + sk = sk.replace('-','_') + self.arg_parser.add_argument("--page_" + sk, + type=float, + dest="page_" + sk, + help="Spacing page" + " " + sk) + - def svg_traverse_node(self, node, tag=None): - def _tag(tag): - return re.sub(r'\{.*?\}', r'', tag) + def svg_traverse_node(self, node, tag=None, exclude=None): + def _tag(t): + return re.sub(r'\{.*?\}', r'', t) l = [] if isinstance(node, etree._Element): - if tag is None or _tag(node.tag) == tag: + if (tag is None or _tag(node.tag) == tag) and (exclude is None or _tag(node.tag) != exclude): l.append(node) for child in node.getchildren(): - l.extend(self.svg_traverse_node(child)) + l.extend(self.svg_traverse_node(child, tag=tag, exclude=exclude)) elif isinstance(node, etree._Comment): pass elif isinstance(node, list): for child in node: - l.extend(self.svg_traverse_node(child)) + l.extend(self.svg_traverse_node(child, tag=tag, exclude=exclude)) return l def replace_text(self, selected, substs): @@ -189,31 +168,34 @@ class InkscapeUprint(inkex.Effect): for t in tspans: if t.text is None: continue t.text = patt.sub(v, t.text) - multilines = t.text.split('\n') - if len(multilines) > 1: - t.text = multilines[0] - for i in range(1, len(multilines)): - new_t = etree.Element('tspan', nsmap=t.nsmap) - new_t.text = multilines[i] - new_t.tail = t.tail - new_t.attrib.update(t.attrib) # copy style - p = t.getparent() - p.insert(p.index(t) + 1, new_t) + # multilines = t.text.split('\n') + # if len(multilines) > 1: + # t.text = multilines[0] + # for i in range(1, len(multilines)): + # new_t = etree.Element('tspan', nsmap=t.nsmap) + # new_t.text = multilines[i] + # new_t.tail = t.tail + # new_t.attrib.update(t.attrib) # copy style + # p = t.getparent() + # p.insert(p.index(t) + 1, new_t) def duplicate_object(self, parent, selected): duplicated = [] + import copy for node in selected: - duplicated.append(etree.Element(node.tag, node.attrib, nsmap=node.nsmap)) - self.duplicate_object(duplicated[-1], node.getchildren()) - duplicated[-1].text = node.text - if 'transform' in node.attrib: - duplicated[-1].set('transform', node.attrib['transform']) + new_node = node.duplicate() + duplicated.append(new_node) + # duplicated.append(etree.Element(node.tag, node.attrib, nsmap=node.nsmap)) + # # # inkex.utils.debug(' node.text = ' + str(type(node.text))) + # duplicated[-1].tail = node.tail + # duplicated[-1].text = node.text + # self.duplicate_object(duplicated[-1], node.getchildren()) parent.append(duplicated[-1]) return duplicated def translate_object(self, obj, x, y): - trans = 'translate(' + str(x) + ',' + str(y) + ')' + trans = 'translate(' + str(float(x)) + ',' + str(float(y)) + ')' for d in obj: if 'transform' in d.attrib: d.set('transform', d.get('transform') + ' ' + trans) else: d.set('transform', trans) @@ -222,68 +204,92 @@ class InkscapeUprint(inkex.Effect): if elements is None: return first_page = self.svg.getElement("//inkscape:page") - parent = first_page.getparent() - pages = [ node for node in self.svg_traverse_node(parent, tag='inkscape:page')] - rightmost_page = max(pages, key=lambda p: p.get('x')) - + if first_page is not None: + page_width = float(first_page.get('width')) + page_height = float(first_page.get('height')) + view = first_page.getparent() + else: + view = self.svg.getElement("//sodipodi:namedview") + if view is None: + inkex.utils.errormsg('No view found') + return + page_width = self.svg.get_page_bbox().width + page_height = self.svg.get_page_bbox().height + + pages = [ node for node in self.svg_traverse_node(view, tag='inkscape:page')] + # the reference page is always the first one - pgbbox = {"width": first_page.get('width'), "height": first_page.get('height')} - elbbox = self.svg.selected.bounding_box() - # number of elements per page - nx = int (( float(pgbbox['width']) - self.__page_spacing['margin-left'] - self.__page_spacing['margin-right'] ) / \ - ( elbbox.width + self.__element_spacing['margin-left'] + self.__element_spacing['margin-right'] )) - ny = int (( float(pgbbox['height']) - self.__page_spacing['margin-top'] - self.__page_spacing['margin-bottom'] ) / \ - ( elbbox.height + self.__element_spacing['margin-top'] + self.__element_spacing['margin-bottom'] )) + pgbbox = {"width": page_width, "height": page_height} + elbbox = self.bbox + nx = int (( float(pgbbox['width']) - self.__page_spacing['margin-left'] - self.__page_spacing['margin-right'] - self.__element_spacing['padding']) / \ + ( elbbox.width + self.__element_spacing['padding'] )) + ny = int (( float(pgbbox['height']) - self.__page_spacing['margin-top'] - self.__page_spacing['margin-bottom'] - self.__element_spacing['padding'] ) / \ + ( elbbox.height + self.__element_spacing['padding'] )) # remove model position - correct_x = self.svg.selected.bounding_box().left - correct_y = self.svg.selected.bounding_box().top + correct_x = self.bbox.left + correct_y = self.bbox.top # compute page position zero - px = float(rightmost_page.get('x')) + float(rightmost_page.get('width')) - py = float(rightmost_page.get('y')) + if len(pages) > 0: + rightmost_page = max(pages, key=lambda p: p.get('x')) + px = float(rightmost_page.get('x')) + py = float(rightmost_page.get('y')) + else: + px = 0. + py = 0. # place elements for i, el in enumerate(elements): # create new page if needed + # inkex.utils.debug('nx: '+str(nx) + ' ny: '+str(ny)) if i % int(nx * ny) == 0: - new_page = etree.Element('{http://www.inkscape.org/namespaces/inkscape}page', nsmap=first_page.nsmap) - new_page.set('x', str(float(rightmost_page.get('x')) + float(rightmost_page.get('width')) + 20 )) - new_page.set('y', str(float(rightmost_page.get('y')) + 0 )) - new_page.set('width', str(float(first_page.get('width')))) - new_page.set('height', str(float(first_page.get('height')))) - parent.append(new_page) + new_page = etree.Element('{http://www.inkscape.org/namespaces/inkscape}page', nsmap=view.nsmap) + new_page.set('x', str(px + ( page_width + self.__page_spacing['padding'] ))) + new_page.set('y', str(py) ) + new_page.set('width', str(page_width)) + new_page.set('height', str(page_height)) + view.append(new_page) rightmost_page = new_page px = float(rightmost_page.get('x')) py = float(rightmost_page.get('y')) - x = px + (i % nx) * ( elbbox.width + self.__element_spacing['margin-left'] + self.__element_spacing['margin-right'] ) + self.__page_spacing['margin-left'] - y = py + (i // nx) * ( elbbox.height + self.__element_spacing['margin-top'] + self.__element_spacing['margin-bottom'] ) + self.__page_spacing['margin-top'] - self.translate_object(el, x-correct_x, y-correct_y) + x = px + (i % nx) * ( elbbox.width + self.__element_spacing['padding'] ) \ + + self.__page_spacing['margin-left'] + self.__element_spacing['padding'] + + y = py + (i % (nx*ny) // nx) * ( elbbox.height + self.__element_spacing['padding'] ) \ + + self.__page_spacing['margin-top'] + self.__element_spacing['padding'] + + self.translate_object(el, x - correct_x, y - correct_y) + - # inkex.utils.errormsg("pages:" + repr(pages)) - # for p in pages: - # inkex.utils.errormsg("p:" + str(p)) - pass + def compute_bounding_box(self, selected): + _tag = lambda t: re.sub(r'\{.*?\}', r'', t) + objs = [ n for n in self.svg_traverse_node(selected[0]) if _tag(n.tag) not in ['text', 'tspan','g'] ] + l = inkex.elements._selected.ElementList(self.svg, objs) + return l.bounding_box() + + def effect(self): - - # for el in self.options.element_spacing: - # self.__element_spacing[el] = self.svg.unittouu(str(self.options.element_spacing[el]) + self.options.units) - # for el in self.options.page_spacing: - # self.__page_spacing[el] = self.svg.unittouu(str(self.options.page_spacing[el]) + self.options.units) - + # inkex.utils.debug('inkex: ' + inkex.__file__) parent = self.svg.get_current_layer() selected = self.svg.selected + for k in self.__page_spacing.keys(): + self.__page_spacing[k] = self.svg.unittouu( str(self.options.__dict__['page_'+k.replace('-','_')]) + self.options.units ) + for k in self.__element_spacing.keys(): + self.__element_spacing[k] = self.svg.unittouu(str(self.options.__dict__['element_'+k.replace('-','_')]) + self.options.units) + if selected: - self.bbox = selected.bounding_box() - self.x_offset = self.svg.unittouu(str(self.options.x) + self.options.units) - self.y_offset = self.svg.unittouu(str(self.options.y) + self.options.units) + self.bbox = self.compute_bounding_box(selected) + if self.bbox is None: + inkex.utils.errormsg('bbox is None') + # self.x_offset = self.svg.unittouu(str(self.options.x) + self.options.units) + # self.y_offset = self.svg.unittouu(str(self.options.y) + self.options.units) else: inkex.utils.errormsg("Error - No object selected") - # self.place_in_pages() return wb = openpyxl.load_workbook(self.options.subst_file)