added more extensions

This commit is contained in:
Mario Voigt 2022-09-20 17:50:38 +02:00
parent 7cab1a92ea
commit 680edc2e15
15 changed files with 1213 additions and 0 deletions

View File

@ -0,0 +1,20 @@
[
{
"name": "Random Delete",
"id": "fablabchemnitz.de.random_delete",
"path": "random_delete",
"dependent_extensions": null,
"original_name": "Random Delete",
"original_id": "fablabchemnitz.de.random_delete",
"license": "GNU GPL v3",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/LICENSE",
"comment": "Written by Mario Voigt",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/random_delete",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Random+Delete",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/vmario89"
]
}
]

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Random Delete</name>
<id>fablabchemnitz.de.random_delete</id>
<param name="prob" type="float" gui-text="Probability of deletion (%):" min="0.0" max="100.0">100.0</param>
<label>This extension allows to delete with a certain probability each of the selected objects.</label>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Various"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">random_delete.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,21 @@
#! /usr/bin/env python3
import random
import inkex
class RandomDelete(inkex.Effect):
def add_arguments(self, pars):
pars.add_argument("--prob", type=float, default=50, help="Probability of deletion")
def effect(self):
if len(self.svg.selected) > 0:
for element in self.svg.selection.values():
if random.random() < self.options.prob/100:
element.delete()
else:
self.msg('Please select some paths first.')
return
if __name__ == '__main__':
RandomDelete().run()

View File

@ -0,0 +1,21 @@
[
{
"name": "Round Corners (Replaced by LPE)",
"id": "fablabchemnitz.de.round_corners",
"path": "round_corners",
"dependent_extensions": null,
"original_name": "Round Corners",
"original_id": "org.inkscape.jnweiger.round_corners",
"license": "GNU GPL v2",
"license_url": "https://github.com/jnweiger/inkscape-round-corners/blob/main/LICENSE",
"comment": "You can do nearly the same with Live Path Effect!",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/round_corners",
"fork_url": "https://github.com/jnweiger/inkscape-round-corners",
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=94175520",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/jnweiger",
"github.com/vmario89"
]
}
]

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Round Corners (Replaced by LPE)</name>
<id>fablabchemnitz.de.round_corners</id>
<param name="radius" type="float" gui-text="Radius: [mm]" precision="2" min="0.001" max="999.99">2.0</param>
<param name="method" type="optiongroup" appearance="radio" gui-text="Corner type:">
<option value="arc">Arc</option>
<option value="line">Line</option>
</param>
<label xml:space="preserve">* Select a path in edit mode.
* Select one or more vertices.
* Start the extension,
- set the radius of the arc.
- Apply
Each selected vertex is replaced by two or more vertices forming
a bezier spline that approximates an arc of the given radius.
When the corner type is set to 'line', the arc is
replaced with a straight cut.
Version: 1.4
</label>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Modify existing Path(s)"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">round_corners.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,521 @@
#!/usr/bin/env python3
# coding=utf-8
#
# Copyright (C) 2020 Juergen Weigert, jnweiger@gmail.com
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# v0.1, 2020-11-08, jw - initial draught, finding and printing selected nodes to the terminal...
# v0.2, 2020-11-08, jw - duplicate the selected nodes in their superpaths, write them back.
# v0.3, 2020-11-21, jw - find "meta-handles"
# v0.4, 2020-11-26, jw - alpha and trim math added. trimming with a striaght line implemented, needs fixes.
# Option 'cut' added.
# v0.5, 2020-11-28, jw - Cut operation looks correct. Dummy midpoint for large arcs added, looks wrong, of course.
# v1.0, 2020-11-30, jw - Code completed. Bot cut and arc work fine.
# v1.1, 2020-12-07, jw - Replaced boolean 'cut' with a method selector 'arc'/'line'. Added round_corners_092.inx
# and started backport in round_corners.py -- attempting to run the same code everywhere.
# v1.2, 2020-12-08, jw - Backporting continued: option parser hack added. Started effect_wrapper() to prepare self.svg
# v1.3, 2020-12-12, jw - minimalistic compatibility layer for inkscape 0.92.4 done. It now works in both, 1.0 and 0.92!
# v1.4, 2020-12-15, jw - find_roundable_nodes() added for auto selecting nodes, if none were selected.
# And fix https://github.com/jnweiger/inkscape-round-corners/issues/2
# 2021-01-15, Mario Voigt - removed oboslete InkScape 0.92.* stuff
#
# Bad side-effect: As the node count increases during operation, the list of
# selected nodes is incorrect afterwards. We have no way to give inkscape an update.
#
"""
Rounded Corners
This extension operates on selected sharp corner nodes and converts them to a fillet (bevel,chamfer).
An arc shaped path segment with the given radius is inserted smoothly.
The fitted arc is approximated by a bezier spline, as we are doing path operations here.
When the sides at the corner are straight lines, the operation never move the sides, it just shortens them to fit the arc.
When the sides are curved, the arc is placed on the tanget line, and the curve may thus change in shape.
Selected smooth nodes are skipped.
Cases with insufficient space (180deg turn or too short segments/handles) are warned about.
References:
- https://gitlab.com/inkscape/extensions/-/wikis/home
- https://gitlab.com/inkscape/extras/extensions-tutorials/-/blob/master/My-First-Effect-Extension.md
- https://gitlab.com/inkscape/extensions/-/wikis/uploads/25063b4ae6c3396fcda428105c5cff89/template_effect.zip
- https://inkscape-extensions-guide.readthedocs.io/en/latest/_modules/inkex/elements.html#ShapeElement.get_path
- https://inkscape.gitlab.io/extensions/documentation/_modules/inkex/paths.html#CubicSuperPath.to_path
- https://stackoverflow.com/questions/734076/how-to-best-approximate-a-geometrical-arc-with-a-bezier-curve
- https://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html
- https://itc.ktu.lt/index.php/ITC/article/download/11812/6479 (Riskus' PDF)
The algorithm of arc_bezier_handles() is based on the approach described in:
A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa,"
Information Technology and Control, 35(4), 2006 pp. 371-378.
"""
import inkex
import sys
import math
import pprint
import copy
import os
__version__ = '1.4' # Keep in sync with round_corners.inx line 16
debug = False # True: babble on controlling tty
max_trim_factor = 0.90 # 0.5: can cut half of a segment length or handle length away for rounding a corner
max_trim_factor_single = 0.98 # 0.98: we can eat up almost everything, as there are no neighbouring trims to be expected.
class RoundCorners(inkex.EffectExtension):
def add_arguments(self, pars): # an __init__ in disguise ...
try:
self.tty = open("/dev/tty", 'w')
except:
self.tty = open(os.devnull, 'w') # '/dev/null' for POSIX, 'nul' for Windows.
if debug: print("RoundedCorners ...", file=self.tty)
self.nodes_inserted = {}
self.eps = 0.00001 # avoid division by zero
self.radius = None
self.max_trim_factor = max_trim_factor
self.skipped_degenerated = 0 # not a useful corner (e.g. 180deg corner)
self.skipped_small_count = 0 # not enough room for arc
self.skipped_small_len = 1e99 # record the shortest handle (or segment) when skipping.
pars.add_argument("--radius", type=float, default=2.0, help="Radius [mm] to round selected vertices. Default: 2")
pars.add_argument("--method", type=str, default="arc", help="operation: one of 'arc' (default), 'line'")
def effect(self):
if debug:
# SvgInputMixin __init__: "id:subpath:position of selected nodes, if any"
print(self.options.selected_nodes, file=self.tty)
self.radius = math.fabs(self.options.radius)
self.cut = False
if self.options.method in ('line'):
self.cut = True
if len(self.options.selected_nodes) < 1:
# find selected objects and construct a list of selected_nodes for them...
for p in self.options.ids:
self.options.selected_nodes.extend(self.find_roundable_nodes(p))
if len(self.options.selected_nodes) < 1:
raise inkex.AbortExtension("Could not find nodes inside a path. No path objects selected?")
if len(self.options.selected_nodes) == 1:
# when we only trim one node, we can eat up almost everything,
# no need to leave room for rounding neighbour nodes.
self.max_trim_factor = max_trim_factor_single
for node in sorted(self.options.selected_nodes):
## we walk through the list sorted, so that node indices are processed within a subpath in ascending numeric order.
## that makes adjusting index offsets after node inserts easier.
ss = self.round_corner(node)
def find_roundable_nodes(self, path_id):
""" select all nodes of all (sub)paths. except for
- the last (one or two) nodes of a closed path (which coindide with the first node)
- the first and last node of an open path (which cannot be smoothed)
"""
ret = []
elem = self.svg.getElementById(path_id)
if elem.tag != '{'+elem.nsmap['svg']+'}path':
return ret # ellipse never works.
try:
csp = elem.path.to_superpath()
except:
return ret
for sp_idx in range(0, len(csp)):
sp = csp[sp_idx]
if len(sp) < 3:
continue # subpaths of 2 or less nodes are ignored
if self.very_close(sp[0], sp[-1]):
idx_s = 0 # closed paths count from 0 to either n-1 or n-2
idx_e = len(sp) - 1
if self.very_close_xy(sp[-2][1], sp[-1][1]):
idx_e = len(sp) - 2
else:
idx_s = 1 # open paths count from 1 to either n-1
idx_e = len(sp) - 1
for idx in range(idx_s, idx_e):
ret.append("%s:%d:%d" % (path_id, sp_idx, idx))
if debug:
print("find_roundable_nodes: ", self.options.selected_nodes, file=sys.stderr)
return ret
def very_close(self, n1, n2):
"deep compare. all elements in sub arrays are compared for (very close) numerical equality"
return self.very_close_xy(n1[0], n2[0]) and self.very_close_xy(n1[1], n2[1]) and self.very_close_xy(n1[2], n2[2])
def very_close_xy(self, p1, p2):
"one 2 element array is compared for (very close) numerical equality"
eps = 1e-9
return abs(p1[0]-p2[0]) < eps and abs(p1[1]-p2[1]) < eps
def round_corner(self, node_id):
""" round the corner at (adjusted) node_idx of subpath
Side_effect: store (or increment) in self.inserted["pathname:subpath"] how many points were inserted in that subpath.
the adjusted node_idx is computed by adding that number (if exists) to the value of the node_id before doing any manipulation
"""
s = node_id.split(":")
path_id = s[0]
subpath_idx = int(s[1])
subpath_id = s[0] + ':' + s[1]
idx_adjust = self.nodes_inserted.get(subpath_id, 0)
node_idx = int(s[2]) + idx_adjust
elem = self.svg.getElementById(path_id)
if elem is None:
print("selected_node %s not found in svg document" % node_id, file=sys.stderr)
return None
elem.apply_transform() # modifies path inplace? -- We save later back to the same element. Maybe we should not?
path = elem.path
s = path.to_superpath()
sp = s[subpath_idx]
## call the actual path manipulator, record how many nodes were inserted.
orig_len = len(sp)
sp = self.subpath_round_corner(sp, node_idx)
idx_adjust += len(sp) - orig_len
# convert the superpath back to a normal path
s[subpath_idx] = sp
elem.set_path(s.to_path(curves_only=False))
self.nodes_inserted[subpath_id] = idx_adjust
# If we picked up the 'd' attribute of a non-path (e.g. star), we must make sure the object now becomes a path.
# Otherwise inkscape uses the sodipodi data and ignores our changed 'd' attribute.
if '{'+elem.nsmap['sodipodi']+'}type' in elem.attrib:
del(elem.attrib['{'+elem.nsmap['sodipodi']+'}type'])
# Debugging is no longer available or not yet implemented? This explodes, although it is
# documented in https://inkscape.gitlab.io/extensions/documentation/inkex.command.html
# inkex.command.write_svg(self.svg, "/tmp/seen.svg")
# - AttributeError: module 'inkex' has no attribute 'command'
# But hey, we can always resort to good old ET.dump(self.document) ...
def super_node(self, sp, node_idx):
""" In case of node_idx 0, we need to use either the last, the second-last or the third last node as a previous node.
For a closed subpath, the last node and the first node are identical. Then, the second last node may be still at the
same location if it has a handle. If so, we take the third last instead. Gah. It has a certain logic...
In case of the node_idx being the last node, we already know that the subpath is not closed,
we use 0 as the next node.
The direction sn.prev.dir does not really point to the coordinate of the previous node, but to the end of the
next-handle of the prvious node. This is the same when there are straight lines. The absence of handles is
denoted by having the same coordinates for handle and node.
Same for next.dir, it points to the next.prev handle.
The exact implementation here is:
- sn.next.handle is set to a relative vector that is the tangent of the curve towards the next point.
we implement four cases:
- if neither node nor next have handles, the connection is a straight line, and next.handle points
in the direction of the next node itself.
- if the curve between node and next is defined by two handles, then sn.next.handle is in the direction of the
nodes own handle,
- if the curve between node and next is defined one handle at the node itself, then sn.next.handle is in the
direction of the nodes own handle,
- if the curve between node and next is defined one handle at the next node, then sn.next.handle is in the
direction from the node to the end of that other handle.
- when trimming back later, we move along that tangent, instead of following the curve.
That is an approximation when the segment is curved, and exact when it is straight.
(Finding exact candidate points on curved lines that have tangents with the desired circle
is beyond me today. Multiple candidates may exist. Any volunteers?)
"""
prev_idx = node_idx - 1
sp_node_idx_ = copy.deepcopy(sp[node_idx]) # if this wraps around, at node_idx=0, we may need to tweak the prev handle
if node_idx == 0:
prev_idx = len(sp) - 1
if self.very_close(sp_node_idx_, sp[prev_idx]):
prev_idx = prev_idx - 1 # skip one node, it is the 'close marker'
if self.very_close_xy(sp_node_idx_[1], sp[prev_idx][1]):
# still no distance, skip more. Needed for https://github.com/jnweiger/inkscape-round-corners/issues/2
sp_node_idx_[0] = sp[prev_idx][0] # this sp_node_idx_ must acts as if its prev handle is that one.
prev_idx = prev_idx - 1
else:
self.skipped_degenerated += 1 # path ends here.
return None, None
# if debug: pprint.pprint({'node_idx': node_idx, 'len(sp)':len(sp), 'sp': sp}, stream=self.tty)
if node_idx == len(sp)-1:
self.skipped_degenerated += 1 # path ends here. On a closed loop, we can never select the last point.
return None, None
next_idx = node_idx + 1
if next_idx >= len(sp): next_idx = 0
t = sp_node_idx_
p = sp[prev_idx]
n = sp[next_idx]
dir1 = [ p[2][0] - t[1][0], p[2][1] - t[1][1] ] # direction to the previous node (rel coords)
dir2 = [ n[0][0] - t[1][0], n[0][1] - t[1][1] ] # direction to the next node (rel coords)
dist1 = math.sqrt(dir1[0]*dir1[0] + dir1[1]*dir1[1]) # distance to the previous node
dist2 = math.sqrt(dir2[0]*dir2[0] + dir2[1]*dir2[1]) # distance to the next node
handle1 = [ t[0][0] - t[1][0], t[0][1] - t[1][1] ] # handle towards previous node (rel coords)
handle2 = [ t[2][0] - t[1][0], t[2][1] - t[1][1] ] # handle towards next node (rel coords)
if self.very_close_xy(handle1, [ 0, 0 ]): handle1 = dir1
if self.very_close_xy(handle2, [ 0, 0 ]): handle2 = dir2
prev = { 'idx': prev_idx, 'dir':dir1, 'handle':handle1 }
next = { 'idx': next_idx, 'dir':dir2, 'handle':handle2 }
sn = { 'idx': node_idx, 'prev': prev, 'next': next, 'x': t[1][0], 'y': t[1][1] }
if dist1 < self.radius:
if debug:
print("subpath node_idx=%d, dist to prev(%d) is smaller than radius: %g < %g" %
(node_idx, prev_idx, dist1, self.radius), file=sys.stderr)
pprint.pprint(sn, stream=sys.stderr)
if self.skipped_small_len > dist1: self.skipped_small_len = dist1
self.skipped_small_count += 1
return None, None
if dist2 < self.radius:
if debug:
print("subpath node_idx=%d, dist to next(%d) is smaller than radius: %g < %g" %
(node_idx, next_idx, dist2, self.radius), file=sys.stderr)
pprint.pprint(sn, stream=sys.stderr)
if self.skipped_small_len > dist2: self.skipped_small_len = dist2
self.skipped_small_count += 1
return None, None
len_h1 = math.sqrt(handle1[0]*handle1[0] + handle1[1]*handle1[1])
len_h2 = math.sqrt(handle2[0]*handle2[0] + handle2[1]*handle2[1])
prev['hlen'] = len_h1
next['hlen'] = len_h2
if len_h1 < self.radius:
if debug:
print("subpath node_idx=%d, handle to prev(%d) is shorter than radius: %g < %g" %
(node_idx, prev_idx, len_h1, self.radius), file=sys.stderr)
pprint.pprint(sn, stream=sys.stderr)
if self.skipped_small_len > len_h1: self.skipped_small_len = len_h1
self.skipped_small_count += 1
return None, None
if len_h2 < self.radius:
if debug:
print("subpath node_idx=%d, handle to next(%d) is shorter than radius: %g < %g" %
(node_idx, next_idx, len_h2, self.radius), file=sys.stderr)
pprint.pprint(sn, stream=sys.stderr)
if self.skipped_small_len > len_h2: self.skipped_small_len = len_h2
self.skipped_small_count += 1
return None, None
if len_h1 > dist1: # shorten that handle to dist1, avoid overshooting the point
handle1[0] = handle1[0] * dist1 / len_h1
handle1[1] = handle1[1] * dist1 / len_h1
prev['hlen'] = dist1
if len_h2 > dist2: # shorten that handle to dist2, avoid overshooting the point
handle2[0] = handle2[0] * dist2 / len_h2
handle2[1] = handle2[1] * dist2 / len_h2
next['hlen'] = dist2
return sn, sp_node_idx_
def arc_c_m_from_super_node(self, s):
"""
Given the supernode s and the radius self.radius, we compute and return two points:
c, the center of the arc and m, the midpoint of the arc.
Method used:
- construct the ray c_m_vec that runs though the original point p=[x,y] through c and m.
- next.trim_pt, [x,y] and c form a rectangular triangle. Thus we can
compute cdist as the length of the hypothenuses under trim and radius.
- c is then cdist away from [x,y] along the vector c_m_vec.
- m is closer to [x,y] than c by exactly radius.
"""
a = [ s['prev']['trim_pt'][0] - s['x'], s['prev']['trim_pt'][1] - s['y'] ]
b = [ s['next']['trim_pt'][0] - s['x'], s['next']['trim_pt'][1] - s['y'] ]
c_m_vec = [ a[0] + b[0],
a[1] + b[1] ]
l = math.sqrt( c_m_vec[0]*c_m_vec[0] + c_m_vec[1]*c_m_vec[1] )
cdist = math.sqrt( self.radius*self.radius + s['trim']*s['trim'] ) # distance [x,y] to circle center c.
c = [ s['x'] + cdist * c_m_vec[0] / l, # circle center
s['y'] + cdist * c_m_vec[1] / l ]
m = [ s['x'] + (cdist-self.radius) * c_m_vec[0] / l, # spline midpoint
s['y'] + (cdist-self.radius) * c_m_vec[1] / l ]
return (c, m)
def arc_bezier_handles(self, p1, p4, c):
"""
Compute the control points p2 and p3 between points p1 and p4, so that the cubic bezier spline
defined by p1,p2,p3,p2 approximates an arc around center c
Algorithm based on Aleksas Riškus and Hans Muller. Sorry Pomax, saw your works too, but did not use any.
"""
x1,y1 = p1
x4,y4 = p4
xc,yc = c
ax = x1 - xc
ay = y1 - yc
bx = x4 - xc
by = y4 - yc
q1 = ax * ax + ay * ay
q2 = q1 + ax * bx + ay * by
k2 = 4./3. * (math.sqrt(2 * q1 * q2) - q2) / (ax * by - ay * bx)
x2 = xc + ax - k2 * ay
y2 = yc + ay + k2 * ax
x3 = xc + bx + k2 * by
y3 = yc + by - k2 * bx
return ([x2, y2], [x3, y3])
def subpath_round_corner(self, sp, node_idx):
sn, sp_node_idx_ = self.super_node(sp, node_idx)
if sn is None: return sp # do nothing. stderr messages are already printed.
# The angle to be rounded is now between the vectors a and b
#
a = sn['prev']['handle']
b = sn['next']['handle']
a_len = sn['prev']['hlen']
b_len = sn['next']['hlen']
try:
# From https://de.wikipedia.org/wiki/Schnittwinkel_(Geometrie)
# Wikipedia has an abs() in the formula, which extracts the smaller of the two angles.
# We don't want that. We need to distinguish betwenn spitzwingklig and stumpfwinklig.
#
alpha = math.acos( (a[0]*b[0]+a[1]*b[1]) / ( math.sqrt(a[0]*a[0]+a[1]*a[1]) * math.sqrt(b[0]*b[0]+b[1]*b[1]) ) )
except:
# Division by 0 error means path folds back on itself here. No space to apply a radius between the segments.
self.skipped_degenerated += 1
return sp
sn['alpha'] = math.degrees(alpha)
# find the amount to trim back both sides so that a circle of radius self.radius would perfectly fit.
if alpha < self.eps:
# path folds back on itself here. No space to apply a radius between the segments.
self.skipped_degenerated += 1
return sp
if abs(alpha - math.pi) < self.eps:
# stretched. radius won't be visible, that is just fine. No need to warn about that.
return sp
trim = self.radius / math.tan(0.5 * alpha)
sn['trim'] = trim
if trim < 0.0:
print("Error: at node_idx=%d: angle=%g°, trim is negative: %g" % (node_idx, math.degrees(alpha), trim), file=sys.stderr)
return sp
# a_len points to the previous node. There we can always allow max_trim_factor_single, as the trim was either already done,
# or will not be done. Only at b_len we need to reserve space for the next trim.
# FIXME: also allow max_trim_factor_single at b_len, when we find that the very next node will not be rounded.
#
available_len = min(max_trim_factor_single*a_len, self.max_trim_factor*b_len)
if trim > available_len:
if debug:
if trim > max_trim_factor_single*a_len:
print("Skipping where hlen_a %g * max_trim %g < needed_trim %g" % (a_len, max_trim_factor_single, trim), file=self.tty)
if trim > self.max_trim_factor*b_len:
print("Skipping where hlen_b %g * max_trim %g < needed_trim %g" % (b_len, self.max_trim_factor, trim), file=self.tty)
pprint.pprint(sn, stream=self.tty)
if self.skipped_small_len > available_len:
self.skipped_small_len = available_len
self.skipped_small_count += 1
return sp
trim_pt_p = [ sn['x'] + a[0] * trim / a_len, sn['y'] + a[1] * trim / a_len ]
trim_pt_n = [ sn['x'] + b[0] * trim / b_len, sn['y'] + b[1] * trim / b_len ]
sn['prev']['trim_pt'] = trim_pt_p
sn['next']['trim_pt'] = trim_pt_n
if debug:
pprint.pprint(sn, stream=self.tty)
pprint.pprint(self.cut, stream=self.tty)
# We replace the node_idx node by two nodes node_a, node_b.
# We need an extra middle node node_m if alpha < 90° -- alpha is the angle between the tangents,
# as the arc spans the remainder to complete 180° an arc with more than 90° needs the midpoint.
# We preserve the endpoints of the two outside handles if they are non-0-length.
# We know that such handles are long enough (because of the above max_trim_factor checks)
# to not flip around when applying the trim.
# But we move the endpoints of 0-length outside handles with the point when trimming,
# so that they don't end up on the inside.
prev_handle = sp_node_idx_[0][:]
next_handle = sp_node_idx_[2][:]
if self.very_close_xy(prev_handle, sp_node_idx_[1]): prev_handle = trim_pt_p[:]
if self.very_close_xy(next_handle, sp_node_idx_[1]): next_handle = trim_pt_n[:]
p1 = trim_pt_p[:]
p7 = trim_pt_n[:]
arc_c, p4 = self.arc_c_m_from_super_node(sn)
node_a = [ prev_handle, p1[:], p1[:] ] # deep copy, as we may want to modify the second handle later
node_b = [ p7[:], p7[:], next_handle ] # deep copy, as we may want to modify the first handle later
if alpha >= 0.5*math.pi or self.cut:
if self.cut == False:
# p3,p4,p5 do not exist, we need no midpoint
p2, p6 = self.arc_bezier_handles(p1, p7, arc_c)
node_a[2] = p2
node_b[0] = p6
if node_idx == 0:
# use prev idx to know about the extra skip. +1 for the node here, +1 for inclusive.
# CAUTION: Keep in sync below
sp = [node_a] + [node_b] + sp[1:sn['prev']['idx']+2]
else:
sp = sp[:node_idx] + [node_a] + [node_b] + sp[node_idx+1:]
else:
p2, p3 = self.arc_bezier_handles(p1, p4, arc_c)
p5, p6 = self.arc_bezier_handles(p4, p7, arc_c)
node_m = [ p3, p4, p5 ]
node_a[2] = p2
node_b[0] = p6
if node_idx == 0:
# use prev idx to know about the extra skip. +1 for the node here, +1 for inclusive.
# CAUTION: Keep in sync above
sp = [node_a] + [node_m] + [node_b] + sp[1:sn['prev']['idx']+2]
else:
sp = sp[:node_idx] + [node_a] + [node_m] + [node_b] + sp[node_idx+1:]
# A closed path is formed by making the last node indentical to the first node.
# So, if we trim at the first node, then duplicte that trim on the last node, to keep the loop closed.
if node_idx == 0:
sp[-1][0] = sp[0][0][:]
sp[-1][1] = sp[0][1][:]
sp[-1][2] = sp[0][2][:]
return sp
def clean_up(self): # __fini__
if self.tty is not None:
self.tty.close()
super(RoundCorners, self).clean_up()
if self.skipped_degenerated:
print("Warning: Skipped %d degenerated nodes (180° turn or end of path?).\n" % self.skipped_degenerated, file=sys.stderr)
if self.skipped_small_count:
print("Warning: Skipped %d nodes with not enough space (Value %g is too small. Try again with a smaller radius or only one node selected).\n" % (self.skipped_small_count, self.skipped_small_len), file=sys.stderr)
if __name__ == '__main__':
RoundCorners().run()

View File

@ -0,0 +1,21 @@
[
{
"name": "Source Code Text",
"id": "fablabchemnitz.de.source_code_text",
"path": "source_code_text",
"dependent_extensions": null,
"original_name": "Source Code Text",
"original_id": "org.henry.poster_text",
"license": "GNU GPL v2",
"license_url": "https://gitlab.com/inkscape/extensions/-/blob/master/LICENSE.txt",
"comment": "based on Lorem Ipsum extension",
"source_url": "",
"fork_url": "https://gist.github.com/om-henners/8c642c87b71daa3ea68222d40167edbc",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Source+Code+Text",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/om-henners",
"github.com/vmario89"
]
}
]

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Source Code Text</name>
<id>fablabchemnitz.de.source_code_text</id>
<param name="directory" type="path" mode="folder" gui-text="Directory to search for code">C:\Users\</param>
<param name="pattern" type="string" gui-text="Filename pattern to match">py</param>
<param name="wordsperpara" type="int" min="0" max="10000" gui-text="Maximum words per paragraph">0</param>
<param name="numparas" type="int" min="0" max="1000" gui-text="Limit number of paragraphs">1</param>
<label appearance="header">Help</label>
<label>Based on the "Lorem Ipsum" plugin, this plugin searches the base directory for code, and strings it all together by concatenating on whitespace. If a flowed text is selected, Source code is added to it; otherwise a new flowed text object, the size of the page, is created in a new layer.</label>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Text"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">source_code_text.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,73 @@
#!/usr/bin/env python3
import os
import re
import random
import inkex
from lxml import etree
class SourceCodeText(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("--directory", default='~/', help="Default directory")
pars.add_argument("--pattern", default='py', help="File extension pattern")
pars.add_argument("--wordsperpara", type=int, default=0, help="Maximum words per paragraph")
pars.add_argument("--numparas", type=int, default=1, help="Number of paragraphs")
def text_generation(self):
#Get all the matching files. Then yield words one at a time. This can take a while if there are a lot of files, but shouldn't be too bad.
matcher = re.compile('.+\.{}$'.format(self.options.pattern))
matched_files = []
for root, _, names in os.walk(os.path.expanduser(self.options.directory)):
for name in names:
if matcher.match(name):
matched_files.append(os.path.join(root, name))
random.shuffle(matched_files)
for path in matched_files:
with open(path, encoding = 'utf-8') as file:
for word in file.read().split():
yield word
def add_text(self, node):
#Add the text to the node
word_generator = self.text_generation()
for _ in range(self.options.numparas):
words = []
para = etree.SubElement(node, inkex.addNS('flowPara','svg'))
if self.options.wordsperpara:
try:
for _, word in zip(range(self.options.wordsperpara), word_generator):
words.append(next(word_generator))
except: #Exception as e:
#inkex.errormsg(e)
pass
else:
words = word_generator
if words:
para.text = ' '.join(words)
etree.SubElement(node, inkex.addNS('flowPara','svg'))
else:
break
def effect(self):
found=0
for id, node in self.svg.selected.items():
if node.tag == inkex.addNS('flowRoot','svg'):
found+=1
if found==1:
self.addText(node)
if not found:
#inkex.debug('No "flowRoot" elements selected. Unable to add text.')
svg=self.document.getroot()
gattribs = {inkex.addNS('label','inkscape'):'lorem ipsum',inkex.addNS('groupmode','inkscape'):'layer'}
g=etree.SubElement(svg,inkex.addNS('g','svg'),gattribs)
flowRoot=etree.SubElement(g,inkex.addNS('flowRoot','svg'),{inkex.addNS('space','xml'):'preserve'})
flowRegion=etree.SubElement(flowRoot,inkex.addNS('flowRegion','svg'))
rattribs = {'x':'0','y':'0','width':svg.get('width'),'height':svg.get('height')}
rect=etree.SubElement(flowRegion,inkex.addNS('rect','svg'),rattribs)
self.add_text(flowRoot)
if __name__ == '__main__':
SourceCodeText().run()

View File

@ -0,0 +1,20 @@
[
{
"name": "Split And Break Bezier At t",
"id": "fablabchemnitz.de.split_and_break_bezier_at_t",
"path": "split_and_break_bezier_at_t",
"dependent_extensions": null,
"original_name": "fablabchemnitz.de.split_and_break_bezier_at_t",
"original_id": "split_and_break_bezier_at_t",
"license": "GNU GPL v3",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/LICENSE",
"comment": "written by Mario Voigt",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/split_and_break_bezier_at_t",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Split+And+Break+Bezier+At+t",
"inkscape_gallery_url": "https://inkscape.org/de/~MarioVoigt/%E2%98%85split-and-break-bezier-at-t",
"main_authors": [
"github.com/vmario89"
]
}
]

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Split And Break Bezier At t</name>
<id>fablabchemnitz.de.split_and_break_bezier_at_t</id>
<param name="tab" type="notebook">
<page name="tab_settings" gui-text="Settings">
<param name="split_select" type="optiongroup" appearance="radio" gui-text="Split by" gui-description="Choose to split by length or percentage">
<option value="length">length</option>
<option value="t">percentage (t)</option>
</param>
<param name="unit" type="optiongroup" appearance="combo" gui-text="Units">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
<option value="pt">pt</option>
<option value="px">px</option>
<option value="pc">pc</option>
</param>
<param name="target_length" type="float" min="0.0" precision="4" appearance="full" gui-text="Length">0.5000</param>
<param name="target_t" type="float" min="0.0001" max="0.9999" precision="4" appearance="full" gui-text="t">0.5000</param>
<param name="keep_start" type="bool" gui-text="Keep start">true</param>
<param name="keep_end" type="bool" gui-text="Keep end">true</param>
<param name="keep_seg" type="bool" gui-text="Keep only segment where trim applies" gui-description="A path can consist of more than one segment!">false</param>
</page>
<page name="tab_about" gui-text="About">
<label appearance="header">Split And Break Bezier At t</label>
<label>Splits a path at value t=0..1 (t=0.5 means 50%) or at a defined length with unit.
Applies independently for each sub path in selection. Use 'Path > Reverse' to change the cutting direction.</label>
<label>2021 - 2022 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
<spacer/>
<label appearance="header">Online Documentation</label>
<label appearance="url">https://y.stadtfabrikanten.org/splitandbreakbezieratt</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X</label>
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</label>
<spacer/>
<label appearance="header">MightyScape Extension Collection</label>
<label>This piece of software is part of the MightyScape for Inkscape Extension Collection and is licensed under GNU GPL v3</label>
<label appearance="url">https://y.stadtfabrikanten.org/mightyscape-overview</label>
</page>
<page name="tab_donate" gui-text="Donate">
<label appearance="header">Coffee + Pizza</label>
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
<spacer/>
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
<spacer/>
<label>Thanks for using our extension and helping us!</label>
<image>../000_about_fablabchemnitz.svg</image>
</page>
</param>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Paths - Cut/Intersect/Purge"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">split_and_break_bezier_at_t.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,157 @@
#!/usr/bin/env python3
"""
Extension for InkScape 1.0
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 01.06.2021
Last patch: 02.06.2021
License: GNU GPL v3
Splits a path at value t=0..1 (t=0.5 means 50%) or at a defined length with unit.
Applies independently for each sub path in selection. Use 'Path > Reverse' to change the cutting direction.
"""
import copy
import inkex
from inkex import bezier, CubicSuperPath, PathElement, Path
from inkex.bezier import csplength
class SplitAndBreakBezierAtT(inkex.EffectExtension):
def breakContours(self, element, breakelements = None):
''' this does the same as "CTRL + SHIFT + K" '''
if breakelements == None:
breakelements = []
if element.tag == inkex.addNS('path','svg'):
parent = element.getparent()
idx = parent.index(element)
idSuffix = 0
raw = element.path.to_arrays()
subPaths, prev = [], 0
for i in range(len(raw)): #breaks compound paths into sub paths
if raw[i][0] == 'M' and i != 0:
subPaths.append(raw[prev:i])
prev = i
subPaths.append(raw[prev:])
for subpath in subPaths:
replacedelement = copy.copy(element)
oldId = replacedelement.get('id')
csp = CubicSuperPath(subpath)
if len(subpath) > 1 and csp[0][0] != csp[0][1]: #avoids pointy paths like M "31.4794 57.6024 Z"
replacedelement.set('d', csp)
replacedelement.set('id', oldId + str(idSuffix))
parent.insert(idx, replacedelement)
idSuffix += 1
breakelements.append(replacedelement)
parent.remove(element)
for child in element.getchildren():
self.breakContours(child, breakelements)
return breakelements
def add_arguments(self, pars):
pars.add_argument('--tab')
pars.add_argument('--split_select', default="t")
pars.add_argument('--unit', default="mm")
pars.add_argument('--target_length', type=float, default=0.5)
pars.add_argument('--target_t', type=float, default=0.5)
pars.add_argument('--keep_start', type=inkex.Boolean, default=True)
pars.add_argument('--keep_end', type=inkex.Boolean, default=True)
pars.add_argument('--keep_seg', type=inkex.Boolean, default=False)
def effect(self):
#if self.options.split_select == "t" and self.options.target_t == 0.0:
# inkex.utils.debug("You have seleted 'percentage (t)' but your t parameter is 0.0. It would simply result in element deletion!")
# return
#if self.options.split_select == "t" and self.options.target_t == 1.0:
# inkex.utils.debug("You have seleted 'percentage (t)' but your t parameter is 1.0. It would'nt exist any trim result!")
# return
breakApartElements = None
for element in self.svg.selection.filter(PathElement):
breakApartElements = self.breakContours(element, breakApartElements)
if breakApartElements is not None:
for element in breakApartElements:
csp = element.path.to_superpath()
slengths, totalLength = csplength(csp)
if totalLength == 0:
inkex.utils.debug("{} is invalid: zero length (path d='{}'). Skipping ...".format(element.get('id'), element.path))
continue
if self.options.split_select == "t":
length_at_target_t = self.options.target_t * totalLength
elif self.options.split_select == "length":
length_at_target_t = self.svg.unittouu(str(self.options.target_length) + self.options.unit)
if length_at_target_t > totalLength:
inkex.utils.debug("Entered length is larger than length of {}. Skipping ...".format(element.get('id')))
continue
self.options.target_t = length_at_target_t / totalLength #override
new = []
keep = [] #some copy for the segment where the split applies
lengthSum = 0
segOfTOccurence = None
for seg in csp:
new.append([seg[0][:]])
for i in range(1,len(seg)):
aSeg = seg[i][0]
segLength = bezier.cspseglength(new[-1][-1], seg[i])
lengthSum += segLength
current_t = lengthSum / totalLength
#insert a new breaking node in case we are at the desired t parameter
if current_t >= self.options.target_t:
if segOfTOccurence is None:
segOfTOccurence = i
t_dist = 1 - ((lengthSum - length_at_target_t) / segLength)
result = bezier.cspbezsplitatlength(new[-1][-1], seg[i], t_dist)
better_result = [[list(el) for el in elements] for elements in result]
new[-1][-1], nxt, seg[i] = better_result
new[-1].append(nxt[:])
if self.options.keep_start is True and self.options.keep_end is False:
if segOfTOccurence == 1:
keep.append([seg[i-1][0], seg[i-1][0], seg[i-1][0]])
else:
keep.append([seg[i-1][1], seg[i-1][1], seg[i-1][1]])
keep.append([better_result[0][2], nxt[0], nxt[1]])
elif self.options.keep_start is False and self.options.keep_end is True:
keep.append([better_result[0][2], nxt[0], nxt[1]])
keep.append([better_result[1][2], better_result[2][0], seg[i][1]])
elif self.options.keep_start is True and self.options.keep_end is True:
if segOfTOccurence == 1:
keep.append([seg[i-1][0], seg[i-1][0], seg[i-1][0]])
else:
keep.append([seg[i-1][1], seg[i-1][1], seg[i-1][1]])
keep.append([seg[i-1][2], aSeg, seg[i][1]])
new[-1].append(seg[i])
if self.options.keep_seg is False:
newpath = CubicSuperPath(new).to_path(curves_only=True).to_arrays()
#insert the splitting at the occurence (we add "m 0,0") to break the path
newpath.insert(segOfTOccurence + 1, ['m', [0, 0]])
element.path = Path(newpath)
breakAparts = self.breakContours(element)
if len(breakAparts) > 0:
pathStart = breakAparts[0]
if len(breakAparts) > 1:
pathEnd = breakAparts[1]
if self.options.keep_start is False and len(breakAparts) > 0:
pathStart.delete()
if self.options.keep_end is False and len(breakAparts) > 1:
pathEnd.delete()
else:
element.path = CubicSuperPath(keep)
#print the breaking point coordinate
#for step, (x, y) in enumerate(breakAparts[1].path.end_points):
# self.msg("x={},y={}".format(x, y))
# break
else:
inkex.utils.debug("Selection seems to be empty!")
return
if __name__ == '__main__':
SplitAndBreakBezierAtT().run()

View File

@ -0,0 +1,21 @@
[
{
"name": "Streaks",
"id": "fablabchemnitz.de.streaks",
"path": "streaks",
"dependent_extensions": null,
"original_name": "streaks",
"original_id": "ca.sfu.AT.kurn.Streaks",
"license": "GNU GPL v3",
"license_url": "https://sourceforge.net/projects/inkscape-streaks/",
"comment": "ported to Inkscape v1 by Mario Voigt",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/streaks",
"fork_url": "https://sourceforge.net/projects/inkscape-streaks",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Streaks",
"inkscape_gallery_url": null,
"main_authors": [
"sourceforge.net/andrew-kurn",
"github.com/vmario89"
]
}
]

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Streaks</name>
<id>fablabchemnitz.de.streaks</id>
<param type="notebook" name="Nmain">
<page name="top" gui-text="Color">
<label appearance="header">Fills a box with a texture made of vertical line segments.</label>
<param name="strokeColor" type="color" appearance="colorbutton" gui-text="Line color">255</param>
<separator/>
<param name="strokeWidth" type="int" gui-text="Line width (px)">2</param>
</page>
<page name="main" gui-text="Main">
<param max="256" name="blur" type="int" gui-text="Blur">2</param>
<param max="1000" name="linno" type="int" gui-text="# of columns">50</param>
<param name="xrand" type="bool" gui-text="Lines randomized">true</param>
<separator/>
<param name="pagep" type="bool" gui-text="Default box to page size?">true</param>
<param max="10000" name="cusx" type="int" gui-text="Custom size x">500</param>
<param max="10000" name="cusy" type="int" gui-text="Custom size y">500</param>
</page>
<page name="vert" gui-text="Each column">
<param min="1" max="256" name="segLen" type="int" gui-text="# of segments">8</param>
<param name="yrand" type="bool" gui-text="Lengths randomized">true</param>
<param name="dashp" type="bool" gui-text="Use dashes?">true</param>
<param name="blankp" type="bool" gui-text="Use blanks?">true</param>
<param name="dotp" type="bool" gui-text="Use dots?">true</param>
<param max="1000" name="dots" type="int" gui-text="Dots per height">100</param>
<label>This sets the size of a dot relative to the total height. Higher is shorter.</label>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Shape Generators">
<submenu name="Streaks And Blobs" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">streaks.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,157 @@
#! /usr/bin/env python3
import random
rr = random.randint(1,10)
import inkex
from lxml import etree
class Streaks(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument('--blur', type = int, default = 2)
pars.add_argument('--linno', type = int, default = 50)
pars.add_argument('--xrand', type = inkex.Boolean, default = True)
pars.add_argument('--pagep', type = inkex.Boolean, default = True)
pars.add_argument('--cusx', type = int, default = 500)
pars.add_argument('--cusy', type = int, default = 500)
pars.add_argument('--segLen', type = int, default = 8)
pars.add_argument('--yrand', type = inkex.Boolean, default = True)
pars.add_argument('--dashp', type = inkex.Boolean, default = True)
pars.add_argument('--blankp', type = inkex.Boolean, default = True)
pars.add_argument('--dotp', type = inkex.Boolean, default = True)
pars.add_argument('--dots', type = int, default = 100)
pars.add_argument('--strokeColor', default = 255)
pars.add_argument('--strokeWidth', type = int, default = 2)
pars.add_argument("--Nmain", default='title')
def effect(self):
blur = int(self.options.blur)
linno = int(self.options.linno)
xrand = bool(self.options.xrand)
pagep = bool(self.options.pagep)
cusx = int(self.options.cusx)
cusy = int(self.options.cusy)
segLen = int(self.options.segLen)
yrand = bool(self.options.yrand)
dashp = bool(self.options.dashp)
blankp = bool(self.options.blankp)
dotp = bool(self.options.dotp)
dots = int(self.options.dots)
strokeColor = int(self.options.strokeColor)
strokeWidth = int(self.options.strokeWidth)
# Get access to main SVG document element and get its dimensions.
svg = self.document.getroot()
if pagep :
try :
width = self.svg.unittouu(svg.get('width'))
height = self.svg.unittouu(svg.attrib['height'])
except AttributeError :
width = self.unittouu(svg.get('width'))
height = self.unittouu(svg.attrib['height'])
# inkex.errormsg("Page size %d %d" % (width, height))
else :
width = cusx
height = cusy
# Find defs node.
for child in svg :
if -1 != child.tag.find("defs") :
break
else:
inkex.errormsg("No defs child found")
defs = child
if blur :
filter = etree.SubElement(defs, "filter")
filter.set(inkex.addNS('collect', 'inkscape'), 'always')
filname = self.svg.get_unique_id('filter')
filter.set('id' , filname)
finfo = etree.SubElement(filter, 'feGaussianBlur')
finfo.set(inkex.addNS('collect', 'inkscape'), 'always')
finfo.set('stdDeviation', str(blur))
""" Debug
for i in range(len(svg)) :
k = svg[i].attrib
for ky in k :
inkex.errormsg(ky)
# Clean any old layers
flag = False
for i in range(len(svg)) :
dic = svg[i].attrib
for key in dic:
if -1 != key.find("label") :
if 'Streak Layer' == dic[key] :
del svg[i]
flag = True
if flag :
inkex.errormsg("Found old Streak layer")
else:
inkex.errormsg("Clean")
"""
# Create a new layer.
layer = etree.SubElement(svg, 'g')
layer.set(inkex.addNS('label', 'inkscape'), 'Streak Layer')
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
# Create path element
path = etree.Element(inkex.addNS('path','svg'))
alpha = strokeColor & 255
color = (strokeColor >> 8) & int('ffffff', 16)
style = {
'stroke' : '#%06X' % color,
'stroke-width' : "{}px".format(strokeWidth),
}
#inkex.errormsg("Colour %s" % strokeColor)
if blur : style['filter'] = 'url(#' + filname +')'
path.set('style', str(inkex.Style(style)))
pathstring = ''
seglim = int(height / segLen)
ditlen = int(height / dots)
xco = 0
while xco < width :
y = 0
flag = random.randint(0, 2)
while y < height :
if yrand :
yinc = random.randint(1, seglim)
else :
yinc = seglim
if flag == 1 and dashp: #Draw dash
pathstring += ' M '+str(xco)+','+str(y)+' L '+str(xco)+','+str(min(y + yinc, height))
y += yinc + ditlen
elif flag == 2 and dotp: #Draw dots
ylim = min(y + yinc, height)
while y < ylim :
pathstring += ' M '+str(xco)+','+str(y)+' L '+str(xco)+','+str(min(y + ditlen, height))
y += 2*ditlen
elif flag == 0 and blankp :
y += yinc #Adding blank space
elif not (dashp or dotp or blankp) : #Squiggle if user turns them off
sdit = str(2*ditlen)+' '
pathstring += ' M '+str(xco)+','+str(y)+' q '+ 2*sdit + '0 ' +sdit
for i in range(int(height/(2*ditlen))) :
pathstring += 't 0 '+sdit
y = height
flag = (flag + 1)%3
if xrand :
xco += random.randint(0, int(2 * width / linno))
else :
xco += width / linno
path.set('d', pathstring)
# Connect elements together.
layer.append(path)
if __name__ == '__main__':
Streaks().run()