This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz_contour_scanner.py

202 lines
12 KiB
Python
Raw Normal View History

#!/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"
- 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
- 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-20 00:15:29 +02:00
Last patch: 19.08.2020
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")
self.arg_parser.add_argument("--color_opened", type=Color, default='4012452351', help="Color opened contours")
self.arg_parser.add_argument("--highlight_closed", type=inkex.Boolean, default=True, help="Highlight closed contours")
self.arg_parser.add_argument("--color_closed", type=Color, default='2330080511', help="Color closed contours")
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")
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")
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)
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
style = inkex.Style({'stroke': 'none', 'fill': self.options.color_intersectionpoints})
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',
'stroke': self.options.color_selfintersecting, 'stroke-linecap': 'butt', 'fill': 'none'}
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)
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))
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()