diff --git a/extensions/fablabchemnitz_chain_paths.py b/extensions/fablabchemnitz_chain_paths.py index 478f9aaa..3fd3591e 100644 --- a/extensions/fablabchemnitz_chain_paths.py +++ b/extensions/fablabchemnitz_chain_paths.py @@ -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) diff --git a/extensions/fablabchemnitz_contour_scanner.inx b/extensions/fablabchemnitz_contour_scanner.inx new file mode 100644 index 00000000..58fa1641 --- /dev/null +++ b/extensions/fablabchemnitz_contour_scanner.inx @@ -0,0 +1,45 @@ + + + Contour Scanner + fablabchemnitz.de.contour_scanner + + + 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. + General + false + false + 1.0 + Highlight paths + true + #FF0000FF + true + #00FF00FF + true + #0000FFFF + true + #0066FFFF + 10 + Remove paths + false + false + false + + + Written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz) (https://gitea.fablabchemnitz.de) + Last update: 09.08.2020 + This piece of software is part of the MightyScape for InkScape 1.0/1.1dev Extension Collection + you found a bug or got some fresh code? Just report to mario.voigt@stadtfabrikanten.org. Thanks! + + + + path + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz_contour_scanner.py b/extensions/fablabchemnitz_contour_scanner.py new file mode 100644 index 00000000..1ef3fcd2 --- /dev/null +++ b/extensions/fablabchemnitz_contour_scanner.py @@ -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() \ No newline at end of file diff --git a/extensions/fablabchemnitz_exportxy.py b/extensions/fablabchemnitz_exportxy.py index 113f2257..075fc404 100644 --- a/extensions/fablabchemnitz_exportxy.py +++ b/extensions/fablabchemnitz_exportxy.py @@ -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" diff --git a/extensions/fablabchemnitz_poly_point_isect.py b/extensions/fablabchemnitz_poly_point_isect.py new file mode 100644 index 00000000..c60d3c5f --- /dev/null +++ b/extensions/fablabchemnitz_poly_point_isect.py @@ -0,0 +1,1287 @@ + +# BentleyOttmann sweep-line implementation +# (for finding all intersections in a set of line segments) + +__all__ = ( + "isect_segments", + "isect_polygon", + + # same as above but includes segments with each intersections + "isect_segments_include_segments", + "isect_polygon_include_segments", + + # for testing only (correct but slow) + "isect_segments__naive", + "isect_polygon__naive", +) + +# ---------------------------------------------------------------------------- +# Main Poly Intersection + +# Defines to change behavior. +# +# Whether to ignore intersections of line segments when both +# their end points form the intersection point. +USE_IGNORE_SEGMENT_ENDINGS = True + +USE_DEBUG = True + +USE_VERBOSE = False + +# checks we should NOT need, +# but do them in case we find a test-case that fails. +USE_PARANOID = False + +# Support vertical segments, +# (the bentley-ottmann method doesn't support this). +# We use the term 'START_VERTICAL' for a vertical segment, +# to differentiate it from START/END/INTERSECTION +USE_VERTICAL = True +# end defines! +# ------------ + +# --------- +# Constants +X, Y = 0, 1 + +# ----------------------------------------------------------------------------- +# Switchable Number Implementation + +NUMBER_TYPE = 'native' + +if NUMBER_TYPE == 'native': + Real = float + NUM_EPS = Real("1e-10") + NUM_INF = Real(float("inf")) +elif NUMBER_TYPE == 'decimal': + # Not passing tests! + import decimal + Real = decimal.Decimal + decimal.getcontext().prec = 80 + NUM_EPS = Real("1e-10") + NUM_INF = Real(float("inf")) +elif NUMBER_TYPE == 'numpy': + import numpy + Real = numpy.float64 + del numpy + NUM_EPS = Real("1e-10") + NUM_INF = Real(float("inf")) +elif NUMBER_TYPE == 'gmpy2': + # Not passing tests! + import gmpy2 + gmpy2.set_context(gmpy2.ieee(128)) + Real = gmpy2.mpz + NUM_EPS = Real(float("1e-10")) + NUM_INF = gmpy2.get_emax_max() + del gmpy2 +else: + raise Exception("Type not found") + +NUM_EPS_SQ = NUM_EPS * NUM_EPS +NUM_ZERO = Real(0.0) +NUM_ONE = Real(1.0) + + +class Event: + __slots__ = ( + "type", + "point", + "segment", + + # this is just cache, + # we may remove or calculate slope on the fly + "slope", + "span", + ) + (() if not USE_DEBUG else ( + # debugging only + "other", + "in_sweep", + )) + + class Type: + END = 0 + INTERSECTION = 1 + START = 2 + if USE_VERTICAL: + START_VERTICAL = 3 + + def __init__(self, type, point, segment, slope): + assert(isinstance(point, tuple)) + self.type = type + self.point = point + self.segment = segment + + # will be None for INTERSECTION + self.slope = slope + if segment is not None: + self.span = segment[1][X] - segment[0][X] + + if USE_DEBUG: + self.other = None + self.in_sweep = False + + # note that this isn't essential, + # it just avoids non-deterministic ordering, see #9. + def __hash__(self): + return hash(self.point) + + def is_vertical(self): + # return self.segment[0][X] == self.segment[1][X] + return self.span == NUM_ZERO + + def y_intercept_x(self, x: Real): + # vertical events only for comparison (above_all check) + # never added into the binary-tree its self + if USE_VERTICAL: + if self.is_vertical(): + return None + + if x <= self.segment[0][X]: + return self.segment[0][Y] + elif x >= self.segment[1][X]: + return self.segment[1][Y] + + # use the largest to avoid float precision error with nearly vertical lines. + delta_x0 = x - self.segment[0][X] + delta_x1 = self.segment[1][X] - x + if delta_x0 > delta_x1: + ifac = delta_x0 / self.span + fac = NUM_ONE - ifac + else: + fac = delta_x1 / self.span + ifac = NUM_ONE - fac + assert(fac <= NUM_ONE) + return (self.segment[0][Y] * fac) + (self.segment[1][Y] * ifac) + + @staticmethod + def Compare(sweep_line, this, that): + if this is that: + return 0 + if USE_DEBUG: + if this.other is that: + return 0 + current_point_x = sweep_line._current_event_point_x + this_y = this.y_intercept_x(current_point_x) + that_y = that.y_intercept_x(current_point_x) + # print(this_y, that_y) + if USE_VERTICAL: + if this_y is None: + this_y = this.point[Y] + if that_y is None: + that_y = that.point[Y] + + delta_y = this_y - that_y + + assert((delta_y < NUM_ZERO) == (this_y < that_y)) + # NOTE, VERY IMPORTANT TO USE EPSILON HERE! + # otherwise w/ float precision errors we get incorrect comparisons + # can get very strange & hard to debug output without this. + if abs(delta_y) > NUM_EPS: + return -1 if (delta_y < NUM_ZERO) else 1 + else: + this_slope = this.slope + that_slope = that.slope + if this_slope != that_slope: + if sweep_line._before: + return -1 if (this_slope > that_slope) else 1 + else: + return 1 if (this_slope > that_slope) else -1 + + delta_x_p1 = this.segment[0][X] - that.segment[0][X] + if delta_x_p1 != NUM_ZERO: + return -1 if (delta_x_p1 < NUM_ZERO) else 1 + + delta_x_p2 = this.segment[1][X] - that.segment[1][X] + if delta_x_p2 != NUM_ZERO: + return -1 if (delta_x_p2 < NUM_ZERO) else 1 + + return 0 + + def __repr__(self): + return ("Event(0x%x, s0=%r, s1=%r, p=%r, type=%d, slope=%r)" % ( + id(self), + self.segment[0], self.segment[1], + self.point, + self.type, + self.slope, + )) + + +class SweepLine: + __slots__ = ( + # A map holding all intersection points mapped to the Events + # that form these intersections. + # {Point: set(Event, ...), ...} + "intersections", + "queue", + + # Events (sorted set of ordered events, no values) + # + # note: START & END events are considered the same so checking if an event is in the tree + # will return true if its opposite side is found. + # This is essential for the algorithm to work, and why we don't explicitly remove START events. + # Instead, the END events are never added to the current sweep, and removing them also removes the start. + "_events_current_sweep", + # The point of the current Event. + "_current_event_point_x", + # A flag to indicate if we're slightly before or after the line. + "_before", + ) + + def __init__(self): + self.intersections = {} + + self._current_event_point_x = None + self._events_current_sweep = RBTree(cmp=Event.Compare, cmp_data=self) + self._before = True + + def get_intersections(self): + """ + Return a list of unordered intersection points. + """ + if Real is float: + return list(self.intersections.keys()) + else: + return [(float(p[0]), float(p[1])) for p in self.intersections.keys()] + + # Not essential for implementing this algorithm, but useful. + def get_intersections_with_segments(self): + """ + Return a list of unordered intersection '(point, segment)' pairs, + where segments may contain 2 or more values. + """ + if Real is float: + return [ + (p, [event.segment for event in event_set]) + for p, event_set in self.intersections.items() + ] + else: + return [ + ( + (float(p[0]), float(p[1])), + [((float(event.segment[0][0]), float(event.segment[0][1])), + (float(event.segment[1][0]), float(event.segment[1][1]))) + for event in event_set], + ) + for p, event_set in self.intersections.items() + ] + + # Checks if an intersection exists between two Events 'a' and 'b'. + def _check_intersection(self, a: Event, b: Event): + # Return immediately in case either of the events is null, or + # if one of them is an INTERSECTION event. + if ( + (a is None or b is None) or + (a.type == Event.Type.INTERSECTION) or + (b.type == Event.Type.INTERSECTION) + ): + return + + if a is b: + return + + # Get the intersection point between 'a' and 'b'. + p = isect_seg_seg_v2_point( + a.segment[0], a.segment[1], + b.segment[0], b.segment[1], + ) + + # No intersection exists. + if p is None: + return + + # If the intersection is formed by both the segment endings, AND + # USE_IGNORE_SEGMENT_ENDINGS is true, + # return from this method. + if USE_IGNORE_SEGMENT_ENDINGS: + if ((len_squared_v2v2(p, a.segment[0]) < NUM_EPS_SQ or + len_squared_v2v2(p, a.segment[1]) < NUM_EPS_SQ) and + (len_squared_v2v2(p, b.segment[0]) < NUM_EPS_SQ or + len_squared_v2v2(p, b.segment[1]) < NUM_EPS_SQ)): + + return + + # Add the intersection. + events_for_point = self.intersections.pop(p, set()) + is_new = len(events_for_point) == 0 + events_for_point.add(a) + events_for_point.add(b) + self.intersections[p] = events_for_point + + # If the intersection occurs to the right of the sweep line, OR + # if the intersection is on the sweep line and it's above the + # current event-point, add it as a new Event to the queue. + if is_new and p[X] >= self._current_event_point_x: + event_isect = Event(Event.Type.INTERSECTION, p, None, None) + self.queue.offer(p, event_isect) + + def _sweep_to(self, p): + if p[X] == self._current_event_point_x: + # happens in rare cases, + # we can safely ignore + return + + self._current_event_point_x = p[X] + + def insert(self, event): + assert(event not in self._events_current_sweep) + assert(not USE_VERTICAL or event.type != Event.Type.START_VERTICAL) + if USE_DEBUG: + assert(event.in_sweep == False) + assert(event.other.in_sweep == False) + + self._events_current_sweep.insert(event, None) + + if USE_DEBUG: + event.in_sweep = True + event.other.in_sweep = True + + def remove(self, event): + try: + self._events_current_sweep.remove(event) + if USE_DEBUG: + assert(event.in_sweep == True) + assert(event.other.in_sweep == True) + event.in_sweep = False + event.other.in_sweep = False + return True + except KeyError: + if USE_DEBUG: + assert(event.in_sweep == False) + assert(event.other.in_sweep == False) + return False + + def above(self, event): + return self._events_current_sweep.succ_key(event, None) + + def below(self, event): + return self._events_current_sweep.prev_key(event, None) + + ''' + def above_all(self, event): + while True: + event = self.above(event) + if event is None: + break + yield event + ''' + + def above_all(self, event): + # assert(event not in self._events_current_sweep) + return self._events_current_sweep.key_slice(event, None, reverse=False) + + def handle(self, p, events_current): + if len(events_current) == 0: + return + # done already + # self._sweep_to(events_current[0]) + assert(p[0] == self._current_event_point_x) + + if not USE_IGNORE_SEGMENT_ENDINGS: + if len(events_current) > 1: + for i in range(0, len(events_current) - 1): + for j in range(i + 1, len(events_current)): + self._check_intersection( + events_current[i], events_current[j]) + + for e in events_current: + self.handle_event(e) + + def handle_event(self, event): + t = event.type + if t == Event.Type.START: + # print(" START") + self._before = False + self.insert(event) + + e_above = self.above(event) + e_below = self.below(event) + + self._check_intersection(event, e_above) + self._check_intersection(event, e_below) + if USE_PARANOID: + self._check_intersection(e_above, e_below) + + elif t == Event.Type.END: + # print(" END") + self._before = True + + e_above = self.above(event) + e_below = self.below(event) + + self.remove(event) + + self._check_intersection(e_above, e_below) + if USE_PARANOID: + self._check_intersection(event, e_above) + self._check_intersection(event, e_below) + + elif t == Event.Type.INTERSECTION: + # print(" INTERSECTION") + self._before = True + event_set = self.intersections[event.point] + # note: events_current aren't sorted. + reinsert_stack = [] # Stack + for e in event_set: + # Since we know the Event wasn't already removed, + # we want to insert it later on. + if self.remove(e): + reinsert_stack.append(e) + self._before = False + + # Insert all Events that we were able to remove. + while reinsert_stack: + e = reinsert_stack.pop() + + self.insert(e) + + e_above = self.above(e) + e_below = self.below(e) + + self._check_intersection(e, e_above) + self._check_intersection(e, e_below) + if USE_PARANOID: + self._check_intersection(e_above, e_below) + elif (USE_VERTICAL and + (t == Event.Type.START_VERTICAL)): + + # just check sanity + assert(event.segment[0][X] == event.segment[1][X]) + assert(event.segment[0][Y] <= event.segment[1][Y]) + + # In this case we only need to find all segments in this span. + y_above_max = event.segment[1][Y] + + # self.insert(event) + for e_above in self.above_all(event): + if e_above.type == Event.Type.START_VERTICAL: + continue + y_above = e_above.y_intercept_x( + self._current_event_point_x) + if USE_IGNORE_SEGMENT_ENDINGS: + if y_above >= y_above_max - NUM_EPS: + break + else: + if y_above > y_above_max: + break + + # We know this intersects, + # so we could use a faster function now: + # ix = (self._current_event_point_x, y_above) + # ...however best use existing functions + # since it does all sanity checks on endpoints... etc. + self._check_intersection(event, e_above) + + # self.remove(event) + + +class EventQueue: + __slots__ = ( + # note: we only ever pop_min, this could use a 'heap' structure. + # The sorted map holding the points -> event list + # [Point: Event] (tree) + "events_scan", + ) + + def __init__(self, segments, line: SweepLine): + self.events_scan = RBTree() + # segments = [s for s in segments if s[0][0] != s[1][0] and s[0][1] != s[1][1]] + + for s in segments: + assert(s[0][X] <= s[1][X]) + + slope = slope_v2v2(*s) + + if s[0] == s[1]: + pass + elif USE_VERTICAL and (s[0][X] == s[1][X]): + e_start = Event(Event.Type.START_VERTICAL, s[0], s, slope) + + if USE_DEBUG: + e_start.other = e_start # FAKE, avoid error checking + + self.offer(s[0], e_start) + else: + e_start = Event(Event.Type.START, s[0], s, slope) + e_end = Event(Event.Type.END, s[1], s, slope) + + if USE_DEBUG: + e_start.other = e_end + e_end.other = e_start + + self.offer(s[0], e_start) + self.offer(s[1], e_end) + + line.queue = self + + def offer(self, p, e: Event): + """ + Offer a new event ``s`` at point ``p`` in this queue. + """ + existing = self.events_scan.setdefault( + p, ([], [], [], []) if USE_VERTICAL else + ([], [], []), + ) + # Can use double linked-list for easy insertion at beginning/end + ''' + if e.type == Event.Type.END: + existing.insert(0, e) + else: + existing.append(e) + ''' + + existing[e.type].append(e) + + # return a set of events + def poll(self): + """ + Get, and remove, the first (lowest) item from this queue. + + :return: the first (lowest) item from this queue. + :rtype: Point, Event pair. + """ + assert(len(self.events_scan) != 0) + p, events_current = self.events_scan.pop_min() + return p, events_current + + +def isect_segments_impl(segments, include_segments=False) -> list: + # order points left -> right + if Real is float: + segments = [ + # in nearly all cases, comparing X is enough, + # but compare Y too for vertical lines + (s[0], s[1]) if (s[0] <= s[1]) else + (s[1], s[0]) + for s in segments] + else: + segments = [ + # in nearly all cases, comparing X is enough, + # but compare Y too for vertical lines + ( + (Real(s[0][0]), Real(s[0][1])), + (Real(s[1][0]), Real(s[1][1])), + ) if (s[0] <= s[1]) else + ( + (Real(s[1][0]), Real(s[1][1])), + (Real(s[0][0]), Real(s[0][1])), + ) + for s in segments] + + sweep_line = SweepLine() + queue = EventQueue(segments, sweep_line) + + while len(queue.events_scan) > 0: + if USE_VERBOSE: + print(len(queue.events_scan), sweep_line._current_event_point_x) + p, e_ls = queue.poll() + for events_current in e_ls: + if events_current: + sweep_line._sweep_to(p) + sweep_line.handle(p, events_current) + + if include_segments is False: + return sweep_line.get_intersections() + else: + return sweep_line.get_intersections_with_segments() + + +def isect_polygon_impl(points, include_segments=False) -> list: + n = len(points) + segments = [ + (tuple(points[i]), tuple(points[(i + 1) % n])) + for i in range(n)] + return isect_segments_impl(segments, include_segments=include_segments) + + +def isect_segments(segments) -> list: + return isect_segments_impl(segments, include_segments=False) + + +def isect_polygon(segments) -> list: + return isect_polygon_impl(segments, include_segments=False) + + +def isect_segments_include_segments(segments) -> list: + return isect_segments_impl(segments, include_segments=True) + + +def isect_polygon_include_segments(segments) -> list: + return isect_polygon_impl(segments, include_segments=True) + + +# ---------------------------------------------------------------------------- +# 2D math utilities + + +def slope_v2v2(p1, p2): + if p1[X] == p2[X]: + if p1[Y] < p2[Y]: + return NUM_INF + else: + return -NUM_INF + else: + return (p2[Y] - p1[Y]) / (p2[X] - p1[X]) + + +def sub_v2v2(a, b): + return ( + a[0] - b[0], + a[1] - b[1]) + + +def dot_v2v2(a, b): + return ( + (a[0] * b[0]) + + (a[1] * b[1])) + + +def len_squared_v2v2(a, b): + c = sub_v2v2(a, b) + return dot_v2v2(c, c) + + +def line_point_factor_v2(p, l1, l2, default=NUM_ZERO): + u = sub_v2v2(l2, l1) + h = sub_v2v2(p, l1) + dot = dot_v2v2(u, u) + return (dot_v2v2(u, h) / dot) if dot != NUM_ZERO else default + + +def isect_seg_seg_v2_point(v1, v2, v3, v4, bias=NUM_ZERO): + # Only for predictability and hashable point when same input is given + if v1 > v2: + v1, v2 = v2, v1 + if v3 > v4: + v3, v4 = v4, v3 + + if (v1, v2) > (v3, v4): + v1, v2, v3, v4 = v3, v4, v1, v2 + + div = (v2[0] - v1[0]) * (v4[1] - v3[1]) - (v2[1] - v1[1]) * (v4[0] - v3[0]) + if div == NUM_ZERO: + return None + + vi = (((v3[0] - v4[0]) * + (v1[0] * v2[1] - v1[1] * v2[0]) - (v1[0] - v2[0]) * + (v3[0] * v4[1] - v3[1] * v4[0])) / div, + ((v3[1] - v4[1]) * + (v1[0] * v2[1] - v1[1] * v2[0]) - (v1[1] - v2[1]) * + (v3[0] * v4[1] - v3[1] * v4[0])) / div, + ) + + fac = line_point_factor_v2(vi, v1, v2, default=-NUM_ONE) + if fac < NUM_ZERO - bias or fac > NUM_ONE + bias: + return None + + fac = line_point_factor_v2(vi, v3, v4, default=-NUM_ONE) + if fac < NUM_ZERO - bias or fac > NUM_ONE + bias: + return None + + # vi = round(vi[X], 8), round(vi[Y], 8) + return vi + + +# ---------------------------------------------------------------------------- +# Simple naive line intersect, (for testing only) + + +def isect_segments__naive(segments) -> list: + """ + Brute force O(n2) version of ``isect_segments`` for test validation. + """ + isect = [] + + # order points left -> right + if Real is float: + segments = [ + (s[0], s[1]) if s[0][X] <= s[1][X] else + (s[1], s[0]) + for s in segments] + else: + segments = [ + ( + (Real(s[0][0]), Real(s[0][1])), + (Real(s[1][0]), Real(s[1][1])), + ) if (s[0] <= s[1]) else + ( + (Real(s[1][0]), Real(s[1][1])), + (Real(s[0][0]), Real(s[0][1])), + ) + for s in segments] + + n = len(segments) + + for i in range(n): + a0, a1 = segments[i] + for j in range(i + 1, n): + b0, b1 = segments[j] + if a0 not in (b0, b1) and a1 not in (b0, b1): + ix = isect_seg_seg_v2_point(a0, a1, b0, b1) + if ix is not None: + # USE_IGNORE_SEGMENT_ENDINGS handled already + isect.append(ix) + + return isect + + +def isect_polygon__naive(points) -> list: + """ + Brute force O(n2) version of ``isect_polygon`` for test validation. + """ + isect = [] + + n = len(points) + + if Real is float: + pass + else: + points = [(Real(p[0]), Real(p[1])) for p in points] + + + for i in range(n): + a0, a1 = points[i], points[(i + 1) % n] + for j in range(i + 1, n): + b0, b1 = points[j], points[(j + 1) % n] + if a0 not in (b0, b1) and a1 not in (b0, b1): + ix = isect_seg_seg_v2_point(a0, a1, b0, b1) + if ix is not None: + + if USE_IGNORE_SEGMENT_ENDINGS: + if ((len_squared_v2v2(ix, a0) < NUM_EPS_SQ or + len_squared_v2v2(ix, a1) < NUM_EPS_SQ) and + (len_squared_v2v2(ix, b0) < NUM_EPS_SQ or + len_squared_v2v2(ix, b1) < NUM_EPS_SQ)): + continue + + isect.append(ix) + + return isect + + +# ---------------------------------------------------------------------------- +# Inline Libs +# +# bintrees: 2.0.2, extracted from: +# http://pypi.python.org/pypi/bintrees +# +# - Removed unused functions, such as slicing and range iteration. +# - Added 'cmp' and and 'cmp_data' arguments, +# so we can define our own comparison that takes an arg. +# Needed for sweep-line. +# - Added support for 'default' arguments for prev_item/succ_item, +# so we can avoid exception handling. + +# ------- +# ABCTree + +from operator import attrgetter +_sentinel = object() + + +class _ABCTree(object): + def __init__(self, items=None, cmp=None, cmp_data=None): + """T.__init__(...) initializes T; see T.__class__.__doc__ for signature""" + self._root = None + self._count = 0 + if cmp is None: + def cmp(cmp_data, a, b): + if a < b: + return -1 + elif a > b: + return 1 + else: + return 0 + self._cmp = cmp + self._cmp_data = cmp_data + if items is not None: + self.update(items) + + def clear(self): + """T.clear() -> None. Remove all items from T.""" + def _clear(node): + if node is not None: + _clear(node.left) + _clear(node.right) + node.free() + _clear(self._root) + self._count = 0 + self._root = None + + @property + def count(self): + """Get items count.""" + return self._count + + def get_value(self, key): + node = self._root + while node is not None: + cmp = self._cmp(self._cmp_data, key, node.key) + if cmp == 0: + return node.value + elif cmp < 0: + node = node.left + else: + node = node.right + raise KeyError(str(key)) + + def pop_item(self): + """T.pop_item() -> (k, v), remove and return some (key, value) pair as a + 2-tuple; but raise KeyError if T is empty. + """ + if self.is_empty(): + raise KeyError("pop_item(): tree is empty") + node = self._root + while True: + if node.left is not None: + node = node.left + elif node.right is not None: + node = node.right + else: + break + key = node.key + value = node.value + self.remove(key) + return key, value + popitem = pop_item # for compatibility to dict() + + def min_item(self): + """Get item with min key of tree, raises ValueError if tree is empty.""" + if self.is_empty(): + raise ValueError("Tree is empty") + node = self._root + while node.left is not None: + node = node.left + return node.key, node.value + + def max_item(self): + """Get item with max key of tree, raises ValueError if tree is empty.""" + if self.is_empty(): + raise ValueError("Tree is empty") + node = self._root + while node.right is not None: + node = node.right + return node.key, node.value + + def succ_item(self, key, default=_sentinel): + """Get successor (k,v) pair of key, raises KeyError if key is max key + or key does not exist. optimized for pypy. + """ + # removed graingets version, because it was little slower on CPython and much slower on pypy + # this version runs about 4x faster with pypy than the Cython version + # Note: Code sharing of succ_item() and ceiling_item() is possible, but has always a speed penalty. + node = self._root + succ_node = None + while node is not None: + cmp = self._cmp(self._cmp_data, key, node.key) + if cmp == 0: + break + elif cmp < 0: + if (succ_node is None) or self._cmp(self._cmp_data, node.key, succ_node.key) < 0: + succ_node = node + node = node.left + else: + node = node.right + + if node is None: # stay at dead end + if default is _sentinel: + raise KeyError(str(key)) + return default + # found node of key + if node.right is not None: + # find smallest node of right subtree + node = node.right + while node.left is not None: + node = node.left + if succ_node is None: + succ_node = node + elif self._cmp(self._cmp_data, node.key, succ_node.key) < 0: + succ_node = node + elif succ_node is None: # given key is biggest in tree + if default is _sentinel: + raise KeyError(str(key)) + return default + return succ_node.key, succ_node.value + + def prev_item(self, key, default=_sentinel): + """Get predecessor (k,v) pair of key, raises KeyError if key is min key + or key does not exist. optimized for pypy. + """ + # removed graingets version, because it was little slower on CPython and much slower on pypy + # this version runs about 4x faster with pypy than the Cython version + # Note: Code sharing of prev_item() and floor_item() is possible, but has always a speed penalty. + node = self._root + prev_node = None + + while node is not None: + cmp = self._cmp(self._cmp_data, key, node.key) + if cmp == 0: + break + elif cmp < 0: + node = node.left + else: + if (prev_node is None) or self._cmp(self._cmp_data, prev_node.key, node.key) < 0: + prev_node = node + node = node.right + + if node is None: # stay at dead end (None) + if default is _sentinel: + raise KeyError(str(key)) + return default + # found node of key + if node.left is not None: + # find biggest node of left subtree + node = node.left + while node.right is not None: + node = node.right + if prev_node is None: + prev_node = node + elif self._cmp(self._cmp_data, prev_node.key, node.key) < 0: + prev_node = node + elif prev_node is None: # given key is smallest in tree + if default is _sentinel: + raise KeyError(str(key)) + return default + return prev_node.key, prev_node.value + + def __repr__(self): + """T.__repr__(...) <==> repr(x)""" + tpl = "%s({%s})" % (self.__class__.__name__, '%s') + return tpl % ", ".join(("%r: %r" % item for item in self.items())) + + def __contains__(self, key): + """k in T -> True if T has a key k, else False""" + try: + self.get_value(key) + return True + except KeyError: + return False + + def __len__(self): + """T.__len__() <==> len(x)""" + return self.count + + def is_empty(self): + """T.is_empty() -> False if T contains any items else True""" + return self.count == 0 + + def set_default(self, key, default=None): + """T.set_default(k[,d]) -> T.get(k,d), also set T[k]=d if k not in T""" + try: + return self.get_value(key) + except KeyError: + self.insert(key, default) + return default + setdefault = set_default # for compatibility to dict() + + def get(self, key, default=None): + """T.get(k[,d]) -> T[k] if k in T, else d. d defaults to None.""" + try: + return self.get_value(key) + except KeyError: + return default + + def pop(self, key, *args): + """T.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised + """ + if len(args) > 1: + raise TypeError("pop expected at most 2 arguments, got %d" % (1 + len(args))) + try: + value = self.get_value(key) + self.remove(key) + return value + except KeyError: + if len(args) == 0: + raise + else: + return args[0] + + def prev_key(self, key, default=_sentinel): + """Get predecessor to key, raises KeyError if key is min key + or key does not exist. + """ + item = self.prev_item(key, default) + return default if item is default else item[0] + + def succ_key(self, key, default=_sentinel): + """Get successor to key, raises KeyError if key is max key + or key does not exist. + """ + item = self.succ_item(key, default) + return default if item is default else item[0] + + def pop_min(self): + """T.pop_min() -> (k, v), remove item with minimum key, raise ValueError + if T is empty. + """ + item = self.min_item() + self.remove(item[0]) + return item + + def pop_max(self): + """T.pop_max() -> (k, v), remove item with maximum key, raise ValueError + if T is empty. + """ + item = self.max_item() + self.remove(item[0]) + return item + + def min_key(self): + """Get min key of tree, raises ValueError if tree is empty. """ + return self.min_item()[0] + + def max_key(self): + """Get max key of tree, raises ValueError if tree is empty. """ + return self.max_item()[0] + + def key_slice(self, start_key, end_key, reverse=False): + """T.key_slice(start_key, end_key) -> key iterator: + start_key <= key < end_key. + + Yields keys in ascending order if reverse is False else in descending order. + """ + return (k for k, v in self.iter_items(start_key, end_key, reverse=reverse)) + + def iter_items(self, start_key=None, end_key=None, reverse=False): + """Iterates over the (key, value) items of the associated tree, + in ascending order if reverse is True, iterate in descending order, + reverse defaults to False""" + # optimized iterator (reduced method calls) - faster on CPython but slower on pypy + + if self.is_empty(): + return [] + if reverse: + return self._iter_items_backward(start_key, end_key) + else: + return self._iter_items_forward(start_key, end_key) + + def _iter_items_forward(self, start_key=None, end_key=None): + for item in self._iter_items(left=attrgetter("left"), right=attrgetter("right"), + start_key=start_key, end_key=end_key): + yield item + + def _iter_items_backward(self, start_key=None, end_key=None): + for item in self._iter_items(left=attrgetter("right"), right=attrgetter("left"), + start_key=start_key, end_key=end_key): + yield item + + def _iter_items(self, left=attrgetter("left"), right=attrgetter("right"), start_key=None, end_key=None): + node = self._root + stack = [] + go_left = True + in_range = self._get_in_range_func(start_key, end_key) + + while True: + if left(node) is not None and go_left: + stack.append(node) + node = left(node) + else: + if in_range(node.key): + yield node.key, node.value + if right(node) is not None: + node = right(node) + go_left = True + else: + if not len(stack): + return # all done + node = stack.pop() + go_left = False + + def _get_in_range_func(self, start_key, end_key): + if start_key is None and end_key is None: + return lambda x: True + else: + if start_key is None: + start_key = self.min_key() + if end_key is None: + return (lambda x: self._cmp(self._cmp_data, start_key, x) <= 0) + else: + return (lambda x: self._cmp(self._cmp_data, start_key, x) <= 0 and + self._cmp(self._cmp_data, x, end_key) < 0) + + +# ------ +# RBTree + +class Node(object): + """Internal object, represents a tree node.""" + __slots__ = ['key', 'value', 'red', 'left', 'right'] + + def __init__(self, key=None, value=None): + self.key = key + self.value = value + self.red = True + self.left = None + self.right = None + + def free(self): + self.left = None + self.right = None + self.key = None + self.value = None + + def __getitem__(self, key): + """N.__getitem__(key) <==> x[key], where key is 0 (left) or 1 (right).""" + return self.left if key == 0 else self.right + + def __setitem__(self, key, value): + """N.__setitem__(key, value) <==> x[key]=value, where key is 0 (left) or 1 (right).""" + if key == 0: + self.left = value + else: + self.right = value + + +class RBTree(_ABCTree): + """ + RBTree implements a balanced binary tree with a dict-like interface. + + see: http://en.wikipedia.org/wiki/Red_black_tree + """ + @staticmethod + def is_red(node): + if (node is not None) and node.red: + return True + else: + return False + + @staticmethod + def jsw_single(root, direction): + other_side = 1 - direction + save = root[other_side] + root[other_side] = save[direction] + save[direction] = root + root.red = True + save.red = False + return save + + @staticmethod + def jsw_double(root, direction): + other_side = 1 - direction + root[other_side] = RBTree.jsw_single(root[other_side], other_side) + return RBTree.jsw_single(root, direction) + + def _new_node(self, key, value): + """Create a new tree node.""" + self._count += 1 + return Node(key, value) + + def insert(self, key, value): + """T.insert(key, value) <==> T[key] = value, insert key, value into tree.""" + if self._root is None: # Empty tree case + self._root = self._new_node(key, value) + self._root.red = False # make root black + return + + head = Node() # False tree root + grand_parent = None + grand_grand_parent = head + parent = None # parent + direction = 0 + last = 0 + + # Set up helpers + grand_grand_parent.right = self._root + node = grand_grand_parent.right + # Search down the tree + while True: + if node is None: # Insert new node at the bottom + node = self._new_node(key, value) + parent[direction] = node + elif RBTree.is_red(node.left) and RBTree.is_red(node.right): # Color flip + node.red = True + node.left.red = False + node.right.red = False + + # Fix red violation + if RBTree.is_red(node) and RBTree.is_red(parent): + direction2 = 1 if grand_grand_parent.right is grand_parent else 0 + if node is parent[last]: + grand_grand_parent[direction2] = RBTree.jsw_single(grand_parent, 1 - last) + else: + grand_grand_parent[direction2] = RBTree.jsw_double(grand_parent, 1 - last) + + # Stop if found + if self._cmp(self._cmp_data, key, node.key) == 0: + node.value = value # set new value for key + break + + last = direction + direction = 0 if (self._cmp(self._cmp_data, key, node.key) < 0) else 1 + # Update helpers + if grand_parent is not None: + grand_grand_parent = grand_parent + grand_parent = parent + parent = node + node = node[direction] + + self._root = head.right # Update root + self._root.red = False # make root black + + def remove(self, key): + """T.remove(key) <==> del T[key], remove item from tree.""" + if self._root is None: + raise KeyError(str(key)) + head = Node() # False tree root + node = head + node.right = self._root + parent = None + grand_parent = None + found = None # Found item + direction = 1 + + # Search and push a red down + while node[direction] is not None: + last = direction + + # Update helpers + grand_parent = parent + parent = node + node = node[direction] + + direction = 1 if (self._cmp(self._cmp_data, node.key, key) < 0) else 0 + + # Save found node + if self._cmp(self._cmp_data, key, node.key) == 0: + found = node + + # Push the red node down + if not RBTree.is_red(node) and not RBTree.is_red(node[direction]): + if RBTree.is_red(node[1 - direction]): + parent[last] = RBTree.jsw_single(node, direction) + parent = parent[last] + elif not RBTree.is_red(node[1 - direction]): + sibling = parent[1 - last] + if sibling is not None: + if (not RBTree.is_red(sibling[1 - last])) and (not RBTree.is_red(sibling[last])): + # Color flip + parent.red = False + sibling.red = True + node.red = True + else: + direction2 = 1 if grand_parent.right is parent else 0 + if RBTree.is_red(sibling[last]): + grand_parent[direction2] = RBTree.jsw_double(parent, last) + elif RBTree.is_red(sibling[1-last]): + grand_parent[direction2] = RBTree.jsw_single(parent, last) + # Ensure correct coloring + grand_parent[direction2].red = True + node.red = True + grand_parent[direction2].left.red = False + grand_parent[direction2].right.red = False + + # Replace and remove if found + if found is not None: + found.key = node.key + found.value = node.value + parent[int(parent.right is node)] = node[int(node.left is None)] + node.free() + self._count -= 1 + + # Update root and make it black + self._root = head.right + if self._root is not None: + self._root.red = False + if not found: + raise KeyError(str(key)) diff --git a/extensions/fablabchemnitz_shapes.inx b/extensions/fablabchemnitz_shapes.inx deleted file mode 100644 index 223a3f74..00000000 --- a/extensions/fablabchemnitz_shapes.inx +++ /dev/null @@ -1,91 +0,0 @@ - - - Shapes - fablabchemnitz.de.shapes - Create shapes using the bounding box -of the selected objects - - - - <_item value="rombus">Rombus - <_item value="chamfer">Chamfer - <_item value="chamferinv">Chamfer inverse - <_item value="rect">Rect inside - <_item value="round">Round inside - <_item value="roundinv">Round inside inverse - <_item value="cross">Cross - <_item value="starcenter">Star from center - <_item value="starcorners">Star from corners - <_item value="crosscornersquad">Crossed corners quads - <_item value="crosscornerstri">Crossed corners tris - <_item value="crosscornersround">Crossed corners round - - 20 - - - - <_item value="isosceles">Isosceles - <_item value="equi">Equilateral - <_item value="rect">Rectangle - - false - false - - - - <_item value="tri">Triangle - <_item value="trirect">Rectangle - <_item value="squ">Square - <_item value="rnd">Rounded - <_item value="wav">Wave - - - <_item value="out">Outside - <_item value="ins">Inside - <_item value="alt">Alternate - - 2.0 - 0.0 - - - - <_item value="arrowfilled">Filled - <_item value="arrowstick">Stick - - 20.0 - 40.0 - 10.0 - - - - <_item value="trapecio">Rect - <_item value="blob">Blob - <_item value="oval">Oval - - 0.0 - Need to perform boolean operations. - - - 0.0 - - - - - - - - false - false - false - - all - - - - - - - - \ No newline at end of file diff --git a/extensions/fablabchemnitz_shapes.py b/extensions/fablabchemnitz_shapes.py deleted file mode 100644 index 207921a8..00000000 --- a/extensions/fablabchemnitz_shapes.py +++ /dev/null @@ -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() \ No newline at end of file