Added Contour Scanner (written by myself)

This commit is contained in:
Mario Voigt 2020-08-09 21:25:19 +02:00
parent bb3505ed9e
commit bdb77f587c
7 changed files with 1553 additions and 606 deletions

View File

@ -32,6 +32,7 @@ import sys
import math
import re
import inkex
from inkex.paths import CubicSuperPath, Path
inkex.localization.localize()
from optparse import SUPPRESS_HELP
debug = False
@ -149,7 +150,7 @@ class ChainPaths(inkex.Effect):
inkex.errormsg(_("Object " + id + " is not a path. Try\n - Path->Object to Path\n - Object->Ungroup"))
return
if debug: inkex.utils.debug("id=" + str(id) + ", tag=" + str(node.tag))
path_d = inkex.paths.CubicSuperPath(inkex.paths.Path(node.get('d')))
path_d = CubicSuperPath(Path(node.get('d')))
sub_idx = -1
for sub in path_d:
sub_idx += 1
@ -180,7 +181,7 @@ class ChainPaths(inkex.Effect):
remaining = 0
for id in self.svg.selected:
node = self.svg.selected[id]
path_d = inkex.paths.CubicSuperPath(inkex.paths.Path(node.get('d')))
path_d = CubicSuperPath(Path(node.get('d')))
# ATTENTION: for parsePath() it is the same, if first and last point coincide, or if the path is really closed.
path_closed = True if re.search("z\s*$", node.get('d')) else False
new = []
@ -269,7 +270,7 @@ class ChainPaths(inkex.Effect):
else:
remaining += 1
# BUG: All previously closed loops, are open, after we convert them back with cubicsuperpath.formatPath()
p_fmt = str(inkex.paths.Path(inkex.paths.CubicSuperPath(new).to_path().to_arrays()))
p_fmt = str(Path(CubicSuperPath(new).to_path().to_arrays()))
if path_closed: p_fmt += " z"
if debug: inkex.utils.debug("new path: "+str(p_fmt))
node.set('d', p_fmt)

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Contour Scanner</name>
<id>fablabchemnitz.de.contour_scanner</id>
<param name="main_tabs" type="notebook">
<page name="tab_active" gui-text="Active">
<param name="desc" type="description">This tool helps you to find nasty contours which might bug you and prevent your work from being ready for production. It will find open contours, closed contours and self-intersecting contours. Intersecting contours can be closed or open contours so you can select this option additionally! Last ones usually happen if two or more handles (points) are coincident but which you might don't see. Or you just have large overlaps where the contour crosses itself like an 'eight' character for example. Using the highlighting it's easy to find contours with unproper path handles. Note that if you did not select any paths it will scan the whole document instead.</param>
<param name="help_general" type="description" appearance="header">General</param>
<param name="breakapart" type="bool" gui-text="Break apart contours">false</param>
<param name="removefillsetstroke" type="bool" gui-text="Remove fill and define stroke">false</param>
<param name="strokewidth" min="0.0" max="10000.0" gui-text="Stroke width (px)" type="float">1.0</param>
<param name="help_highlight" type="description" appearance="header">Highlight paths</param>
<param name="highlight_opened" type="bool" gui-text="Highlight opened contours">true</param>
<param name="color_opened" type="color" appearance="colorbutton" gui-text="Color opened contours">#FF0000FF</param>
<param name="highlight_closed" type="bool" gui-text="Highlight closed contours">true</param>
<param name="color_closed" type="color" appearance="colorbutton" gui-text="Color closed contours">#00FF00FF</param>
<param name="highlight_selfintersecting" type="bool" gui-text="Highlight self-intersecting contours">true</param>
<param name="color_selfintersecting" type="color" appearance="colorbutton" gui-text="Color self-intersecting contours">#0000FFFF</param>
<param name="highlight_intersectionpoints" type="bool" gui-text="Highlight self-intersecting points">true</param>
<param name="color_intersectionpoints" type="color" appearance="colorbutton" gui-text="Color self-intersecting points">#0066FFFF</param>
<param name="dotsize" type="int" min="0" max="10000" gui-text="Dot size (px) for self-intersecting points">10</param>
<param name="help_remove" type="description" appearance="header">Remove paths</param>
<param name="remove_opened" type="bool" gui-text="Remove opened contours">false</param>
<param name="remove_closed" type="bool" gui-text="Remove closed contours">false</param>
<param name="remove_selfintersecting" type="bool" gui-text="Remove self-intersecting contours">false</param>
</page>
<page name="tab_info" gui-text="License/Version">
<param name="desc1" type="description">Written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz) (https://gitea.fablabchemnitz.de)</param>
<param name="desc2" type="description">Last update: 09.08.2020</param>
<param name="desc3" type="description">This piece of software is part of the MightyScape for InkScape 1.0/1.1dev Extension Collection</param>
<param name="desc4" type="description" appearance="header">you found a bug or got some fresh code? Just report to mario.voigt@stadtfabrikanten.org. Thanks!</param>
</page>
</param>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Nesting/Cut Optimization"/>
</submenu>
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">fablabchemnitz_contour_scanner.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,213 @@
#!/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. This only works correctly if your path is within the canvas correctly. Otherwise you might get "assert(event.in_sweep == False) AssertionError". This is commented out yet
- 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
License: GNU GPL v3
"""
import sys
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 pout(t):
sys.exit()
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 getColorString(self, pickerColor):
longcolor = int(pickerColor)
if longcolor < 0:
longcolor = longcolor & 0xFFFFFFFF
return '#' + format(longcolor >> 8, '06X')
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='#FF0000FF', 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='#00FF00FF', 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='#0000FFFF', help="Color closed contours")
self.arg_parser.add_argument("--color_intersectionpoints", type=Color, default='#0066FFFF', 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
isect = fablabchemnitz_poly_point_isect.isect_polygon(nodes) # TODO: CREATE MARKERS FOR THIS
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.getColorString(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.getColorString(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 AssertionError as e: # we skip AssertionError
pass
#inkex.utils.debug("Found some path which cannot be tested for self-intersecting behaviour")
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()

View File

@ -16,7 +16,7 @@
#
import inkex
import sys
from inkex import paths
from inkex.paths import CubicSuperPath
from inkex import transforms
def warn(*args, **kwargs):
@ -28,15 +28,15 @@ class ExportXY(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
def effect(self):
for node in self.selected.items():
for node in self.svg.selected.items():
output_all = output_nodes = ""
for id, node in self.selected.items():
for id, node in self.svg.selected.items():
if node.tag == inkex.addNS('path','svg'):
output_all += ""
output_nodes += ""
node.apply_transform()
d = node.get('d')
p = paths.CubicSuperPath(d)
p = CubicSuperPath(d)
for subpath in p:
for csp in subpath:
output_nodes += str(csp[1][0]) + "\t" + str(csp[1][1]) + "\n"

File diff suppressed because it is too large Load Diff

View File

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Shapes</name>
<id>fablabchemnitz.de.shapes</id>
<param name="help-ren" type="description" xml:space="preserve">Create shapes using the bounding box
of the selected objects</param>
<param name="tab" type="notebook">
<page name="chamfer" gui-text="From corners">
<param name="chamfertype" type="optiongroup" appearance="combo" gui-text="Corners type:">
<_item value="rombus">Rombus</_item>
<_item value="chamfer">Chamfer</_item>
<_item value="chamferinv">Chamfer inverse</_item>
<_item value="rect">Rect inside</_item>
<_item value="round">Round inside</_item>
<_item value="roundinv">Round inside inverse</_item>
<_item value="cross">Cross</_item>
<_item value="starcenter">Star from center</_item>
<_item value="starcorners">Star from corners</_item>
<_item value="crosscornersquad">Crossed corners quads</_item>
<_item value="crosscornerstri">Crossed corners tris</_item>
<_item value="crosscornersround">Crossed corners round</_item>
</param>
<param name="size" type="float" min="0.0" max="1000.0" gui-text="Size:">20</param>
</page>
<page name="triangles" gui-text="Triangles">
<param name="tritype" type="optiongroup" appearance="combo" gui-text="Triangle type:">
<_item value="isosceles">Isosceles</_item>
<_item value="equi">Equilateral</_item>
<_item value="rect">Rectangle</_item>
</param>
<param name="trihside" type="bool" gui-text="Right side aligned">false</param>
<param name="trivside" type="bool" gui-text="Top side aligned">false</param>
</page>
<page name="spikes" gui-text="Spikes">
<param name="spikestype" type="optiongroup" appearance="combo" gui-text="Shape:">
<_item value="tri">Triangle</_item>
<_item value="trirect">Rectangle</_item>
<_item value="squ">Square</_item>
<_item value="rnd">Rounded</_item>
<_item value="wav">Wave</_item>
</param>
<param name="spikesdir" type="optiongroup" appearance="combo" gui-text="Direction:">
<_item value="out">Outside</_item>
<_item value="ins">Inside</_item>
<_item value="alt">Alternate</_item>
</param>
<param name="spikesize" type="float" min="0.1" max="1000.0" gui-text="Size:" appearance="full">2.0</param>
<param name="spikesep" type="float" min="-1000.0" max="1000.0" gui-text="Separation:" appearance="full">0.0</param>
</page>
<page name="arrow" gui-text="Arrows">
<param name="arrowtype" type="optiongroup" appearance="combo" gui-text="Arrow type:">
<_item value="arrowfilled">Filled</_item>
<_item value="arrowstick">Stick</_item>
</param>
<param name="headWidth" type="float" min="0.1" max="1000.0" gui-text="Head width:">20.0</param>
<param name="headHeight" type="float" min="0.1" max="1000.0" gui-text="Head height:">40.0</param>
<param name="arrowWidth" type="float" min="0.1" max="1000.0" gui-text="Tail width:">10.0</param>
</page>
<page name="extra" gui-text="Join circles">
<param name="joincirctype" type="optiongroup" appearance="combo" gui-text="Function:">
<_item value="trapecio">Rect</_item>
<_item value="blob">Blob</_item>
<_item value="oval">Oval</_item>
</param>
<param name="joinradius" type="float" min="0.0" max="1000.0" gui-text="Join radius:">0.0</param>
<param name="help-circ" type="description" appearance="header" xml:space="preserve">Need to perform boolean operations.</param>
</page>
</param>
<param name="incdec" type="float" min="-1000.0" max="1000.0" gui-text="Increase/decrease size:">0.0</param>
<param name="unit" gui-text="Unit for values:" type="optiongroup" appearance="combo">
<option value="px">px</option>
<option value="pt">pt</option>
<option value="in">in</option>
<option value="cm">cm</option>
<option value="mm">mm</option>
</param>
<param name="squareselection" type="bool" gui-text="Make the result object square">false</param>
<param name="copyfill" type="bool" gui-text="Copy fill from selected">false</param>
<param name="deleteorigin" type="bool" gui-text="Delete origin object">false</param>
<effect needs-live-preview="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Shape/Pattern from existing Path(s)"/>
</submenu>
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">fablabchemnitz_shapes.py</command>
</script>
</inkscape-extension>

View File

@ -1,508 +0,0 @@
#!/usr/bin/env python3
'''
shapes.py
Copyright (C) 2015-2017 Paco Garcia, www.arakne.es
2017_07_30: added crossed corners
copy class of original object if exists
2017_08_09: rombus moved to From corners tab
2017_08_17: join circles not need boolen operations now
join circles added Oval
2017_08_25: fixed error in objects without style
in oval sets the minimal radius necessary
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-----------------------
'''
import locale
import os
import sys
import tempfile
import webbrowser
import math
from subprocess import Popen, PIPE
import inkex
from fablabchemnitz_arakne_xy import *
from lxml import etree
defStyle = [['stroke-width','0.5'],['fill','#f0ff00'],['stroke','#ff0000']]
locale.setlocale(locale.LC_ALL, '')
class Shapes(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.arg_parser.add_argument("--tab")
self.arg_parser.add_argument("--chamfertype")
self.arg_parser.add_argument("--size", type=float, default="20")
self.arg_parser.add_argument("--incdec", type=float, default="0")
self.arg_parser.add_argument("--tritype", default="")
self.arg_parser.add_argument("--spikestype")
self.arg_parser.add_argument("--spikesdir")
self.arg_parser.add_argument("--unit")
self.arg_parser.add_argument("--spikesize", type=float, default="2.0")
self.arg_parser.add_argument("--spikesep", type=float, default="0.0")
self.arg_parser.add_argument("--arrowtype", default="")
self.arg_parser.add_argument("--headWidth", type=float, default="20.0")
self.arg_parser.add_argument("--headHeight", type=float, default="40.0")
self.arg_parser.add_argument("--arrowWidth", type=float, default="10.0")
self.arg_parser.add_argument("--squareselection", type=inkex.Boolean, default="false")
self.arg_parser.add_argument("--trihside", type=inkex.Boolean, default="false")
self.arg_parser.add_argument("--trivside", type=inkex.Boolean, default="false")
self.arg_parser.add_argument("--copyfill", type=inkex.Boolean, default="false")
self.arg_parser.add_argument("--deleteorigin", type=inkex.Boolean, default="false")
self.arg_parser.add_argument("--joincirctype", default="")
self.arg_parser.add_argument("--joinradius", type=float, default="0.0")
def addEle(self, ele, parent, props):
elem = etree.SubElement(parent, ele)
for n in props: elem.set(n,props[n])
return elem
def chStyles(self,node,sty):
style = inkex.Style.parse_str(node.get('style'))
for n in sty:
if str(style) in n[0]: style.pop(n[0], None)
#if n[1]!="": style[n[0]]=n[1]
node.set('style',str(inkex.Style(style)))
def unit2uu(self, val):
if hasattr(self,"unittouu") is True:
return self.unittouu(val)
else:
return inkex.unittouu(val)
def limits(self, node):
s = node.bounding_box()
l,r,t,b = (s.left,s.right,s.top,s.bottom)
an,al = (r - l, b - t)
incdec = self.svg.unittouu(self.options.incdec)
l, t, r, b, an, al = (l - incdec, t - incdec, r + incdec, b + incdec, an + incdec*2, al + incdec*2)
return (l,r,t,b,an,al)
def estilo(self, nn, orig, style=defStyle):
if self.options.copyfill:
if orig.get('style'):
nn.set("style", orig.get('style'))
if orig.get('class'):
nn.set("class", orig.get('class'))
else:
self.chStyles(nn,style)
def circleABCD(self,p,r,abcd="ABCD",inverse=False,xtra=None):
aa = r * 0.551915024494
parts={
'A':[XY(0,-r),XY(aa,-r), XY(r, -aa),XY(r,0)],
'B':[XY(r,0), XY(r, aa), XY(aa, r),XY(0,r)],
'C':[XY(0,r), XY(-aa,r), XY(-r, aa),XY(-r,0)],
'D':[XY(-r,0),XY(-r,-aa),XY(-aa,-r),XY(0,-r)]}
#pA = parts[abcd[0]]
pA = [XY(p)+N for N in parts[abcd[0]]]
for aa in abcd[1:]:
pA = pA + [XY(p)+N for N in parts[aa][1:]]
if inverse==True: pA.reverse()
listA = XYList(pA)
if xtra:
for n in xtra:
listA[n].extend(xtra[n])
return listA
def draw(self, node, sh='"rombus"'):
#inkex.errormsg(sh)
sO = self.options
l, r, t, b, an, al = self.limits(node)
sqSel = sO.squareselection
copyfill = sO.copyfill
deleteorigin=sO.deleteorigin
side = min(al,an)
if sqSel:
incx=(an-side)/2.0
l,r,an =(l+incx,r-incx,side)
incy=(al-side)/2.0
t +=incy
b -=incy
al = side
cX, cY = (an/2.0,al/2.0)
pp = node.getparent()
varBez = 0.551915024494
a = self.svg.unittouu(sO.size)
a_2, a2 = (a / 2.0,a * 2.0)
dS = "m %sz"
pnts = [[l+cX,t],[cX,cY],[-cX,cY],[-cX,-cY]]
aa = a * varBez
chtype=sO.chamfertype
tritype=sO.tritype
if sh=='"chamfer"':
an2, al2 = ((an-a)/2.0,(al-a)/2.0)
if chtype=="rombus" and a>0:
pnts=[[l+cX - a_2,t],[a,0],[an2,al2],[0,a],[-an2,al2],[-a,0],[-an2,-al2],[0,-a]]
if chtype=="chamfer":
pnts=[[l+a,t],[an - a2,0],[a,a],[0,al-a2],[-a,a],[-(an - a2),0],[-a,-a],[0,-(al-a2)]]
if chtype=="chamferinv":
pnts=[[l,t],[a,0],[-a,a],[an-a,0," z m"],[a,0],[0,a],[a,al," z m"],[0,-a],[-a,a],[-an+a,0," z m"],[-a,-a],[0,a]]
if chtype=="round":
pnts = circQ(XY(l,t),a,"B",0,{1:"C"}) + circQ(XY(l,b),a,"A",0,{0:"L",1:"C"}) + circQ(XY(r,b),a,"D",0,{0:"L",1:"C"}) + circQ(XY(r,t),a,"C",0,{0:"L",1:"C"})
if chtype=="roundinv":
pnts=[[l,t],[a,0],[0,aa,"c "],[-aa,a],[-a,a],[an-a,0,"z m "],[a,0],[0,a],[-aa,0," c"],[-a,-aa],[-a,-a],
[a,al-a,"z m "],[0,a],[-a,0],[0,-aa,"c "],[aa,-a],[a,-a],[-an,0,"z m "],[0,a],[a,0],[0,-aa,"c "],[-aa,-a],[-a,-a]]
if chtype=="rect":
pnts=[[l+a,t],[an - a2,0],[0,a],[a,0],[0,al-a2],[-a,0],[0,a],[-(an-a2),0],[0,-a],[-a,0],[0,-(al-a2)],[a,0]]
if chtype=="cross":
pnts=[[l+an2,t],[a,0],[0,al2],[an2,0],[0,a],[-an2,0],[0,al2],[-a,0],[0,-al2],[-an2,0],[0,-a],[an2,0]]
if chtype=="starcorners":
pnts=[[l,t],[cX,al2],[cX,-al2],[-an2,cY],[an2,cY],[-cX,-al2],[-cX,al2],[an2,-cY]]
if chtype=="starcenter":
pnts=[[l+cX,t],[a_2,al2], [an2,a_2], [-an2,a_2],[-a_2,al2],[-a_2,-al2],[-an2,-a_2],[an2,-a_2]]
if chtype=="crosscornersquad":
pnts=[[l-a,t],[0,-a],[a,0],[0,al+a*2],[-a,0],[0,-a],[an+a*2,0],[0,a],[-a,0],[0,-al-a*2],[a,0],[0,a]]
if chtype=="crosscornerstri":
pnts=[[l-a,t],[a,-a],[0,al+a*2],[-a,-a],[an+a*2,0],[-a,a], [0,-al-a*2],[a,a]]
if chtype=="crosscornersround":
dS = "M %sZ"
aa2 = a_2 * varBez
p1 = circQ(XY(r + a_2, t - a_2),a_2,"DAB",1)
p2 = circQ(XY(r + a_2, b + a_2),a_2,"ABC",1)
p3 = circQ(XY(l - a_2, b + a_2),a_2,"BCD",1)
p4 = circQ(XY(l - a_2, t - a_2),a_2,"CDA",1)
pnts = p1 + [[r,t],[r,b+a_2-aa2]] + p2 + [[r+a_2-aa2,b],[l-a_2+aa2,b]] + p3 + [[l,b+a_2-aa],[l,t-a_2+aa]] + p4
pnts[1].append(" C")
if sh=='"triangles"':
trihside, trivside=(sO.trihside, sO.trivside)
if tritype=="isosceles": pnts=[[l+cX,t],[cX,al],[-an,0]]
if tritype=="equi":
sqrt3 = 1.7320508075
height = sqrt3/2*side
tcx, tcy = ((an - side)/2.0, (al - height)/2.0)
pnts=[[cX+l,t+tcy],[an/2.0-tcx,height],[-side,0]]
if tritype=="rect":
x1 = l + tern(not trivside and trihside,an,0)
x2 = tern(not trivside and trihside,0,an)
x3 = tern(trivside and trihside,0,-an)
pnts=[[x1,t],[x2,tern(not trivside,al,0)],[x3,tern(not trivside,0,al)]]
if sh=='"spikes"':
spikestype = sO.spikestype
spikesdir = sO.spikesdir
ssep = self.svg.unittouu(sO.spikesep)
ss = self.svg.unittouu(sO.spikesize)
anX, anY = (int( (an+ssep) / (ss * 2 + ssep)), int( (al+ssep) / (ss * 2 + ssep)))
iniX, iniY = (((an+ssep) - (anX * (ss * 2 + ssep))) / 2.0, ((al+ssep) - (anY * (ss * 2 + ssep))) / 2.0)
dir = 1
pnts = [[l,t],[iniX,0]]
if spikesdir=='ins': dir = -1.0
if spikestype=="tri":
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[ss,-ss*dir],[ss,ss*dir]])
if ssep>0 and n < (anX-1): pnts.append([ssep,0])
pnts.extend([[iniX,0],[0,iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[ss * dir,ss],[-ss * dir,ss]])
if ssep>0 and n < (anY-1): pnts.append([0, ssep])
pnts.extend([[0,iniY],[-iniX,0]])
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[-ss,ss*dir],[-ss,-ss*dir]])
if ssep>0 and n < (anX-1): pnts.append([-ssep,0])
pnts.extend([[-iniX,0],[0,-iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[-ss*dir,-ss],[ss*dir,-ss]])
if ssep>0 and n < (anY-1): pnts.append([0, -ssep])
if spikestype=="trirect":
anX, anY = ( int((an + ssep) / (ss + ssep)), int((al + ssep) / (ss + ssep)) )
iniX, iniY = (((an + ssep) - (anX * (ss + ssep))) / 2.0, ((al + ssep) - (anY * (ss + ssep))) / 2.0)
pnts = [[l,t],[iniX,0]]
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[0,-ss*dir],[ss,ss*dir]])
if ssep>0 and n < (anX-1): pnts.append([ssep,0])
pnts.extend([[iniX,0],[0,iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[ss * dir,0],[-ss * dir,ss]])
if ssep>0 and n < (anY-1): pnts.append([0, ssep])
pnts.extend([[0,iniY],[-iniX,0]])
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[0,ss*dir],[-ss,-ss*dir]])
if ssep>0 and n < (anX-1): pnts.append([-ssep,0])
pnts.extend([[-iniX,0],[0,-iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[-ss*dir,0],[ss*dir,-ss]])
if ssep>0 and n < (anY-1): pnts.append([0, -ssep])
if spikestype=="squ":
anX, anY = ( int((an + ssep) / (ss + ssep)), int((al + ssep) / (ss + ssep)) )
iniX, iniY = (((an + ssep) - (anX * (ss + ssep))) / 2.0, ((al + ssep) - (anY * (ss + ssep))) / 2.0)
pnts = [[l,t],[iniX,0]]
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[0,-ss * dir], [ss,0], [0,ss * dir]])
if ssep>0 and n < (anX-1): pnts.append([ssep,0])
pnts.extend([[iniX,0],[0,iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[ss * dir,0],[0,ss],[-ss * dir,0]])
if ssep>0 and n < (anY-1): pnts.append([0,ssep])
pnts.extend([[0,iniY],[-iniX,0]])
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[0,ss * dir],[-ss,0],[0,-ss * dir]])
if ssep>0 and n < (anX-1): pnts.append([-ssep,0])
pnts.extend([[-iniX,0],[0,-iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[-ss * dir,0],[0,-ss],[ss * dir,0]])
if ssep>0 and n < (anY-1): pnts.append([0,-ssep])
if spikestype=="rnd":
dif = ss - (ss*varBez)
dBez = ss*varBez
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[0,-dBez * dir," c"],[dif,-ss * dir],[ss,-ss * dir],#fijo
[dBez,0],[ss,dif * dir],[ss,ss * dir]]) #fijo
if ssep>0 and n < (anX-1): pnts.append([ssep,0,' l'])
pnts.extend([[iniX,0," l"],[0,iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[dBez * dir,0," c"],[ss * dir,dif],[ss * dir,ss],#fijo
[0,dBez],[-dif * dir,ss],[-ss * dir,ss]]) #fijo
if ssep>0 and n < (anY-1): pnts.append([0,ssep,' l'])
pnts.extend([[0,iniY,' l'],[-iniX,0]])
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[0,dBez * dir," c"],[-dif,ss * dir],[-ss,ss * dir],#fijo
[-dBez,0],[-ss,-dif * dir],[-ss,-ss * dir]]) #fijo
if ssep>0 and n < (anX-1): pnts.append([-ssep,0,' l'])
pnts.extend([[-iniX,0,' l'],[0,-iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[-dBez * dir,0," c"],[-ss * dir,-dif],[-ss * dir,-ss],#fijo
[0,-dBez],[dif * dir,-ss],[ss * dir,-ss]]) #fijo
if ssep>0 and n < (anY-1): pnts.append([0,-ssep,' l'])
if spikestype=="wav":
dif = ss - (ss*varBez)
dBez = ss*varBez
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[0,-dBez * dir," c"],[dif,-ss * dir],[ss,-ss * dir],#fijo
[0,dBez*dir],[dBez,ss*dir],[ss,ss * dir]]) #fijo
if ssep>0 and n < (anX-1): pnts.append([ssep,0,' l'])
pnts.extend([[iniX,0," l"],[0,iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[dBez * dir,0," c"],[ss * dir,dif],[ss * dir,ss],#fijo
[-dBez*dir,0],[-ss*dir,dBez],[-ss * dir,ss]]) #fijo
if ssep>0 and n < (anY-1): pnts.append([0,ssep,' l'])
pnts.extend([[0,iniY,' l'],[-iniX,0]])
for n in range(anX):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[0,dBez * dir," c"],[-dif,ss * dir],[-ss,ss * dir],#fijo
[0,-dBez*dir], [-dif, -ss*dir],[-ss,-ss * dir]]) #fijo
if ssep>0 and n < (anX-1): pnts.append([-ssep,0,' l'])
pnts.extend([[-iniX,0,' l'],[0,-iniY]])
for n in range(anY):
if spikesdir == 'alt' : dir = 1 if n % 2 == 1 else -1
pnts.extend([[-dBez * dir,0," c"],[-ss * dir,-dif],[-ss * dir,-ss],#fijo
[dBez*dir,0],[ss*dir,-dBez],[ss * dir,-ss]]) #fijo
if ssep>0 and n < (anY-1): pnts.append([0,-ssep,' l'])
if sh=='"arrow"':
arrowType=sO.arrowtype
headH, headW, arrowW = (self.svg.unittouu(sO.headHeight), self.svg.unittouu(sO.headWidth), self.svg.unittouu(sO.arrowWidth))
hw2=headW/2.0
if arrowType=="arrowfilled":
pnts=[[l+cX,t],[hw2,headH],[-(headW-arrowW)/2.0,0],[0,al-headH],[-arrowW,0],[0,-(al-headH)],[-(headW-arrowW)/2.0,0]]
else:
dS = "m %s"
pnts=[[l+cX,t],[0,al],[-hw2,-al+headH,"m "],[hw2,-headH],[hw2,headH]]
d = ""
for n in pnts:
ss = "" if len(n)<3 else n[2]
d += "%s%s,%s " % (ss, str(n[0]),str(n[1]))
nn = self.addEle('path',pp, {'d':dS % (d)})
self.estilo(nn,node)
if deleteorigin: node.getparent().remove(node)
def makeRel(self,arr):
b = arr[:]
for n in range(1,len(arr)):
s = b[n]
for i in range(0,n):
s = s - arr[i]
b[n] = s
return b
def circle(self,p,r):
varBez = 0.551915024494
dS = "m %s"
aa = r * varBez
d=""
pnts=[[p.x - r,p.y],[0,aa,"c "],[r - aa,r],[r,r],[aa,0,"c "],[r,-r+aa],[r,-r],[0,-aa,"c "],[-r+aa,-r],[-r,-r],[-aa,0,"c "],[-r,r-aa],[-r, r]]
for n in pnts:
ss = "" if len(n)<3 else n[2]
d += "%s%s,%s " % (ss, str(n[0]),str(n[1]))
return d
def addTxt(self, node, x, y, text, dy = 0):
new2 = self.addEle(inkex.addNS('text','svg'), node,{'x':str(x),'y':str(y)})
new = etree.SubElement(new2, inkex.addNS('tspan','svg'), {inkex.addNS('role','sodipodi'): 'line'})
new.set('style','text-align:center; vertical-align:bottom; font-size:10; fill-opacity:1.0;stroke:none; font-weight:normal; font-style:normal; fill:#000000')
new.set('dy', str(dy))
new.text = str(text)
def circsCone(self, sels, sh='"rombus"'):
sO = self.options
copyfill = sO.copyfill
deleteorigin = sO.deleteorigin
joincirctype = sO.joincirctype
r2 = sO.joinradius
for nodos in range(len(sels)-1):
node = sels[nodos]
node2 = sels[nodos+1]
lA, rA, tA, bA, anA, alA = self.limits(node)
lB, rB, tB, bB, anB, alB = self.limits(node2)
rA, cY = (anA/2.0,alA/2.0)
rB, cY2 = (anB/2.0,alB/2.0)
PtA = XY(lA + rA, tA + cY)
PtB = XY(lB + rB, tB + cY2)
if (circleInCircle(PtA,rA,PtB,rB) or circleInCircle(PtB,rB,PtA,rA)):
pass
else:
pp = node.getparent()
rotAB = XY(PtB).getAngle(PtA)
dist = PtA.hipo(PtB)
if joincirctype=='trapecio':
# alineamos las esferas en Y
rDif = rA - rB
Axis = XY(-rDif,0)
D2 = math.sqrt((dist*dist) - (rDif*rDif)) / dist
P1 = XY(Axis).mul(rA / dist)
P2 = XY(-dist,0) + XY(Axis).mul(rB / dist)
r = P1.VDist(P2)
Rot1 = XY(P2.x,rB * D2).getAngleD(XY(P2.x + r, rA * D2))
curva1a = bezs2XYList(createArcBez(rA,-90 -Rot1, -270 + Rot1))
d = XYListSt(curva1a, rotAB, PtA)
pnts2 = bezs2XYList(createArcBez(rB, 90 + Rot1, 270 - Rot1),XY(-dist,0))
d2 = XYListSt(pnts2, rotAB, PtA)
nn = self.addEle('path',pp, {'d':"M%s L%sZ" % (d,d2)})
self.estilo(nn,node)
# ################## B L O B ##############
if joincirctype=='blob':
if ((r2==0) and (dist<(rA+rB))):
r2 = dist - rB
if (r2 > 0):
rad1 = rA + r2
rad2 = rB + r2
a = (math.pow(dist,2) - math.pow(rB+r2,2) + math.pow(rA+r2,2))/(dist*2)
else:
r2 = dist - rA - rB
rad1 = dist - rB
rad2 = dist - rA
a = (math.pow(dist-rB,2) - math.pow(dist-rA,2) + math.pow(dist,2))/(dist*2);
# alineamos las esferas en Y
rt = math.atan2(PtB.y - PtA.y, PtB.x - PtA.x)
# # distancia del centro 1 a la interseccion de los circulos
x = (dist * dist - rad2 * rad2 + rad1 * rad1) / (dist*2)
if (rad1 * rad1 - x * x) > 0 :
catB = math.sqrt(rad1 * rad1 - x * x)
rt = math.degrees(XY(0,0).getAngle(XY(-x, -catB)))
rt2 = math.degrees(XY(0,0).getAngle(XY(-(dist - x), -catB)))
curva1 = bezs2XYList(createArcBez(rA, rt, -rt))
curva1.reverse()
curva2 = bezs2XYList(createArcBez(r2, -180 + rt, -rt2),XY(-x, -catB))
curva3 = bezs2XYList(createArcBez(rB, rt2+180,180-rt2),XY(-dist, 0))
curva3.reverse()
curva4 = bezs2XYList(createArcBez(r2, rt2, 180 - rt),XY(-x, catB))
curva1= curva1+curva2[1:]+curva3[1:]+curva4[1:]
sCurva1 = XYListSt(curva1, rotAB, PtA)
nn = self.addEle('path',pp,{'d':"M %s" % (sCurva1)})
self.estilo(nn,node)
# ################################################
# ################## O V A L #####################
# ################################################
if joincirctype=='oval':
minR2 = dist + min(rA,rB)
if r2 < minR2:
r2 = minR2
info('Changed radius to '+str(minR2))
rad1 = r2 - rA
rad2 = r2 - rB
a = (math.pow(dist,2) - math.pow(rB+r2,2) + math.pow(rA+r2,2))/(dist*2)
rt = math.atan2(PtB.y - PtA.y, PtB.x - PtA.x)
D = dist #XY(PtA).sub(PtB).vlength() # distancia entre los centros
# distancia del centro 1 a la interseccion de los circulos
x = (D*D - rad2 * rad2 + rad1 * rad1) / (D*2)
catB = math.sqrt(rad1 * rad1 - x * x)
rotAB=XY(PtB).getAngle(PtA)
rot1 = math.degrees(XY(0,0).getAngle(XY(-x,-catB))) + 180.0
curva1 = bezs2XYList(createArcBez(rA, -rot1, rot1))
curva1.reverse()
rot2 = math.degrees(XY(-dist,0).getAngle(XY(-x,-catB))) +180.0
curva2 = bezs2XYList(createArcBez(r2, -rot2,-rot1),XY(-x,catB))
curva2.reverse()
curva3 = bezs2XYList(createArcBez(rB, rot2,-rot2),XY(-dist,0))
curva3.reverse()
curva4 = bezs2XYList(createArcBez(r2, rot1,rot2),XY(-x,-catB))
curva4.reverse()
curva1= curva1+curva2[1:]+curva3[1:]+curva4[1:] #+curva3[1:]+curva4[1:]
sCurva1 = XYListSt(curva1, rotAB, PtA)
# curva1
nn = self.addEle('path',pp,{'d':"M %sZ" % (sCurva1),'style':'stroke-width:0.02;fill:#cc0000;stroke:#000000;'})
self.estilo(nn,node)
if deleteorigin: node.getparent().remove(node)
def draw_shapes(self):
tab = str(self.options.tab)
sels = []
for id, node in self.svg.selected.items():
sels.append(node)
if tab != '"extra"':
for id, node in self.svg.selected.items():
self.draw(node, tab)
else:
if len(sels)<2:
inkex.errormsg('Select at least two objects')
else:
self.circsCone(sels, tab)
def loc_str(self, str):
return locale.format("%.f", float(str), 0)
def effect(self):
slices = self.draw_shapes()
if __name__ == "__main__":
Shapes().run()