2021-07-23 02:36:56 +02:00
#!/usr/bin/env python3
'''
Extension for InkScape 1.0
Features
- This tool is a helper to adjust the document border including an offset value , which is added .
Sending vector data to Epilog Dashboard often results in trimmed paths . This leads to wrong geometry where the laser misses to cut them .
So we add a default ( small ) amount of 1.0 doc units to expand the document ' s canvas
Author : Mario Voigt / FabLab Chemnitz
Mail : mario . voigt @stadtfabrikanten.org
Date : 21.04 .2021
2021-10-28 00:21:40 +02:00
Last patch : 28.10 .2021
2021-07-23 02:36:56 +02:00
License : GNU GPL v3
#known bugs:
- viewbox / width / height do not correctly apply if documents only contain an object ( not a path ) . After converting it to path it works . Seems to be a bbox problem
- note from 07.05 .2021 : seems if the following order is viewBox / width / height , or width / height / viewBox , the units are not respected . So me mess around a little bit
#Todo
- add some way to handle translations properly
'''
import math
2021-10-26 18:55:45 +02:00
import sys
2021-07-23 02:36:56 +02:00
import inkex
from inkex import Transform
2021-10-26 18:55:45 +02:00
sys . path . append ( " ../apply_transformations " )
2021-07-23 02:36:56 +02:00
class EpilogDashboardBboxAdjust ( inkex . EffectExtension ) :
def getElementChildren ( self , element , elements = None ) :
if elements == None :
elements = [ ]
if element . tag != inkex . addNS ( ' g ' , ' svg ' ) :
elements . append ( element )
for child in element . getchildren ( ) :
self . getElementChildren ( child , elements )
return elements
def add_arguments ( self , pars ) :
pars . add_argument ( " --tab " )
2021-10-26 18:55:45 +02:00
pars . add_argument ( " --apply_transformations " , type = inkex . Boolean , default = False , help = " Run ' Apply Transformations ' extension before running vpype. Helps avoiding geometry shifting " )
2021-07-23 02:36:56 +02:00
pars . add_argument ( " --offset " , type = float , default = " 1.0 " , help = " XY Offset (mm) from top left corner " )
pars . add_argument ( " --removal " , default = " none " , help = " Remove all elements outside the bounding box or selection " )
pars . add_argument ( " --use_machine_size " , type = inkex . Boolean , default = False , help = " Use machine size " )
2021-10-28 00:21:40 +02:00
pars . add_argument ( " --machine_size " , default = " 812x508 " , help = " Machine/Size (mm) " )
2021-07-23 02:36:56 +02:00
pars . add_argument ( " --debug " , type = inkex . Boolean , default = False , help = " Debug output " )
2021-10-26 18:55:45 +02:00
pars . add_argument ( " --skip_errors " , type = inkex . Boolean , default = False , help = " Skip on errors " )
2021-07-23 02:36:56 +02:00
def effect ( self ) :
2021-10-26 18:55:45 +02:00
applyTransformationsAvailable = False # at first we apply external extension
try :
import apply_transformations
applyTransformationsAvailable = True
except Exception as e :
# self.msg(e)
self . msg ( " Calling ' Apply Transformations ' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ... " )
if self . options . apply_transformations is True and applyTransformationsAvailable is True :
apply_transformations . ApplyTransformations ( ) . recursiveFuseTransform ( self . document . getroot ( ) )
2021-07-23 02:36:56 +02:00
offset = self . options . offset
2021-10-28 00:21:40 +02:00
units = self . svg . unit
2021-10-26 18:55:45 +02:00
scale_factor = self . svg . unittouu ( " 1px " )
#https://wiki.inkscape.org/wiki/Units_In_Inkscape
2021-07-23 02:36:56 +02:00
# create a new bounding box and get the bbox size of all elements of the document (we cannot use the page's bbox)
bbox = inkex . BoundingBox ( )
if len ( self . svg . selected ) > 0 :
2021-10-26 18:55:45 +02:00
#bbox = self.svg.selection.bounding_box() #it could be so easy! But ...
for element in self . svg . selected . values ( ) :
'''
. . . rectangles cause some strangle scaling issue , offendingly caused by namedview units .
The rectangle attributes are set in px . They ignore the real units from namedview .
Strange fact : ellipses , spirals and other primitives work flawlessly .
'''
2021-10-28 03:42:25 +02:00
if isinstance ( element , inkex . Rectangle ) or \
isinstance ( element , inkex . Circle ) or \
isinstance ( element , inkex . Ellipse ) :
2021-10-26 18:55:45 +02:00
bbox + = element . bounding_box ( ) * scale_factor
2021-10-28 03:42:25 +02:00
elif isinstance ( element , inkex . TextElement ) or \
isinstance ( element , inkex . Tspan ) :
2021-10-26 18:55:45 +02:00
if self . options . skip_errors is False :
self . msg ( " Text elements are not supported! " )
return
else :
continue
else :
bbox + = element . bounding_box ( )
2021-07-23 02:36:56 +02:00
else :
#for element in self.svg.root.getchildren():
for element in self . document . getroot ( ) . iter ( " * " ) :
2021-10-23 02:32:35 +02:00
if isinstance ( element , inkex . ShapeElement ) and element . tag != inkex . addNS ( ' use ' , ' svg ' ) and element . get ( ' inkscape:groupmode ' ) != ' layer ' : #bbox fails for svg:use elements and layers:
2021-10-28 03:42:25 +02:00
if isinstance ( element , inkex . Rectangle ) or \
isinstance ( element , inkex . Circle ) or \
isinstance ( element , inkex . Ellipse ) :
bbox + = element . bounding_box ( ) * scale_factor
elif isinstance ( element , inkex . TextElement ) or \
isinstance ( element , inkex . Tspan ) :
2021-10-26 18:55:45 +02:00
if self . options . skip_errors is False :
self . msg ( " Text elements are not supported! " )
return
else :
continue
else :
bbox + = element . bounding_box ( )
2021-07-23 02:36:56 +02:00
if abs ( bbox . width ) == math . inf or abs ( bbox . height ) == math . inf :
2021-10-26 18:55:45 +02:00
inkex . utils . debug ( " Error while calculating overall bounding box! Check your element types. Things like svg:text or svg:use are not supported. Impossible to continue! " )
2021-07-23 02:36:56 +02:00
return
2021-10-26 18:55:45 +02:00
#if len(self.svg.selected) > 0:
# selected = self.svg.selected
#else:
# selected = self.svg.root.getchildren()
#for element in selected:
# transform = inkex.Transform()
# parent = element.getparent()
# if parent is not None and isinstance(parent, inkex.ShapeElement):
# transform = parent.composed_transform()
# try:
# '''
# ...rectangles cause some strangle scaling issue, offendingly caused by namedview units.
# The rectangle attributes are set in px. They ignore the real units from namedview.
# Strange fact: ellipses, spirals and other primitives work flawlessly.
# '''
# if isinstance (element, inkex.Rectangle) or isinstance (element, inkex.TextElement):
# bbox += element.bounding_box(transform) * scale_factor
# else:
# bbox += element.bounding_box(transform)
# except Exception:
# logger.exception("Bounding box not computed")
# logger.info("Skipping bounding box")
# transform = element.composed_transform()
# x1, y1 = transform.apply_to_point([0, 0])
# x2, y2 = transform.apply_to_point([1, 1])
# bbox += inkex.BoundingBox((x1, x2), (y1, y2))
2021-07-23 02:36:56 +02:00
# adjust the viewBox to the bbox size and add the desired offset
if self . options . use_machine_size is True :
2021-10-28 00:21:40 +02:00
machineWidth = self . svg . unittouu ( self . options . machine_size . split ( ' x ' ) [ 0 ] + " mm " )
machineHeight = self . svg . unittouu ( self . options . machine_size . split ( ' x ' ) [ 1 ] + " mm " )
2021-07-23 02:36:56 +02:00
width = f ' { machineWidth } ' + units
height = f ' { machineHeight } ' + units
viewBoxXmin = - offset
viewBoxYmin = - offset
viewBoxXmax = machineWidth
viewBoxYmax = machineHeight
else :
width = f ' { bbox . width + offset * 2 } ' + units
height = f ' { bbox . height + offset * 2 } ' + units
viewBoxXmin = - offset
viewBoxYmin = - offset
viewBoxXmax = bbox . width + offset * 2
viewBoxYmax = bbox . height + offset * 2
self . document . getroot ( ) . attrib [ ' width ' ] = width
self . document . getroot ( ) . attrib [ ' viewBox ' ] = f ' { viewBoxXmin } { viewBoxYmin } { viewBoxXmax } { viewBoxYmax } '
self . document . getroot ( ) . attrib [ ' height ' ] = height
# translate all elements to fit the adjusted viewBox
mat = Transform ( " translate( %f , %f ) " % ( - bbox . left , - bbox . top ) )
for element in self . document . getroot ( ) . iter ( " * " ) :
if isinstance ( element , inkex . ShapeElement ) and element . tag != inkex . addNS ( ' g ' , ' svg ' ) :
2021-10-20 22:02:21 +02:00
element . transform = Transform ( mat ) * element . composed_transform ( )
2021-07-23 02:36:56 +02:00
if self . options . removal == " outside_canvas " :
for element in self . document . getroot ( ) . iter ( " * " ) :
if isinstance ( element , inkex . ShapeElement ) and element . tag != inkex . addNS ( ' g ' , ' svg ' ) :
ebbox = element . bounding_box ( )
#self.msg("{} | bbox: left = {:0.3f} right = {:0.3f} top = {:0.3f} bottom = {:0.3f}".format(element.get('id'), ebbox.left, ebbox.right, ebbox.top, ebbox.bottom))
#check if the element's bbox is inside the view canvas. If not: delete it!
if ebbox . right < viewBoxXmin or \
ebbox . left > viewBoxXmax or \
ebbox . top < viewBoxYmin or \
ebbox . bottom > viewBoxYmax :
if self . options . debug is True :
self . msg ( " Removing {} {} " . format ( element . get ( ' id ' ) , ebbox ) )
element . delete ( )
elif self . options . removal == " outside_selection " :
if len ( self . svg . selected ) == 0 :
inkex . utils . debug ( " Your selection is empty but you have chosen the option to remove all elements outside selection! " )
return
allElements = [ ]
for selected in self . svg . selection :
allElements = self . getElementChildren ( selected , allElements )
for element in self . document . getroot ( ) . iter ( " * " ) :
if element not in allElements and isinstance ( element , inkex . ShapeElement ) and element . tag != inkex . addNS ( ' g ' , ' svg ' ) :
if self . options . debug is True :
self . msg ( " Removing {} " . format ( element . get ( ' id ' ) ) )
element . delete ( )
if __name__ == ' __main__ ' :
EpilogDashboardBboxAdjust ( ) . run ( )