2020-08-09 21:25:19 +02:00
#!/usr/bin/env python3
"""
Extension for InkScape 1.0
Features
- helps to find contours which are closed or not . Good for repairing contours , closing contours , . . .
- works for paths which are packed into groups or groups of groups . #
- can break contours apart like in " Path -> Break Apart "
2020-08-13 01:28:19 +02:00
- implements Bentley - Ottmann algorithm from https : / / github . com / ideasman42 / isect_segments - bentley_ottmann to scan for self - intersecting paths . You might get " assert(event.in_sweep == False) AssertionError " . Don ' t know how to fix rgis
2020-08-09 21:25:19 +02:00
- colorized paths respective to their type
- can add dots to intersection points you ' d like to fix
Author : Mario Voigt / FabLab Chemnitz
Mail : mario . voigt @stadtfabrikanten.org
Date : 09.08 .2020
2020-08-13 01:28:19 +02:00
Last patch : 11.08 .2020
2020-08-09 21:25:19 +02:00
License : GNU GPL v3
"""
from math import *
import inkex
from inkex . paths import Path , CubicSuperPath
from inkex import Style , Color , Circle
from lxml import etree
import fablabchemnitz_poly_point_isect
import copy
def adjustStyle ( self , node ) :
if node . attrib . has_key ( ' style ' ) :
style = node . get ( ' style ' )
if style :
declarations = style . split ( ' ; ' )
for i , decl in enumerate ( declarations ) :
parts = decl . split ( ' : ' , 2 )
if len ( parts ) == 2 :
( prop , val ) = parts
prop = prop . strip ( ) . lower ( )
if prop == ' stroke-width ' :
declarations [ i ] = prop + ' : ' + str ( self . svg . unittouu ( str ( self . options . strokewidth ) + " px " ) )
if prop == ' fill ' :
declarations [ i ] = prop + ' :none '
node . set ( ' style ' , ' ; ' . join ( declarations ) + ' ;stroke:#000000;stroke-opacity:1.0 ' )
class ContourScanner ( inkex . Effect ) :
def __init__ ( self ) :
inkex . Effect . __init__ ( self )
self . arg_parser . add_argument ( " --breakapart " , type = inkex . Boolean , default = False , help = " Break apart contours " )
self . arg_parser . add_argument ( " --removefillsetstroke " , type = inkex . Boolean , default = False , help = " Remove fill and define stroke " )
self . arg_parser . add_argument ( " --strokewidth " , type = float , default = 1.0 , help = " Stroke width (px) " )
self . arg_parser . add_argument ( " --highlight_opened " , type = inkex . Boolean , default = True , help = " Highlight opened contours " )
2020-08-13 18:14:56 +02:00
self . arg_parser . add_argument ( " --color_opened " , type = Color , default = ' 4012452351 ' , help = " Color opened contours " )
2020-08-09 21:25:19 +02:00
self . arg_parser . add_argument ( " --highlight_closed " , type = inkex . Boolean , default = True , help = " Highlight closed contours " )
2020-08-13 18:14:56 +02:00
self . arg_parser . add_argument ( " --color_closed " , type = Color , default = ' 2330080511 ' , help = " Color closed contours " )
2020-08-09 21:25:19 +02:00
self . arg_parser . add_argument ( " --highlight_selfintersecting " , type = inkex . Boolean , default = True , help = " Highlight self-intersecting contours " )
self . arg_parser . add_argument ( " --highlight_intersectionpoints " , type = inkex . Boolean , default = True , help = " Highlight self-intersecting points " )
2020-08-13 18:14:56 +02:00
self . arg_parser . add_argument ( " --color_selfintersecting " , type = Color , default = ' 1923076095 ' , help = " Color closed contours " )
self . arg_parser . add_argument ( " --color_intersectionpoints " , type = Color , default = ' 4239343359 ' , help = " Color closed contours " )
2020-08-09 21:25:19 +02:00
self . arg_parser . add_argument ( " --dotsize " , type = int , default = 10 , help = " Dot size (px) for self-intersecting points " )
self . arg_parser . add_argument ( " --remove_opened " , type = inkex . Boolean , default = False , help = " Remove opened contours " )
self . arg_parser . add_argument ( " --remove_closed " , type = inkex . Boolean , default = False , help = " Remove closed contours " )
self . arg_parser . add_argument ( " --remove_selfintersecting " , type = inkex . Boolean , default = False , help = " Remove self-intersecting contours " )
self . arg_parser . add_argument ( " --main_tabs " )
#split combined contours into single contours if enabled - this is exactly the same as "Path -> Break Apart"
def breakContours ( self , node ) :
replacedNodes = [ ]
if node . tag == inkex . addNS ( ' path ' , ' svg ' ) :
parent = node . getparent ( )
idx = parent . index ( node )
idSuffix = 0
raw = Path ( node . get ( " d " ) ) . to_arrays ( )
subpaths , prev = [ ] , 0
for i in range ( len ( raw ) ) : # Breaks compound paths into simple paths
if raw [ i ] [ 0 ] == ' M ' and i != 0 :
subpaths . append ( raw [ prev : i ] )
prev = i
subpaths . append ( raw [ prev : ] )
for subpath in subpaths :
replacedNode = copy . copy ( node )
oldId = replacedNode . get ( ' id ' )
replacedNode . set ( ' d ' , CubicSuperPath ( subpath ) )
replacedNode . set ( ' id ' , oldId + str ( idSuffix ) . zfill ( 5 ) )
parent . insert ( idx , replacedNode )
idSuffix + = 1
replacedNodes . append ( replacedNode )
parent . remove ( node )
for child in node :
self . breakContours ( child )
return replacedNodes
def scanContours ( self , node ) :
if node . tag == inkex . addNS ( ' path ' , ' svg ' ) :
if self . options . removefillsetstroke :
adjustStyle ( self , node )
dot_group = node . getparent ( ) . add ( inkex . Group ( ) )
raw = ( Path ( node . get ( ' d ' ) ) . to_arrays ( ) )
subpaths , prev = [ ] , 0
for i in range ( len ( raw ) ) : # Breaks compound paths into simple paths
if raw [ i ] [ 0 ] == ' M ' and i != 0 :
subpaths . append ( raw [ prev : i ] )
prev = i
subpaths . append ( raw [ prev : ] )
for simpath in subpaths :
if len ( simpath ) > 0 :
closed = False
if simpath [ - 1 ] [ 0 ] == ' Z ' :
closed = True
if not closed :
if self . options . highlight_opened :
style = { ' stroke-linejoin ' : ' miter ' , ' stroke-width ' : str ( self . svg . unittouu ( str ( self . options . strokewidth ) + " px " ) ) ,
' stroke-opacity ' : ' 1.0 ' , ' fill-opacity ' : ' 1.0 ' ,
' stroke ' : self . options . color_opened , ' stroke-linecap ' : ' butt ' , ' fill ' : ' none ' }
node . attrib [ ' style ' ] = Style ( style ) . to_str ( )
if self . options . remove_opened :
try :
node . getparent ( ) . remove ( node )
except AttributeError :
pass #we ignore that parent can be None
if closed :
if self . options . highlight_closed :
style = { ' stroke-linejoin ' : ' miter ' , ' stroke-width ' : str ( self . svg . unittouu ( str ( self . options . strokewidth ) + " px " ) ) ,
' stroke-opacity ' : ' 1.0 ' , ' fill-opacity ' : ' 1.0 ' ,
' stroke ' : self . options . color_closed , ' stroke-linecap ' : ' butt ' , ' fill ' : ' none ' }
node . attrib [ ' style ' ] = Style ( style ) . to_str ( )
if self . options . remove_closed :
try :
node . getparent ( ) . remove ( node )
except AttributeError :
pass #we ignore that parent can be None
for simpath in subpaths :
closed = False
if simpath [ - 1 ] [ 0 ] == ' Z ' :
closed = True
if simpath [ - 2 ] [ 0 ] == ' L ' : simpath [ - 1 ] [ 1 ] = simpath [ 0 ] [ 1 ]
else : simpath . pop ( )
nodes = [ ]
for i in range ( len ( simpath ) ) :
if simpath [ i ] [ 0 ] == ' V ' : # vertical and horizontal lines only have one point in args, but 2 are required
simpath [ i ] [ 0 ] = ' L ' #overwrite V with regular L command
add = simpath [ i - 1 ] [ 1 ] [ 0 ] #read the X value from previous segment
simpath [ i ] [ 1 ] . append ( simpath [ i ] [ 1 ] [ 0 ] ) #add the second (missing) argument by taking argument from previous segment
simpath [ i ] [ 1 ] [ 0 ] = add #replace with recent X after Y was appended
if simpath [ i ] [ 0 ] == ' H ' : # vertical and horizontal lines only have one point in args, but 2 are required
simpath [ i ] [ 0 ] = ' L ' #overwrite H with regular L command
simpath [ i ] [ 1 ] . append ( simpath [ i - 1 ] [ 1 ] [ 1 ] ) #add the second (missing) argument by taking argument from previous segment
nodes . append ( simpath [ i ] [ 1 ] [ - 2 : ] )
#if one of the options is activated we also check for self-intersecting
if self . options . highlight_selfintersecting or self . options . highlight_intersectionpoints :
try :
if len ( nodes ) > 0 : #try to find self-intersecting /overlapping polygons
2020-08-09 21:47:40 +02:00
isect = fablabchemnitz_poly_point_isect . isect_polygon ( nodes ) #TODO: FIND OUT HOW TO HANDLE OPEN CONTOURS TO OMIT VIRTUALLY CROSSING LINES (WHICH DO NOT INTERSECT)
2020-08-09 21:25:19 +02:00
if len ( isect ) > 0 :
#make dot markings at the intersection points
if self . options . highlight_intersectionpoints :
for xy in isect :
#Add a dot label for this path element
2020-08-19 14:06:44 +02:00
style = inkex . Style ( { ' stroke ' : ' none ' , ' fill ' : self . options . color_intersectionpoints } )
2020-08-09 21:25:19 +02:00
circle = dot_group . add ( Circle ( cx = str ( xy [ 0 ] ) , cy = str ( xy [ 1 ] ) , r = str ( self . svg . unittouu ( str ( self . options . dotsize / 2 ) + " px " ) ) ) )
circle . style = style
if self . options . highlight_selfintersecting :
style = { ' stroke-linejoin ' : ' miter ' , ' stroke-width ' : str ( self . svg . unittouu ( str ( self . options . strokewidth ) + " px " ) ) ,
' stroke-opacity ' : ' 1.0 ' , ' fill-opacity ' : ' 1.0 ' ,
2020-08-19 14:06:44 +02:00
' stroke ' : self . options . color_selfintersecting , ' stroke-linecap ' : ' butt ' , ' fill ' : ' none ' }
2020-08-09 21:25:19 +02:00
node . attrib [ ' style ' ] = Style ( style ) . to_str ( )
if self . options . remove_selfintersecting :
if node . getparent ( ) is not None : #might be already been deleted by previously checked settings so check again
node . getparent ( ) . remove ( node )
2020-08-13 01:28:19 +02:00
except Exception as e : # we skip AssertionError
#inkex.utils.debug("Accuracy Error. Try to reduce the precision of the paths using the extension called Rounder to cutoff unrequired decimals.")
print ( str ( e ) )
2020-08-09 21:25:19 +02:00
for child in node :
self . scanContours ( child )
def effect ( self ) :
if self . options . breakapart :
if len ( self . svg . selected ) == 0 :
self . breakContours ( self . document . getroot ( ) )
self . scanContours ( self . document . getroot ( ) )
else :
newContourSet = [ ]
for id , item in self . svg . selected . items ( ) :
newContourSet . append ( self . breakContours ( item ) )
for newContours in newContourSet :
for newContour in newContours :
self . scanContours ( newContour )
else :
if len ( self . svg . selected ) == 0 :
self . scanContours ( self . document . getroot ( ) )
else :
for id , item in self . svg . selected . items ( ) :
self . scanContours ( item )
ContourScanner ( ) . run ( )