Merge branch 'master' of https://780f082f9e7a3158c671efd803efd1ea70270517@gitea.fablabchemnitz.de/MarioVoigt/mightyscape-1.X.git
This commit is contained in:
commit
be4b28db3b
@ -4,8 +4,10 @@
|
|||||||
# Copyright Mark "Klowner" Riedesel
|
# Copyright Mark "Klowner" Riedesel
|
||||||
# https://github.com/Klowner/inkscape-applytransforms
|
# https://github.com/Klowner/inkscape-applytransforms
|
||||||
#
|
#
|
||||||
import inkex
|
import copy
|
||||||
import math
|
import math
|
||||||
|
from lxml import etree
|
||||||
|
import inkex
|
||||||
from inkex.paths import CubicSuperPath, Path
|
from inkex.paths import CubicSuperPath, Path
|
||||||
from inkex.transforms import Transform
|
from inkex.transforms import Transform
|
||||||
from inkex.styles import Style
|
from inkex.styles import Style
|
||||||
@ -57,7 +59,7 @@ class ApplyTransform(inkex.EffectExtension):
|
|||||||
|
|
||||||
def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
|
def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
|
||||||
|
|
||||||
transf = Transform(transf) * Transform(node.get("transform", None))
|
transf = Transform(transf) * Transform(node.get("transform", None)) #a, b, c, d = linear transformations / e, f = translations
|
||||||
|
|
||||||
if 'transform' in node.attrib:
|
if 'transform' in node.attrib:
|
||||||
del node.attrib['transform']
|
del node.attrib['transform']
|
||||||
@ -139,10 +141,56 @@ class ApplyTransform(inkex.EffectExtension):
|
|||||||
else:
|
else:
|
||||||
node.set("r", edgex / 2)
|
node.set("r", edgex / 2)
|
||||||
|
|
||||||
|
elif node.tag == inkex.addNS("use", "svg"):
|
||||||
|
href = None
|
||||||
|
old_href_key = '{http://www.w3.org/1999/xlink}href'
|
||||||
|
new_href_key = 'href'
|
||||||
|
if node.attrib.has_key(old_href_key) is True: # {http://www.w3.org/1999/xlink}href (which gets displayed as 'xlink:href') attribute is deprecated. the newer attribute is just 'href'
|
||||||
|
href = node.attrib.get(old_href_key)
|
||||||
|
#node.attrib.pop(old_href_key)
|
||||||
|
if node.attrib.has_key(new_href_key) is True:
|
||||||
|
href = node.attrib.get(new_href_key) #we might overwrite the previous deprecated xlink:href but it's okay
|
||||||
|
#node.attrib.pop(new_href_key)
|
||||||
|
|
||||||
|
#get the linked object from href attribute
|
||||||
|
linkedObject = self.document.getroot().xpath("//*[@id = '%s']" % href.lstrip('#')) #we must remove hashtag symbol
|
||||||
|
linkedObjectCopy = copy.copy(linkedObject[0])
|
||||||
|
objectType = linkedObject[0].tag
|
||||||
|
|
||||||
|
if objectType == inkex.addNS("image", "svg"):
|
||||||
|
mask = None #image might have an alpha channel
|
||||||
|
new_mask_id = self.svg.get_unique_id("mask")
|
||||||
|
newMask = None
|
||||||
|
if node.attrib.has_key('mask') is True:
|
||||||
|
mask = node.attrib.get('mask')
|
||||||
|
#node.attrib.pop('mask')
|
||||||
|
|
||||||
|
#get the linked mask from mask attribute. We remove the old and create a new
|
||||||
|
if mask is not None:
|
||||||
|
linkedMask = self.document.getroot().xpath("//*[@id = '%s']" % mask.lstrip('url(#').rstrip(')')) #we must remove hashtag symbol
|
||||||
|
linkedMask[0].getparent().remove(linkedMask[0])
|
||||||
|
maskAttributes = {'id': new_mask_id}
|
||||||
|
newMask = etree.SubElement(self.document.getroot(), inkex.addNS('mask', 'svg'), maskAttributes)
|
||||||
|
|
||||||
|
width = float(linkedObjectCopy.get('width')) * transf.a
|
||||||
|
height = float(linkedObjectCopy.get('height')) * transf.d
|
||||||
|
linkedObjectCopy.set('width', '{:1.6f}'.format(width))
|
||||||
|
linkedObjectCopy.set('height', '{:1.6f}'.format(height))
|
||||||
|
linkedObjectCopy.set('x', '{:1.6f}'.format(transf.e))
|
||||||
|
linkedObjectCopy.set('y', '{:1.6f}'.format(transf.f))
|
||||||
|
if newMask is not None:
|
||||||
|
linkedObjectCopy.set('mask', 'url(#' + new_mask_id + ')')
|
||||||
|
maskRectAttributes = {'x': '{:1.6f}'.format(transf.e), 'y': '{:1.6f}'.format(transf.f), 'width': '{:1.6f}'.format(width), 'height': '{:1.6f}'.format(height), 'style':'fill:#ffffff;'}
|
||||||
|
maskRect = etree.SubElement(newMask, inkex.addNS('rect', 'svg'), maskRectAttributes)
|
||||||
|
else:
|
||||||
|
self.recursiveFuseTransform(linkedObjectCopy, transf)
|
||||||
|
|
||||||
|
self.document.getroot().append(linkedObjectCopy) #for each svg:use we append a copy to the document root
|
||||||
|
node.getparent().remove(node) #then we remove the use object
|
||||||
|
|
||||||
elif node.tag in [inkex.addNS('rect', 'svg'),
|
elif node.tag in [inkex.addNS('rect', 'svg'),
|
||||||
inkex.addNS('text', 'svg'),
|
inkex.addNS('text', 'svg'),
|
||||||
inkex.addNS('image', 'svg'),
|
inkex.addNS('image', 'svg')]:
|
||||||
inkex.addNS('use', 'svg')]:
|
|
||||||
inkex.utils.errormsg(
|
inkex.utils.errormsg(
|
||||||
"Shape %s (%s) not yet supported, try Object to path first"
|
"Shape %s (%s) not yet supported, try Object to path first"
|
||||||
% (node.TAG, node.get("id"))
|
% (node.TAG, node.get("id"))
|
||||||
|
@ -2,20 +2,43 @@
|
|||||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
<name>Cleanup Styles</name>
|
<name>Cleanup Styles</name>
|
||||||
<id>fablabchemnitz.de.cleanup</id>
|
<id>fablabchemnitz.de.cleanup</id>
|
||||||
<param name="stroke_width" type="float" precision="4" min="0.0000" max="5.0000" gui-text="Stroke width">0.1000</param>
|
<param name="main_tabs" type="notebook">
|
||||||
<param name="stroke_units" gui-text="Units" type="optiongroup" appearance="combo">
|
<page name="tab_active" gui-text="Cleanup Styles">
|
||||||
<option value="px">px</option>
|
<param name="dedicated_style_attributes" gui-text="Handling of dedicated style attributes" gui-description="We delete dedicated attributes like 'fill' or 'stroke'. Please choose an option what should happen with those properties." type="optiongroup" appearance="combo">
|
||||||
<option value="pt">pt</option>
|
<option value="prefer_composed">Catch dedicated, but prefer composed (master) style</option>
|
||||||
<option value="in">in</option>
|
<option value="prefer_dedicated">Catch dedicated and prefer over composed (master) style</option>
|
||||||
<option value="cm">cm</option>
|
<option value="ignore">Ignore dedicated</option>
|
||||||
<option value="mm">mm</option>
|
</param>
|
||||||
|
<param name="stroke_width_override" type="bool" gui-text="Override stroke width">false</param>
|
||||||
|
<param name="stroke_width" type="float" precision="3" min="0.0000" max="5.000" gui-text="Stroke width">0.100</param>
|
||||||
|
<param name="stroke_width_units" gui-text="Units" type="optiongroup" appearance="combo">
|
||||||
|
<option value="px">px</option>
|
||||||
|
<option value="pt">pt</option>
|
||||||
|
<option value="in">in</option>
|
||||||
|
<option value="cm">cm</option>
|
||||||
|
<option value="mm">mm</option>
|
||||||
|
</param>
|
||||||
|
<param name="stroke_opacity_override" type="bool" gui-text="Override stroke opacity">false</param>
|
||||||
|
<param name="stroke_opacity" type="float" precision="1" min="0.0" max="100.0" gui-text="Stroke opacity (%)">100.0</param>
|
||||||
|
<param name="reset_stroke_attributes" type="bool" gui-text="Reset stroke style attributes" gui-description="Remove stroke style attributes 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linejoin', 'stroke-linecap', 'stroke-miterlimit'">true</param>
|
||||||
|
<param name="reset_fill_attributes" type="bool" gui-text="Reset fill style attributes" gui-description="Sets 'fill:none;fill-opacity:1;' to style attribute">true</param>
|
||||||
|
<param name="apply_hairlines" type="bool" gui-text="Add additional hairline style" gui-description="Adds 'vector-effect:non-scaling-stroke;' and '-inkscape-stroke:hairline;' Hint: stroke-width is kept in background. All hairlines still have a valued width.">true</param>
|
||||||
|
<param name="apply_black_strokes" type="bool" gui-text="Apply black strokes where strokes missing" gui-description="Adds 'stroke:#000000;' to style attribute">true</param>
|
||||||
|
<param name="remove_group_styles" type="bool" gui-text="Remove styles from groups" gui-description="Remove style attributes from parent groups. So we have styles directly at the level of visivle nodes!">false</param>
|
||||||
|
<label>This extension works on current selection or for complete document</label>
|
||||||
|
</page>
|
||||||
|
<page name="tab_about" gui-text="About">
|
||||||
|
<label appearance="header">About</label>
|
||||||
|
<separator />
|
||||||
|
<label>Cleanup Styles by Mario Voigt / Stadtfabrikanten e.V. (2021)</label>
|
||||||
|
<label>This piece of software is part of the MightyScape for InkScape 1.0/1.1dev Extension Collection.</label>
|
||||||
|
<label>You found a bug or got some fresh code? Just report to mario.voigt@stadtfabrikanten.org. Thanks!</label>
|
||||||
|
<label appearance="url">https://fablabchemnitz.de</label>
|
||||||
|
<label>License: GNU GPL v3</label>
|
||||||
|
<separator />
|
||||||
|
<label>This extension generates inventory stickers for thermo printers (we use Brother QL-720NW) from our Teedy instance. Teedy is an open source software document management system (DMS). You can find the complete documentation at the Wiki space of https://fablabchemnitz.de</label>
|
||||||
|
</page>
|
||||||
</param>
|
</param>
|
||||||
<param name="opacity" type="float" precision="1" min="0" max="100" gui-text="Opacity (%)">100.0</param>
|
|
||||||
<param name="reset_style_attributes" type="bool" gui-text="Reset stroke style attributes" gui-description="Remove stroke style attributes like stroke-dasharray, stroke-dashoffset, stroke-linejoin, linecap, stroke-miterlimit">true</param>
|
|
||||||
<param name="reset_fill_attributes" type="bool" gui-text="Reset fill style attributes" gui-description="Sets 'fill:none;' to style attribute">true</param>
|
|
||||||
<param name="apply_hairlines" type="bool" gui-text="Add additional hairline style" gui-description="Adds 'vector-effect:non-scaling-stroke;' and '-inkscape-stroke:hairline;' Hint: stroke-width is kept in background. All hairlines still have a valued width.">true</param>
|
|
||||||
<param name="apply_black_strokes" type="bool" gui-text="Apply black strokes where strokes missing" gui-description="Adds 'stroke:#000000;' to style attribute">true</param>
|
|
||||||
<label>This extension works on current selection or for complete document</label>
|
|
||||||
<effect needs-live-preview="true">
|
<effect needs-live-preview="true">
|
||||||
<object-type>all</object-type>
|
<object-type>all</object-type>
|
||||||
<effects-menu>
|
<effects-menu>
|
||||||
|
@ -16,23 +16,40 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program; if not, write to the Free Software
|
along with this program; if not, write to the Free Software
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
Based on coloreffect.py by Jos Hirth and Aaron C. Spike
|
Based on
|
||||||
|
- coloreffect.py by Jos Hirth and Aaron C. Spike
|
||||||
|
- cleanup.py https://github.com/attoparsec/inkscape-extensions by attoparsec
|
||||||
|
|
||||||
|
Author: Mario Voigt / FabLab Chemnitz
|
||||||
|
Mail: mario.voigt@stadtfabrikanten.org
|
||||||
|
Last Patch: 12.04.2021
|
||||||
|
License: GNU GPL v3
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- This extension does not check if attributes contain duplicates properties like "opacity:1;fill:#393834;fill-opacity:1;opacity:1;fill:#393834;fill-opacity:1". We assume the SVG syntax is correct
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import inkex
|
import inkex
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class Cleanup(inkex.EffectExtension):
|
class Cleanup(inkex.EffectExtension):
|
||||||
|
|
||||||
|
groups = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
inkex.Effect.__init__(self)
|
inkex.Effect.__init__(self)
|
||||||
self.arg_parser.add_argument("--stroke_width", type=float, default=0.1, help="Stroke width")
|
self.arg_parser.add_argument("--main_tabs")
|
||||||
self.arg_parser.add_argument("--stroke_units", default="mm", help="Stroke unit")
|
self.arg_parser.add_argument("--dedicated_style_attributes", default="ignore", help="Handling of dedicated style attributes")
|
||||||
self.arg_parser.add_argument("--opacity", type=float, default="100.0", help="Opacity")
|
self.arg_parser.add_argument("--stroke_width_override", type=inkex.Boolean, default=False, help="Override stroke width")
|
||||||
self.arg_parser.add_argument("--reset_style_attributes", type=inkex.Boolean, help="Remove stroke style attributes like stroke-dasharray, stroke-dashoffset, stroke-linejoin, linecap, stroke-miterlimit")
|
self.arg_parser.add_argument("--stroke_width", type=float, default=0.100, help="Stroke width")
|
||||||
self.arg_parser.add_argument("--reset_fill_attributes", type=inkex.Boolean, help="Sets 'fill:none;' to style attribute")
|
self.arg_parser.add_argument("--stroke_width_units", default="mm", help="Stroke width unit")
|
||||||
|
self.arg_parser.add_argument("--stroke_opacity_override", type=inkex.Boolean, default=False, help="Override stroke opacity")
|
||||||
|
self.arg_parser.add_argument("--stroke_opacity", type=float, default="100.0", help="Stroke opacity (%)")
|
||||||
|
self.arg_parser.add_argument("--reset_stroke_attributes", type=inkex.Boolean, help="Remove stroke style attributes 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linejoin', 'stroke-linecap', 'stroke-miterlimit'")
|
||||||
|
self.arg_parser.add_argument("--reset_fill_attributes", type=inkex.Boolean, help="Sets 'fill:none;fill-opacity:1;' to style attribute")
|
||||||
self.arg_parser.add_argument("--apply_hairlines", type=inkex.Boolean, help="Adds 'vector-effect:non-scaling-stroke;' and '-inkscape-stroke:hairline;' Hint: stroke-width is kept in background. All hairlines still have a valued width.")
|
self.arg_parser.add_argument("--apply_hairlines", type=inkex.Boolean, help="Adds 'vector-effect:non-scaling-stroke;' and '-inkscape-stroke:hairline;' Hint: stroke-width is kept in background. All hairlines still have a valued width.")
|
||||||
self.arg_parser.add_argument("--apply_black_strokes", type=inkex.Boolean, help="Adds 'stroke:#000000;' to style attribute")
|
self.arg_parser.add_argument("--apply_black_strokes", type=inkex.Boolean, help="Adds 'stroke:#000000;' to style attribute")
|
||||||
|
self.arg_parser.add_argument("--remove_group_styles", type=inkex.Boolean, help="Remove styles from groups")
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
if len(self.svg.selected) == 0:
|
if len(self.svg.selected) == 0:
|
||||||
@ -40,6 +57,11 @@ class Cleanup(inkex.EffectExtension):
|
|||||||
else:
|
else:
|
||||||
for id, node in self.svg.selected.items():
|
for id, node in self.svg.selected.items():
|
||||||
self.getAttribs(node)
|
self.getAttribs(node)
|
||||||
|
#finally remove the styles from collected groups (if enabled)
|
||||||
|
if self.options.remove_group_styles is True:
|
||||||
|
for group in self.groups:
|
||||||
|
if group.attrib.has_key('style') is True:
|
||||||
|
group.attrib.pop('style')
|
||||||
|
|
||||||
def getAttribs(self, node):
|
def getAttribs(self, node):
|
||||||
self.changeStyle(node)
|
self.changeStyle(node)
|
||||||
@ -48,72 +70,106 @@ class Cleanup(inkex.EffectExtension):
|
|||||||
|
|
||||||
#stroke and fill styles can be included in style attribute or they can exist separately (can occure in older SVG files). We do not parse other attributes than style
|
#stroke and fill styles can be included in style attribute or they can exist separately (can occure in older SVG files). We do not parse other attributes than style
|
||||||
def changeStyle(self, node):
|
def changeStyle(self, node):
|
||||||
nodeDict = []
|
|
||||||
nodeDict.append(inkex.addNS('line','svg'))
|
|
||||||
nodeDict.append(inkex.addNS('polyline','svg'))
|
|
||||||
nodeDict.append(inkex.addNS('polygon','svg'))
|
|
||||||
nodeDict.append(inkex.addNS('circle','svg'))
|
|
||||||
nodeDict.append(inkex.addNS('ellipse','svg'))
|
|
||||||
nodeDict.append(inkex.addNS('rect','svg'))
|
|
||||||
nodeDict.append(inkex.addNS('path','svg'))
|
|
||||||
nodeDict.append(inkex.addNS('g','svg'))
|
|
||||||
if node.tag in nodeDict:
|
|
||||||
if node.attrib.has_key('style'):
|
|
||||||
style = node.get('style')
|
|
||||||
if style:
|
|
||||||
#add missing style attributes if required
|
|
||||||
if style.endswith(';') is False:
|
|
||||||
style += ';'
|
|
||||||
if re.search('(;|^)stroke:(.*?)(;|$)', style) is None: #if "stroke" is None, add one. We need to distinguish because there's also attribute "-inkscape-stroke" that's why we check starting with ^ or ;
|
|
||||||
style += 'stroke:none;'
|
|
||||||
if "stroke-width:" not in style:
|
|
||||||
style += 'stroke-width:{:1.4f};'.format(self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_units))
|
|
||||||
if "stroke-opacity:" not in style:
|
|
||||||
style += 'stroke-opacity:{:1.1f};'.format(self.options.opacity / 100)
|
|
||||||
|
|
||||||
if self.options.apply_hairlines is True:
|
#we check/modify the style of all shapes (not groups)
|
||||||
if "vector-effect:non-scaling-stroke" not in style:
|
if isinstance(node, inkex.ShapeElement) and not isinstance(node, inkex.Group):
|
||||||
style += 'vector-effect:non-scaling-stroke;'
|
# the final styles applied to this element (with influence from top level elements like groups)
|
||||||
if "-inkscape-stroke:hairline" not in style:
|
composed_style = node.composed_style()
|
||||||
style += '-inkscape-stroke:hairline;'
|
composedStyleAttributes = str(composed_style).split(';') #array
|
||||||
|
composedStyleAttributesDict = {}
|
||||||
|
if len(composed_style) > 0: #Style "composed_style" might contain just empty '' string which will lead to failing dict update
|
||||||
|
for composedStyleAttribute in composedStyleAttributes:
|
||||||
|
composedStyleAttributesDict.update({'{}'.format(composedStyleAttribute.split(':')[0]): composedStyleAttribute.split(':')[1]})
|
||||||
|
|
||||||
if re.search('fill:(.*?)(;|$)', style) is None: #if "fill" is None, add one.
|
#three options to handle dedicated attributes (attributes not in the "style" attribute, but separate):
|
||||||
style += 'fill:none;'
|
# - just delete all dedicated properties
|
||||||
|
# - merge dedicated properties, and prefer them over those from composed style
|
||||||
|
# - merge dedicated properties, but prefer properties from composed style
|
||||||
|
dedicatedStyleAttributesDict = {}
|
||||||
|
popDict = []
|
||||||
|
popDict.extend(['stroke', 'stroke-opacity', 'stroke-width', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'fill', 'fill-opacity'])
|
||||||
|
for popItem in popDict:
|
||||||
|
if node.attrib.has_key(str(popItem)):
|
||||||
|
dedicatedStyleAttributesDict.update({'{}'.format(popItem): node.get(popItem)})
|
||||||
|
node.attrib.pop(popItem)
|
||||||
|
|
||||||
#then parse the content and check what we need to adjust
|
#inkex.utils.debug("composedStyleAttributesDict = " + str(composedStyleAttributesDict))
|
||||||
declarations = style.split(';')
|
#inkex.utils.debug("dedicatedStyleAttributesDict = " + str(dedicatedStyleAttributesDict))
|
||||||
for i, decl in enumerate(declarations):
|
|
||||||
parts = decl.split(':', 2)
|
if self.options.dedicated_style_attributes == 'prefer_dedicated':
|
||||||
if len(parts) == 2:
|
composedStyleAttributesDict.update(dedicatedStyleAttributesDict)
|
||||||
(prop, val) = parts
|
node.set('style', composedStyleAttributesDict)
|
||||||
prop = prop.strip().lower()
|
elif self.options.dedicated_style_attributes == 'prefer_composed':
|
||||||
if prop == 'stroke-width':
|
dedicatedStyleAttributesDict.update(composedStyleAttributesDict)
|
||||||
new_val = self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_units)
|
node.set('style', dedicatedStyleAttributesDict)
|
||||||
declarations[i] = prop + ':{:1.4f}'.format(new_val)
|
elif self.options.dedicated_style_attributes == 'ignore':
|
||||||
if prop == 'stroke-opacity':
|
pass
|
||||||
new_val = self.options.opacity / 100
|
|
||||||
declarations[i] = prop + ':{:1.1f}'.format(new_val)
|
# now parse the style with possibly merged dedicated attributes modded style attribute (dedicatedStyleAttributes)
|
||||||
if self.options.reset_style_attributes is True:
|
if node.attrib.has_key('style') is False:
|
||||||
if prop == 'stroke-dasharray':
|
node.set('style', 'stroke:#000000;') #we add basic stroke color black. We cannot set to empty value or just ";" because it will not update properly
|
||||||
declarations[i] = ''
|
style = node.get('style')
|
||||||
if prop == 'stroke-dashoffset':
|
|
||||||
declarations[i] = ''
|
#add missing style attributes if required
|
||||||
if prop == 'stroke-linejoin':
|
if style.endswith(';') is False:
|
||||||
declarations[i] = ''
|
style += ';'
|
||||||
if prop == 'stroke-linecap':
|
if re.search('(;|^)stroke:(.*?)(;|$)', style) is None: #if "stroke" is None, add one. We need to distinguish because there's also attribute "-inkscape-stroke" that's why we check starting with ^ or ;
|
||||||
declarations[i] = ''
|
style += 'stroke:none;'
|
||||||
if prop == 'stroke-miterlimit':
|
if self.options.stroke_width_override is True and "stroke-width:" not in style:
|
||||||
declarations[i] = ''
|
style += 'stroke-width:{:1.4f};'.format(self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_width_units))
|
||||||
if self.options.apply_black_strokes is True:
|
if self.options.stroke_opacity_override is True and "stroke-opacity:" not in style:
|
||||||
if prop == 'stroke':
|
style += 'stroke-opacity:{:1.1f};'.format(self.options.stroke_opacity / 100)
|
||||||
if val == 'none':
|
|
||||||
new_val = '#000000'
|
if self.options.apply_hairlines is True:
|
||||||
declarations[i] = prop + ':' + new_val
|
if "vector-effect:non-scaling-stroke" not in style:
|
||||||
if self.options.reset_fill_attributes is True:
|
style += 'vector-effect:non-scaling-stroke;'
|
||||||
if prop == 'fill':
|
if "-inkscape-stroke:hairline" not in style:
|
||||||
new_val = 'none'
|
style += '-inkscape-stroke:hairline;'
|
||||||
declarations[i] = prop + ':' + new_val
|
|
||||||
node.set('style', ';'.join(declarations))
|
if re.search('fill:(.*?)(;|$)', style) is None: #if "fill" is None, add one.
|
||||||
|
style += 'fill:none;'
|
||||||
|
|
||||||
|
#then parse the content and check what we need to adjust
|
||||||
|
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' and self.options.stroke_width_override is True:
|
||||||
|
new_val = self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_width_units)
|
||||||
|
declarations[i] = prop + ':{:1.4f}'.format(new_val)
|
||||||
|
if prop == 'stroke-opacity' and self.options.stroke_opacity_override is True:
|
||||||
|
new_val = self.options.stroke_opacity / 100
|
||||||
|
declarations[i] = prop + ':{:1.1f}'.format(new_val)
|
||||||
|
if self.options.reset_stroke_attributes is True:
|
||||||
|
if prop == 'stroke-dasharray':
|
||||||
|
declarations[i] = ''
|
||||||
|
if prop == 'stroke-dashoffset':
|
||||||
|
declarations[i] = ''
|
||||||
|
if prop == 'stroke-linejoin':
|
||||||
|
declarations[i] = ''
|
||||||
|
if prop == 'stroke-linecap':
|
||||||
|
declarations[i] = ''
|
||||||
|
if prop == 'stroke-miterlimit':
|
||||||
|
declarations[i] = ''
|
||||||
|
if self.options.apply_black_strokes is True:
|
||||||
|
if prop == 'stroke':
|
||||||
|
if val == 'none':
|
||||||
|
new_val = '#000000'
|
||||||
|
declarations[i] = prop + ':' + new_val
|
||||||
|
if self.options.reset_fill_attributes is True:
|
||||||
|
if prop == 'fill':
|
||||||
|
new_val = 'none'
|
||||||
|
declarations[i] = prop + ':' + new_val
|
||||||
|
if prop == 'fill-opacity':
|
||||||
|
new_val = '1'
|
||||||
|
declarations[i] = prop + ':' + new_val
|
||||||
|
node.set('style', ';'.join(declarations))
|
||||||
|
|
||||||
|
# if element is group we add it to collection to remove it's style after parsing all selected items
|
||||||
|
elif isinstance(node, inkex.ShapeElement) and isinstance(node, inkex.Group):
|
||||||
|
self.groups.append(node)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
Cleanup().run()
|
Cleanup().run()
|
19
extensions/fablabchemnitz/export_selection.inx
Normal file
19
extensions/fablabchemnitz/export_selection.inx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<_name>Export selection as SVG</_name>
|
||||||
|
<id>fablabchemnitz.de.export_selection_as_svg</id>
|
||||||
|
<param name="wrap_transform" type="boolean" _gui-text="Wrap final document in transform">false</param>
|
||||||
|
<param name="export_dir" type="path" mode="folder" gui-text="Location to save exported documents">./inkscape_export/</param>
|
||||||
|
<effect needs-document="true" needs-live-preview="false">
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<menu-tip>Export selection to separate SVG file.</menu-tip>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Import/Export/Transfer"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command reldir="inx" interpreter="python">export_selection.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
100
extensions/fablabchemnitz/export_selection.py
Normal file
100
extensions/fablabchemnitz/export_selection.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from copy import deepcopy
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
import inkex.command
|
||||||
|
from lxml import etree
|
||||||
|
from scour.scour import scourString
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
GROUP_ID = 'export_selection_transform'
|
||||||
|
|
||||||
|
|
||||||
|
class ExportObject(inkex.EffectExtension):
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--wrap_transform", type=inkex.Boolean, default=False, help="Wrap final document in transform")
|
||||||
|
pars.add_argument("--export_dir", default="~/inkscape_export/", help="Location to save exported documents")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if not self.svg.selected:
|
||||||
|
return
|
||||||
|
|
||||||
|
export_dir = Path(self.absolute_href(self.options.export_dir))
|
||||||
|
os.makedirs(export_dir, exist_ok=True)
|
||||||
|
|
||||||
|
bbox = inkex.BoundingBox()
|
||||||
|
for elem in self.svg.selected.values():
|
||||||
|
transform = inkex.Transform()
|
||||||
|
parent = elem.getparent()
|
||||||
|
if parent is not None and isinstance(parent, inkex.ShapeElement):
|
||||||
|
transform = parent.composed_transform()
|
||||||
|
try:
|
||||||
|
bbox += elem.bounding_box(transform)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Bounding box not computed")
|
||||||
|
logger.info("Skipping bounding box")
|
||||||
|
transform = elem.composed_transform()
|
||||||
|
x1, y1 = transform.apply_to_point([0, 0])
|
||||||
|
x2, y2 = transform.apply_to_point([1, 1])
|
||||||
|
bbox += inkex.BoundingBox((x1, x2), (y1, y2))
|
||||||
|
|
||||||
|
template = self.create_document()
|
||||||
|
filename = None
|
||||||
|
|
||||||
|
group = etree.SubElement(template, '{http://www.w3.org/2000/svg}g')
|
||||||
|
group.attrib['id'] = GROUP_ID
|
||||||
|
group.attrib['transform'] = str(inkex.Transform(((1, 0, -bbox.left), (0, 1, -bbox.top))))
|
||||||
|
|
||||||
|
for elem in self.svg.selected.values():
|
||||||
|
|
||||||
|
elem_copy = deepcopy(elem)
|
||||||
|
elem_copy.attrib['transform'] = str(elem.composed_transform())
|
||||||
|
group.append(elem_copy)
|
||||||
|
|
||||||
|
width = math.ceil(bbox.width)
|
||||||
|
height = math.ceil(bbox.height)
|
||||||
|
template.attrib['viewBox'] = f'0 0 {width} {height}'
|
||||||
|
template.attrib['width'] = f'{width}'
|
||||||
|
template.attrib['height'] = f'{height}'
|
||||||
|
|
||||||
|
if filename is None:
|
||||||
|
filename = elem.attrib.get('id', None)
|
||||||
|
if filename:
|
||||||
|
filename = filename.replace(os.sep, '_') + '.svg'
|
||||||
|
if not filename:
|
||||||
|
filename = 'element.svg'
|
||||||
|
|
||||||
|
template.append(group)
|
||||||
|
|
||||||
|
if not self.options.wrap_transform:
|
||||||
|
self.load(inkex.command.inkscape_command(template.tostring(), select=GROUP_ID, verbs=['SelectionUnGroup']))
|
||||||
|
template = self.svg
|
||||||
|
for child in template.getchildren():
|
||||||
|
if child.tag == '{http://www.w3.org/2000/svg}metadata':
|
||||||
|
template.remove(child)
|
||||||
|
|
||||||
|
self.save_document(template, export_dir / filename)
|
||||||
|
|
||||||
|
def create_document(self):
|
||||||
|
document = self.svg.copy()
|
||||||
|
for child in document.getchildren():
|
||||||
|
if child.tag == '{http://www.w3.org/2000/svg}defs':
|
||||||
|
continue
|
||||||
|
document.remove(child)
|
||||||
|
return document
|
||||||
|
|
||||||
|
def save_document(self, document, filename):
|
||||||
|
with open(filename, 'wb') as fp:
|
||||||
|
document = document.tostring()
|
||||||
|
fp.write(scourString(document).encode('utf8'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ExportObject().run()
|
16
extensions/fablabchemnitz/flip.inx
Normal file
16
extensions/fablabchemnitz/flip.inx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Flip</name>
|
||||||
|
<id>fablabchemnitz.de.Flip</id>
|
||||||
|
<effect>
|
||||||
|
<object-type>path</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Modify existing Path(s)"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command reldir="inx" interpreter="python">flip.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
38
extensions/fablabchemnitz/flip.py
Normal file
38
extensions/fablabchemnitz/flip.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import math
|
||||||
|
from inkex import EffectExtension, PathElement, transforms as T
|
||||||
|
|
||||||
|
# https://en.wikipedia.org/wiki/Rotations_and_reflections_in_two_dimensions
|
||||||
|
def reflection_matrix(theta):
|
||||||
|
theta2 = 2 * theta
|
||||||
|
return [
|
||||||
|
[math.cos(theta2), math.sin(theta2), 0],
|
||||||
|
[math.sin(theta2), -math.cos(theta2), 0],
|
||||||
|
]
|
||||||
|
|
||||||
|
def svg_matrix_order(mat):
|
||||||
|
((a, c, e), (b, d, f)) = mat
|
||||||
|
return a, b, c, d, e, f
|
||||||
|
|
||||||
|
class FlipPath(EffectExtension):
|
||||||
|
"""Extension to flip a path about the line from the start to end node"""
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
for node in self.svg.selection.filter(PathElement).values():
|
||||||
|
points = list(node.path.end_points)
|
||||||
|
if len(points) < 2 or points[0] == points[-1]:
|
||||||
|
continue
|
||||||
|
start = points[0]
|
||||||
|
end = points[-1]
|
||||||
|
v = end - start
|
||||||
|
theta = math.atan2(v.y, v.x)
|
||||||
|
|
||||||
|
# transforms go in reverse order
|
||||||
|
mat = T.Transform()
|
||||||
|
mat.add_translate(start)
|
||||||
|
mat.add_matrix(*svg_matrix_order(reflection_matrix(theta)))
|
||||||
|
mat.add_translate(-start)
|
||||||
|
|
||||||
|
node.path = node.path.transform(mat)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
FlipPath().run()
|
@ -25,7 +25,7 @@
|
|||||||
<label appearance="header">About</label>
|
<label appearance="header">About</label>
|
||||||
<separator />
|
<separator />
|
||||||
<label>Inventory Stickers by Mario Voigt / Stadtfabrikanten e.V. (2021)</label>
|
<label>Inventory Stickers by Mario Voigt / Stadtfabrikanten e.V. (2021)</label>
|
||||||
<label>This piece of software is part of the MightyScape for InkScape 1.0/1.1dev Extension Collection</label>
|
<label>This piece of software is part of the MightyScape for InkScape 1.0/1.1dev Extension Collection.</label>
|
||||||
<label>you found a bug or got some fresh code? Just report to mario.voigt@stadtfabrikanten.org. Thanks!</label>
|
<label>you found a bug or got some fresh code? Just report to mario.voigt@stadtfabrikanten.org. Thanks!</label>
|
||||||
<label appearance="url">https://fablabchemnitz.de</label>
|
<label appearance="url">https://fablabchemnitz.de</label>
|
||||||
<label>License: GNU GPL v3</label>
|
<label>License: GNU GPL v3</label>
|
||||||
|
@ -39,17 +39,26 @@
|
|||||||
<separator/>
|
<separator/>
|
||||||
<vbox>
|
<vbox>
|
||||||
<label appearance="header">Objects / Misc</label>
|
<label appearance="header">Objects / Misc</label>
|
||||||
<!--<param name="svg" type="bool" gui-text="svg">true</param>-->
|
<hbox>
|
||||||
<!--<param name="sodipodi" type="bool" gui-text="sodipodi">true</param>-->
|
<vbox>
|
||||||
<param name="clipPath" type="bool" gui-text="clipPath">true</param>
|
<!--<param name="svg" type="bool" gui-text="svg">true</param>-->
|
||||||
<param name="defs" type="bool" gui-text="defs">true</param>
|
<!--<param name="sodipodi" type="bool" gui-text="sodipodi">true</param>-->
|
||||||
<param name="image" type="bool" gui-text="image">true</param>
|
<param name="clipPath" type="bool" gui-text="clipPath">true</param>
|
||||||
<param name="marker" type="bool" gui-text="marker">true</param>
|
<param name="defs" type="bool" gui-text="defs">true</param>
|
||||||
<param name="metadata" type="bool" gui-text="metadata">true</param>
|
<param name="image" type="bool" gui-text="image">true</param>
|
||||||
<param name="pattern" type="bool" gui-text="pattern">true</param>
|
<param name="mask" type="bool" gui-text="mask">true</param>
|
||||||
<param name="script" type="bool" gui-text="script">true</param>
|
<param name="marker" type="bool" gui-text="marker">true</param>
|
||||||
<param name="symbol" type="bool" gui-text="symbol">true</param>
|
<param name="metadata" type="bool" gui-text="metadata">true</param>
|
||||||
<param name="use" type="bool" gui-text="use">true</param>
|
</vbox>
|
||||||
|
<spacer/>
|
||||||
|
<vbox>
|
||||||
|
<param name="pattern" type="bool" gui-text="pattern">true</param>
|
||||||
|
<param name="script" type="bool" gui-text="script">true</param>
|
||||||
|
<param name="switch" type="bool" gui-text="switch">true</param>
|
||||||
|
<param name="symbol" type="bool" gui-text="symbol">true</param>
|
||||||
|
<param name="use" type="bool" gui-text="use">true</param>
|
||||||
|
</vbox>
|
||||||
|
</hbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
</hbox>
|
</hbox>
|
||||||
<separator/>
|
<separator/>
|
||||||
|
@ -47,6 +47,7 @@ class MigrateGroups(inkex.Effect):
|
|||||||
self.arg_parser.add_argument("--tspan", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--tspan", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--linearGradient", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--linearGradient", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--radialGradient", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--radialGradient", type=inkex.Boolean, default=True)
|
||||||
|
self.arg_parser.add_argument("--mask", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--meshGradient", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--meshGradient", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--meshRow", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--meshRow", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--meshPatch", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--meshPatch", type=inkex.Boolean, default=True)
|
||||||
@ -54,6 +55,7 @@ class MigrateGroups(inkex.Effect):
|
|||||||
self.arg_parser.add_argument("--script", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--script", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--symbol", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--symbol", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--stop", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--stop", type=inkex.Boolean, default=True)
|
||||||
|
self.arg_parser.add_argument("--switch", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--use", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--use", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--flowRoot", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--flowRoot", type=inkex.Boolean, default=True)
|
||||||
self.arg_parser.add_argument("--flowRegion", type=inkex.Boolean, default=True)
|
self.arg_parser.add_argument("--flowRegion", type=inkex.Boolean, default=True)
|
||||||
@ -85,8 +87,10 @@ class MigrateGroups(inkex.Effect):
|
|||||||
namespace.append("{http://www.w3.org/2000/svg}meshPatch") if self.options.meshPatch else ""
|
namespace.append("{http://www.w3.org/2000/svg}meshPatch") if self.options.meshPatch else ""
|
||||||
namespace.append("{http://www.w3.org/2000/svg}script") if self.options.script else ""
|
namespace.append("{http://www.w3.org/2000/svg}script") if self.options.script else ""
|
||||||
namespace.append("{http://www.w3.org/2000/svg}symbol") if self.options.symbol else ""
|
namespace.append("{http://www.w3.org/2000/svg}symbol") if self.options.symbol else ""
|
||||||
|
namespace.append("{http://www.w3.org/2000/svg}mask") if self.options.mask else ""
|
||||||
namespace.append("{http://www.w3.org/2000/svg}metadata") if self.options.metadata else ""
|
namespace.append("{http://www.w3.org/2000/svg}metadata") if self.options.metadata else ""
|
||||||
namespace.append("{http://www.w3.org/2000/svg}stop") if self.options.stop else ""
|
namespace.append("{http://www.w3.org/2000/svg}stop") if self.options.stop else ""
|
||||||
|
namespace.append("{http://www.w3.org/2000/svg}switch") if self.options.switch else ""
|
||||||
namespace.append("{http://www.w3.org/2000/svg}use") if self.options.use else ""
|
namespace.append("{http://www.w3.org/2000/svg}use") if self.options.use else ""
|
||||||
namespace.append("{http://www.w3.org/2000/svg}flowRoot") if self.options.flowRoot else ""
|
namespace.append("{http://www.w3.org/2000/svg}flowRoot") if self.options.flowRoot else ""
|
||||||
namespace.append("{http://www.w3.org/2000/svg}flowRegion") if self.options.flowRegion else ""
|
namespace.append("{http://www.w3.org/2000/svg}flowRegion") if self.options.flowRegion else ""
|
||||||
|
15
extensions/fablabchemnitz/openClosedPath.inx
Normal file
15
extensions/fablabchemnitz/openClosedPath.inx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Open closed Path</name>
|
||||||
|
<id>fablabchemnitz.de.openClosedPath</id>
|
||||||
|
<effect>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Modify existing Path(s)"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">openClosedPath.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
37
extensions/fablabchemnitz/openClosedPath.py
Normal file
37
extensions/fablabchemnitz/openClosedPath.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf-8
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Ellen Wasboe, ellen@wasbo.net
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
Remove all z-s from all selected paths
|
||||||
|
My purpose: to open paths of single line fonts with a temporary closing to fit into .ttf/.otf format files
|
||||||
|
"""
|
||||||
|
|
||||||
|
import inkex, re
|
||||||
|
from inkex import PathElement
|
||||||
|
|
||||||
|
class OpenClosedPath(inkex.EffectExtension):
|
||||||
|
# Extension to open a closed path by z or by last node
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
elements = self.svg.selection.filter(PathElement).values()
|
||||||
|
for elem in elements:
|
||||||
|
pp=elem.path.to_absolute() #remove transformation matrix
|
||||||
|
elem.path = re.sub(r"Z","",str(pp))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
OpenClosedPath().run()
|
36
extensions/fablabchemnitz/parabola.inx
Normal file
36
extensions/fablabchemnitz/parabola.inx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Parabola</name>
|
||||||
|
<id>fablabchemnitz.de.parabola</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="common" gui-text="Basic Options">
|
||||||
|
<param name="height" type="int" min="100" max="1000" gui-text="Shape Height:">120</param>
|
||||||
|
<param name="width" type="int" min="100" max="1000" gui-text="Shape Width:">120</param>
|
||||||
|
<param name="seg_count" type="int" min="5" max="100" gui-text="Number of Line Segments:">25</param>
|
||||||
|
<param name="shape" type="optiongroup" appearance="combo" gui-text="Choose a Shape:">
|
||||||
|
<option value="cross">Cross</option>
|
||||||
|
<option value="square">Square</option>
|
||||||
|
<option value="triangle">Triangle</option></param>
|
||||||
|
<spacer/>
|
||||||
|
<label>v1.1.0</label>
|
||||||
|
</page>
|
||||||
|
<page name="corners" gui-text="Corners">
|
||||||
|
<param name="c1" type="bool" gui-text="Corner 1">true</param>
|
||||||
|
<param name="c2" type="bool" gui-text="Corner 2">true</param>
|
||||||
|
<param name="c3" type="bool" gui-text="Corner 3">true</param>
|
||||||
|
<param name="c4" type="bool" gui-text="Corner 4 *">true</param>
|
||||||
|
<label>* Corner 4 doesn't apply to the Triangle Shape</label>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Shape/Pattern from Generator"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">parabola.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
240
extensions/fablabchemnitz/parabola.py
Normal file
240
extensions/fablabchemnitz/parabola.py
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf-8
|
||||||
|
#
|
||||||
|
# 2/27/2021 - v.1.1.0
|
||||||
|
# Copyright (C) 2021 Reginald Waters opensourcebear@nthebare.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.
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
This extension renders a wireframe shape and then draws lines to form a parabola
|
||||||
|
shape.
|
||||||
|
|
||||||
|
The height and width are independently variable. The number of lines will change
|
||||||
|
the density of the end product.
|
||||||
|
"""
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
from inkex import turtle as pturtle
|
||||||
|
|
||||||
|
class parabola(inkex.GenerateExtension):
|
||||||
|
container_label = 'Parabola'
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--height", type=int, default=300, help="Shape Height")
|
||||||
|
pars.add_argument("--width", type=int, default=300, help="Shape Width")
|
||||||
|
pars.add_argument("--seg_count", type=int, default=10, help="Number of line segments")
|
||||||
|
pars.add_argument("--shape", default="square")
|
||||||
|
pars.add_argument("--tab", default="common")
|
||||||
|
pars.add_argument("--c1", default="true")
|
||||||
|
pars.add_argument("--c2", default="false")
|
||||||
|
pars.add_argument("--c3", default="false")
|
||||||
|
pars.add_argument("--c4", default="false")
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
# Let's simplify the variable names
|
||||||
|
ht = int(self.options.height)
|
||||||
|
wt = int(self.options.width)
|
||||||
|
sc = int(self.options.seg_count)
|
||||||
|
shape = self.options.shape
|
||||||
|
c1 = self.options.c1
|
||||||
|
c2 = self.options.c2
|
||||||
|
c3 = self.options.c3
|
||||||
|
c4 = self.options.c4
|
||||||
|
|
||||||
|
point = self.svg.namedview.center
|
||||||
|
style = inkex.Style({
|
||||||
|
'stroke-linejoin': 'miter', 'stroke-width': str(self.svg.unittouu('1px')),
|
||||||
|
'stroke-opacity': '1.0', 'fill-opacity': '1.0',
|
||||||
|
'stroke': '#000000', 'stroke-linecap': 'butt',
|
||||||
|
'fill': 'none'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Setting the amount to move across the horizontal and vertical
|
||||||
|
increaseht = (ht / sc)
|
||||||
|
increasewt = (wt / sc)
|
||||||
|
|
||||||
|
tur = pturtle.pTurtle()
|
||||||
|
|
||||||
|
tur.pu() # Pen up
|
||||||
|
tur.setpos(point) # start in the center
|
||||||
|
|
||||||
|
if shape == "cross":
|
||||||
|
# We draw the cross shape and store the 4 points
|
||||||
|
# Can this be looped?
|
||||||
|
# Should I store the coordinates in an array/list?
|
||||||
|
tur.forward((ht / 2))
|
||||||
|
toppoint = tur.getpos()
|
||||||
|
if c3 == 'true' or c4 == 'true':
|
||||||
|
tur.pd()
|
||||||
|
tur.backward((ht / 2))
|
||||||
|
tur.pu()
|
||||||
|
if c1 == 'true' or c2 == 'true':
|
||||||
|
tur.pd()
|
||||||
|
tur.backward((ht / 2))
|
||||||
|
bottompoint = tur.getpos()
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos(point)
|
||||||
|
tur.right(90)
|
||||||
|
tur.forward((wt / 2))
|
||||||
|
rightpoint = tur.getpos()
|
||||||
|
if c3 == 'true' or c2 == 'true':
|
||||||
|
tur.pd()
|
||||||
|
tur.backward((wt / 2))
|
||||||
|
tur.pu()
|
||||||
|
if c1 == 'true' or c4 == 'true':
|
||||||
|
tur.pd()
|
||||||
|
tur.backward((wt / 2))
|
||||||
|
leftpoint = tur.getpos()
|
||||||
|
|
||||||
|
while sc > 0:
|
||||||
|
if c1 == 'true':
|
||||||
|
# Drawing the SE Corner based on SW coordinates
|
||||||
|
# We always draw this corner
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((bottompoint[0], bottompoint[1] - ( (increaseht / 2) * sc ) ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((bottompoint[0] + ( (increasewt / 2) * sc ), bottompoint[1] - (ht / 2) ))
|
||||||
|
|
||||||
|
if c2 == 'true': # Drawing the SW Corner based on SE Coordinates
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((bottompoint[0], bottompoint[1] - ( (increaseht / 2) * sc ) ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((bottompoint[0] - ( (increasewt / 2) * sc ), bottompoint[1] - (ht / 2) ))
|
||||||
|
|
||||||
|
if c3 == 'true': # Drawing the NW Corner based on NE Coordinates
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((toppoint[0], toppoint[1] + ( (increaseht / 2) * sc ) ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((toppoint[0] - ( (increasewt / 2) * sc ), toppoint[1] + (ht / 2) ))
|
||||||
|
|
||||||
|
if c4 == 'true': # Drawing the NE Corner based on NW Coordinates
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((toppoint[0], toppoint[1] + ( (increaseht / 2) * sc ) ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((toppoint[0] + ( (increasewt / 2) * sc ), toppoint[1] + (ht / 2) ))
|
||||||
|
|
||||||
|
sc = sc - 1
|
||||||
|
|
||||||
|
if shape == "triangle":
|
||||||
|
# We draw the triangle and store the 3 corner points
|
||||||
|
# Loopable?
|
||||||
|
tur.backward((ht / 2))
|
||||||
|
tur.right(90)
|
||||||
|
tur.forward((wt /2))
|
||||||
|
cornera = tur.getpos()
|
||||||
|
if c3 == 'true' or c2 == 'true':
|
||||||
|
tur.pd()
|
||||||
|
tur.backward((wt))
|
||||||
|
cornerb = tur.getpos()
|
||||||
|
tur.pu()
|
||||||
|
if c2 == 'true' or c1 == 'true':
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((point[0], (cornera[1] - ht) ))
|
||||||
|
cornerc = tur.getpos()
|
||||||
|
tur.pu()
|
||||||
|
if c1 == 'true' or c3 == 'true':
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos(cornera)
|
||||||
|
|
||||||
|
# So.. The math below took a lot of trial and error to figure out...
|
||||||
|
# I probably need to take some geography classes...
|
||||||
|
|
||||||
|
while sc > 0:
|
||||||
|
if c1 == 'true':
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos(( (cornerb[0] + ((increasewt / 2) * (sc)) - (wt / 2)), cornerb[1] + (increaseht * sc) - ht ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos(( (cornera[0] + (increasewt / 2) * (sc)), cornera[1] - (increaseht * sc) ))
|
||||||
|
|
||||||
|
if c2 == 'true':
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((cornerb[0] - (increasewt * sc ) , cornerb[1] ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos(( (cornerb[0] + ((increasewt / 2) * sc) - (wt / 2)), cornerb[1] + (increaseht * sc) - ht ))
|
||||||
|
|
||||||
|
if c3 == 'true':
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((cornera[0] + (increasewt * sc ) , cornera[1] ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos(( (cornera[0] - ((increasewt / 2) * sc) + (wt / 2)), cornera[1] + (increaseht * sc) - ht ))
|
||||||
|
|
||||||
|
sc = sc - 1
|
||||||
|
|
||||||
|
|
||||||
|
if shape == "square":
|
||||||
|
# We draw out the square shape and store the coordinates for each corner
|
||||||
|
# Can this be looped?
|
||||||
|
tur.right(90)
|
||||||
|
tur.forward((wt / 2))
|
||||||
|
tur.right(90)
|
||||||
|
tur.forward((ht / 2))
|
||||||
|
swcorner = tur.getpos()
|
||||||
|
if c4 == 'true' or c3 == 'true': # We only draw the 2 lines that are part of these corners
|
||||||
|
tur.pd() # Pen Down
|
||||||
|
tur.right(90)
|
||||||
|
tur.forward(wt)
|
||||||
|
secorner = tur.getpos()
|
||||||
|
tur.pu()
|
||||||
|
if c3 == 'true' or c2 == 'true': # We only draw the 2 lines that are part of these corners
|
||||||
|
tur.pd()
|
||||||
|
tur.right(90)
|
||||||
|
tur.forward(ht)
|
||||||
|
necorner = tur.getpos()
|
||||||
|
tur.pu()
|
||||||
|
if c1 == 'true' or c2 == 'true': # We only draw the 2 lines that are part of these corners
|
||||||
|
tur.pd()
|
||||||
|
tur.right(90)
|
||||||
|
tur.forward(wt)
|
||||||
|
nwcorner = tur.getpos()
|
||||||
|
tur.right(90)
|
||||||
|
tur.pu()
|
||||||
|
if c4 == 'true' or c1 == 'true': # We only draw the 2 lines that are part of these corners
|
||||||
|
tur.pd()
|
||||||
|
tur.forward(ht)
|
||||||
|
|
||||||
|
while sc > 0:
|
||||||
|
if c1 == 'true':
|
||||||
|
# Drawing the NW Corner based on SW coordinates
|
||||||
|
# We always draw this corner
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((swcorner[0], swcorner[1] - ( increaseht * sc ) ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((swcorner[0] + ( increasewt * sc ), swcorner[1] - ht))
|
||||||
|
|
||||||
|
if c2 == 'true': # Drawing the NE Corner based on SE Coordinates
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((secorner[0], secorner[1] - ( increaseht * sc ) ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((secorner[0] - ( increasewt * sc ), secorner[1] - ht))
|
||||||
|
|
||||||
|
if c3 == 'true': # Drawing the SE Corner based on NE Coordinates
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((necorner[0], necorner[1] + ( increaseht * sc ) ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((necorner[0] - ( increasewt * sc ), necorner[1] + ht))
|
||||||
|
|
||||||
|
if c4 == 'true': # Drawing the SW Corner based on NW Coordinates
|
||||||
|
tur.pu()
|
||||||
|
tur.setpos((nwcorner[0], nwcorner[1] + ( increaseht * sc ) ))
|
||||||
|
tur.pd()
|
||||||
|
tur.setpos((nwcorner[0] + ( increasewt * sc ), nwcorner[1] + ht))
|
||||||
|
|
||||||
|
sc = sc - 1
|
||||||
|
|
||||||
|
return inkex.PathElement(d=tur.getPath(), style=str(style))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# execute only if run as a script
|
||||||
|
parabola().run()
|
@ -206,7 +206,7 @@ class PathOps(inkex.Effect):
|
|||||||
def get_selected_ids(self):
|
def get_selected_ids(self):
|
||||||
"""Return a list of valid ids for inkscape path operations."""
|
"""Return a list of valid ids for inkscape path operations."""
|
||||||
id_list = []
|
id_list = []
|
||||||
if not len(self.svg.selected):
|
if len(self.svg.selected) == 0:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# level = 0: unlimited recursion into groups
|
# level = 0: unlimited recursion into groups
|
||||||
@ -346,14 +346,14 @@ class PathOps(inkex.Effect):
|
|||||||
defs = get_defs(self.document.getroot())
|
defs = get_defs(self.document.getroot())
|
||||||
inkscape_tagrefs = defs.findall(
|
inkscape_tagrefs = defs.findall(
|
||||||
"inkscape:tag/inkscape:tagref", namespaces=inkex.NSS)
|
"inkscape:tag/inkscape:tagref", namespaces=inkex.NSS)
|
||||||
return True if len(inkscape_tagrefs) else False
|
return len(inkscape_tagrefs) > 0
|
||||||
|
|
||||||
def update_tagrefs(self, mode='purge'):
|
def update_tagrefs(self, mode='purge'):
|
||||||
"""Check tagrefs for deleted objects."""
|
"""Check tagrefs for deleted objects."""
|
||||||
defs = get_defs(self.document.getroot())
|
defs = get_defs(self.document.getroot())
|
||||||
inkscape_tagrefs = defs.findall(
|
inkscape_tagrefs = defs.findall(
|
||||||
"inkscape:tag/inkscape:tagref", namespaces=inkex.NSS)
|
"inkscape:tag/inkscape:tagref", namespaces=inkex.NSS)
|
||||||
if len(inkscape_tagrefs):
|
if len(inkscape_tagrefs) > 0:
|
||||||
for tagref in inkscape_tagrefs:
|
for tagref in inkscape_tagrefs:
|
||||||
href = tagref.get(inkex.addNS('href', 'xlink'))[1:]
|
href = tagref.get(inkex.addNS('href', 'xlink'))[1:]
|
||||||
if self.svg.getElementById(href) is None:
|
if self.svg.getElementById(href) is None:
|
||||||
|
@ -20,8 +20,11 @@
|
|||||||
<param name="decimals" type="int" min="0" max="10" gui-text="Decimal tolerance" gui-description="The more decimals the more distinct layers you will get. This only applies for the sub layers (threshold > 1)">1</param>
|
<param name="decimals" type="int" min="0" max="10" gui-text="Decimal tolerance" gui-description="The more decimals the more distinct layers you will get. This only applies for the sub layers (threshold > 1)">1</param>
|
||||||
<param name="apply_transformations" type="bool" gui-text="Apply transformations (requires separate extension)" gui-description="This will call the extension 'Apply Transformations'. Helps avoiding geometry shifting">false</param>
|
<param name="apply_transformations" type="bool" gui-text="Apply transformations (requires separate extension)" gui-description="This will call the extension 'Apply Transformations'. Helps avoiding geometry shifting">false</param>
|
||||||
<param name="cleanup" type="bool" gui-text="Cleanup all unused groups/layers (requires separate extension)" gui-description="This will call the extension 'Remove Empty Groups' if available">true</param>
|
<param name="cleanup" type="bool" gui-text="Cleanup all unused groups/layers (requires separate extension)" gui-description="This will call the extension 'Remove Empty Groups' if available">true</param>
|
||||||
|
<param name="put_unfiltered" type="bool" gui-text="Put unfiltered elements to a separate layer">false</param>
|
||||||
<label>This extension will re-layer your selected items or the whole document according to their style (stroke or fill)</label>
|
<param name="show_info" type="bool" gui-text="Show elements which have no style attributes to filter">false</param>
|
||||||
|
<spacer/>
|
||||||
|
<label>This extension will re-layer your selected items or the whole document according to their style values (stroke or fill).</label>
|
||||||
|
<label>The filtering applies only to style attribute of the elements. It does not filter for stroke or fill if they are set separately. You can use the separate 'Cleanup Styles' extension to migrate these separated attributes into style attribute.</label>
|
||||||
<label>Tinkered by Mario Voigt / Stadtfabrikanten e.V. (2020)</label>
|
<label>Tinkered by Mario Voigt / Stadtfabrikanten e.V. (2020)</label>
|
||||||
<label appearance="url">https://fablabchemnitz.de</label>
|
<label appearance="url">https://fablabchemnitz.de</label>
|
||||||
<effect>
|
<effect>
|
||||||
|
@ -8,7 +8,7 @@ Features
|
|||||||
Author: Mario Voigt / FabLab Chemnitz
|
Author: Mario Voigt / FabLab Chemnitz
|
||||||
Mail: mario.voigt@stadtfabrikanten.org
|
Mail: mario.voigt@stadtfabrikanten.org
|
||||||
Date: 19.08.2020
|
Date: 19.08.2020
|
||||||
Last patch: 05.04.2021
|
Last patch: 11.04.2021
|
||||||
License: GNU GPL v3
|
License: GNU GPL v3
|
||||||
"""
|
"""
|
||||||
import inkex
|
import inkex
|
||||||
@ -40,23 +40,17 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
return layer
|
return layer
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
inkex.Effect.__init__(self)
|
inkex.EffectExtension.__init__(self)
|
||||||
self.arg_parser.add_argument("--apply_transformations", type=inkex.Boolean, default=False, help="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting")
|
self.arg_parser.add_argument("--apply_transformations", type=inkex.Boolean, default=False, help="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting")
|
||||||
self.arg_parser.add_argument("--separateby", default = "stroke", help = "Separate by")
|
self.arg_parser.add_argument("--separateby", default = "stroke", help = "Separate by")
|
||||||
self.arg_parser.add_argument("--parsecolors",default = "hexval", help = "Sort colors by")
|
self.arg_parser.add_argument("--parsecolors",default = "hexval", help = "Sort colors by")
|
||||||
self.arg_parser.add_argument("--subdividethreshold", type=int, default = 1, help = "Threshold for splitting into sub layers")
|
self.arg_parser.add_argument("--subdividethreshold", type=int, default = 1, help = "Threshold for splitting into sub layers")
|
||||||
self.arg_parser.add_argument("--decimals", type=int, default = 1, help = "Decimal tolerance")
|
self.arg_parser.add_argument("--decimals", type=int, default = 1, help = "Decimal tolerance")
|
||||||
self.arg_parser.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Decimal tolerance")
|
self.arg_parser.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Decimal tolerance")
|
||||||
|
self.arg_parser.add_argument("--put_unfiltered", type=inkex.Boolean, default = False, help = "Put unfiltered elements to a separate layer")
|
||||||
|
self.arg_parser.add_argument("--show_info", type=inkex.Boolean, default = False, help = "Show elements which have no style attributes to filter")
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
applyTransformAvailable = False # at first we apply external extension
|
|
||||||
try:
|
|
||||||
import applytransform
|
|
||||||
applyTransformAvailable = True
|
|
||||||
except Exception as e:
|
|
||||||
# inkex.utils.debug(e)
|
|
||||||
inkex.utils.debug("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
|
|
||||||
|
|
||||||
|
|
||||||
def colorsort(stroke_value): #this function applies to stroke or fill (hex colors)
|
def colorsort(stroke_value): #this function applies to stroke or fill (hex colors)
|
||||||
if self.options.parsecolors == "hexval":
|
if self.options.parsecolors == "hexval":
|
||||||
@ -69,6 +63,14 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
return float(Color(stroke_value).to_hsl()[2])
|
return float(Color(stroke_value).to_hsl()[2])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
applyTransformAvailable = False # at first we apply external extension
|
||||||
|
try:
|
||||||
|
import applytransform
|
||||||
|
applyTransformAvailable = True
|
||||||
|
except Exception as e:
|
||||||
|
# inkex.utils.debug(e)
|
||||||
|
inkex.utils.debug("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
|
||||||
|
|
||||||
layer_name = None
|
layer_name = None
|
||||||
layerNodeList = [] #list with layer, neutral_value, element and self.options.separateby type
|
layerNodeList = [] #list with layer, neutral_value, element and self.options.separateby type
|
||||||
selected = [] #list of items to parse
|
selected = [] #list of items to parse
|
||||||
@ -80,107 +82,123 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
selected = self.svg.selected.values()
|
selected = self.svg.selected.values()
|
||||||
|
|
||||||
for element in selected:
|
for element in selected:
|
||||||
|
|
||||||
|
# additional option to apply transformations. As we clear up some groups to form new layers, we might lose translations, rotations, etc.
|
||||||
if self.options.apply_transformations is True and applyTransformAvailable is True:
|
if self.options.apply_transformations is True and applyTransformAvailable is True:
|
||||||
applytransform.ApplyTransform().recursiveFuseTransform(element)
|
applytransform.ApplyTransform().recursiveFuseTransform(element)
|
||||||
style = element.get('style')
|
|
||||||
if style is not None:
|
|
||||||
#if no style attributes or stroke/fill are set as extra attribute
|
|
||||||
stroke = element.get('stroke')
|
|
||||||
stroke_width = element.get('stroke-width')
|
|
||||||
stroke_opacity = element.get('stroke-opacity')
|
|
||||||
fill = element.get('fill')
|
|
||||||
fill_opacity = element.get('fill-opacity')
|
|
||||||
|
|
||||||
# possible values for fill are #HEXCOLOR (like #000000), color name (like purple, black, red) or gradients (URLs)
|
if isinstance(element, inkex.ShapeElement): # Elements which have a visible representation on the canvas (even without a style attribute but by their type); if we do not use that ifInstance Filter we provokate unkown InkScape fatal crashes
|
||||||
|
|
||||||
neutral_value = None #we will use this value to slice the filter result into sub layers (threshold)
|
style = element.get('style')
|
||||||
|
if style is not None:
|
||||||
|
#if no style attributes or stroke/fill are set as extra attribute
|
||||||
|
stroke = element.get('stroke')
|
||||||
|
stroke_width = element.get('stroke-width')
|
||||||
|
stroke_opacity = element.get('stroke-opacity')
|
||||||
|
fill = element.get('fill')
|
||||||
|
fill_opacity = element.get('fill-opacity')
|
||||||
|
|
||||||
if fill is not None:
|
# possible values for fill are #HEXCOLOR (like #000000), color name (like purple, black, red) or gradients (URLs)
|
||||||
style = 'fill:'+ fill + ";"
|
|
||||||
if stroke is not None:
|
|
||||||
style = style + 'stroke:' + stroke + ";"
|
|
||||||
|
|
||||||
#we don't want to destroy elements with gradients (they contain svg:stop elements which have a style too) and we don't want to mess with tspans (text)
|
neutral_value = None #we will use this value to slice the filter result into sub layers (threshold)
|
||||||
#the Styles to Layers extension still might brick the gradients (some tests failed)
|
|
||||||
if style and element.tag != inkex.addNS('stop','svg') and element.tag != inkex.addNS('tspan','svg'):
|
if fill is not None:
|
||||||
|
style = 'fill:'+ fill + ";"
|
||||||
|
if stroke is not None:
|
||||||
|
style = style + 'stroke:' + stroke + ";"
|
||||||
|
|
||||||
|
#we don't want to destroy elements with gradients (they contain svg:stop elements which have a style too) and we don't want to mess with tspans (text)
|
||||||
|
#the Styles to Layers extension still might brick the gradients (some tests failed)
|
||||||
|
if style and element.tag != inkex.addNS('stop','svg') and element.tag != inkex.addNS('tspan','svg'):
|
||||||
|
|
||||||
|
if self.options.separateby == "stroke":
|
||||||
|
stroke = re.search('(;|^)stroke:(.*?)(;|$)', style)
|
||||||
|
if stroke is not None:
|
||||||
|
stroke = stroke[0]
|
||||||
|
stroke_value = stroke.split("stroke:")[1].split(";")[0]
|
||||||
|
if stroke_value != "none":
|
||||||
|
stroke_converted = str(Color(stroke_value).to_rgb()) #the color can be hex code or clear name. we handle both the same
|
||||||
|
neutral_value = colorsort(stroke_converted)
|
||||||
|
layer_name = "stroke-" + self.options.parsecolors + "-" + stroke_converted
|
||||||
|
else:
|
||||||
|
layer_name = "stroke-" + self.options.parsecolors + "-none"
|
||||||
|
|
||||||
|
elif self.options.separateby == "stroke_width":
|
||||||
|
stroke_width = re.search('stroke-width:(.*?)(;|$)', style)
|
||||||
|
if stroke_width is not None:
|
||||||
|
stroke_width = stroke_width[0]
|
||||||
|
neutral_value = self.svg.unittouu(stroke_width.split("stroke-width:")[1].split(";")[0])
|
||||||
|
layer_name = stroke_width
|
||||||
|
else:
|
||||||
|
layer_name = "stroke-width-none"
|
||||||
|
|
||||||
|
elif self.options.separateby == "stroke_hairline":
|
||||||
|
stroke_hairline = re.search('-inkscape-stroke:hairline(;|$)', style)
|
||||||
|
if stroke_hairline is not None:
|
||||||
|
neutral_value = 1
|
||||||
|
layer_name = "stroke-hairline-yes"
|
||||||
|
else:
|
||||||
|
neutral_value = 0
|
||||||
|
layer_name = "stroke-hairline-no"
|
||||||
|
|
||||||
|
elif self.options.separateby == "stroke_opacity":
|
||||||
|
stroke_opacity = re.search('stroke-opacity:(.*?)(;|$)', style)
|
||||||
|
if stroke_opacity is not None:
|
||||||
|
stroke_opacity = stroke_opacity[0]
|
||||||
|
neutral_value = float(stroke_opacity.split("stroke-opacity:")[1].split(";")[0])
|
||||||
|
layer_name = stroke_opacity
|
||||||
|
else:
|
||||||
|
layer_name = "stroke-opacity-none"
|
||||||
|
|
||||||
|
elif self.options.separateby == "fill":
|
||||||
|
fill = re.search('fill:(.*?)(;|$)', style)
|
||||||
|
if fill is not None:
|
||||||
|
fill = fill[0]
|
||||||
|
fill_value = fill.split("fill:")[1].split(";")[0]
|
||||||
|
#check if the fill color is a real color or a gradient. if it's a gradient we skip the element
|
||||||
|
if fill_value != "none" and "url" not in fill_value:
|
||||||
|
fill_converted = str(Color(fill_value).to_rgb()) #the color can be hex code or clear name. we handle both the same
|
||||||
|
neutral_value = colorsort(fill_converted)
|
||||||
|
layer_name = "fill-" + self.options.parsecolors + "-" + fill_converted
|
||||||
|
elif "url" in fill_value: #okay we found a gradient. we put it to some group
|
||||||
|
layer_name = "fill-" + self.options.parsecolors + "-gradient"
|
||||||
|
else:
|
||||||
|
layer_name = "fill-" + self.options.parsecolors + "-none"
|
||||||
|
|
||||||
|
elif self.options.separateby == "fill_opacity":
|
||||||
|
fill_opacity = re.search('fill-opacity:(.*?)(;|$)', style)
|
||||||
|
if fill_opacity is not None:
|
||||||
|
fill_opacity = fill_opacity[0]
|
||||||
|
neutral_value = float(fill_opacity.split("fill-opacity:")[1].split(";")[0])
|
||||||
|
layer_name = fill_opacity
|
||||||
|
else:
|
||||||
|
layer_name = "fill-opacity-none"
|
||||||
|
|
||||||
if self.options.separateby == "stroke":
|
|
||||||
stroke = re.search('(;|^)stroke:(.*?)(;|$)', style)
|
|
||||||
if stroke is not None:
|
|
||||||
stroke = stroke[0]
|
|
||||||
stroke_value = stroke.split("stroke:")[1].split(";")[0]
|
|
||||||
if stroke_value != "none":
|
|
||||||
stroke_converted = str(Color(stroke_value).to_rgb()) #the color can be hex code or clear name. we handle both the same
|
|
||||||
neutral_value = colorsort(stroke_converted)
|
|
||||||
layer_name = "stroke-" + self.options.parsecolors + "-" + stroke_converted
|
|
||||||
else:
|
else:
|
||||||
layer_name = "stroke-" + self.options.parsecolors + "-none"
|
inkex.utils.debug("No proper option selected.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
elif self.options.separateby == "stroke_width":
|
if neutral_value is not None: #apply decimals filter
|
||||||
stroke_width = re.search('stroke-width:(.*?)(;|$)', style)
|
neutral_value = float(round(neutral_value, self.options.decimals))
|
||||||
if stroke_width is not None:
|
|
||||||
stroke_width = stroke_width[0]
|
|
||||||
neutral_value = self.svg.unittouu(stroke_width.split("stroke-width:")[1].split(";")[0])
|
|
||||||
layer_name = stroke_width
|
|
||||||
else:
|
|
||||||
layer_name = "stroke-width-none"
|
|
||||||
|
|
||||||
elif self.options.separateby == "stroke_hairline":
|
if layer_name is not None:
|
||||||
stroke_hairline = re.search('-inkscape-stroke:hairline(;|$)', style)
|
layer_name = layer_name.split(";")[0] #cut off existing semicolons to avoid duplicated layers with/without semicolon
|
||||||
if stroke_hairline is not None:
|
currentLayer = self.findLayer(layer_name)
|
||||||
neutral_value = 1
|
if currentLayer is None: #layer does not exist, so create a new one
|
||||||
layer_name = "stroke-hairline-yes"
|
layerNodeList.append([self.createLayer(layerNodeList, layer_name), neutral_value, element, self.options.separateby])
|
||||||
else:
|
else:
|
||||||
neutral_value = 0
|
layerNodeList.append([currentLayer, neutral_value, element, self.options.separateby]) #layer is existent. append items to this later
|
||||||
layer_name = "stroke-hairline-no"
|
else: #if no style attribute in element and not a group
|
||||||
|
if isinstance(element, inkex.Group) is False:
|
||||||
elif self.options.separateby == "stroke_opacity":
|
if self.options.show_info:
|
||||||
stroke_opacity = re.search('stroke-opacity:(.*?)(;|$)', style)
|
inkex.utils.debug(element.get('id') + ' has no style attribute')
|
||||||
if stroke_opacity is not None:
|
if self.options.put_unfiltered:
|
||||||
stroke_opacity = stroke_opacity[0]
|
layer_name = 'without-style-attribute'
|
||||||
neutral_value = float(stroke_opacity.split("stroke-opacity:")[1].split(";")[0])
|
currentLayer = self.findLayer(layer_name)
|
||||||
layer_name = stroke_opacity
|
if currentLayer is None: #layer does not exist, so create a new one
|
||||||
else:
|
layerNodeList.append([self.createLayer(layerNodeList, layer_name), None, element, None])
|
||||||
layer_name = "stroke-opacity-none"
|
else:
|
||||||
|
layerNodeList.append([currentLayer, None, element, None]) #layer is existent. append items to this later
|
||||||
elif self.options.separateby == "fill":
|
|
||||||
fill = re.search('fill:(.*?)(;|$)', style)
|
|
||||||
if fill is not None:
|
|
||||||
fill = fill[0]
|
|
||||||
fill_value = fill.split("fill:")[1].split(";")[0]
|
|
||||||
#check if the fill color is a real color or a gradient. if it's a gradient we skip the element
|
|
||||||
if fill_value != "none" and "url" not in fill_value:
|
|
||||||
fill_converted = str(Color(fill_value).to_rgb()) #the color can be hex code or clear name. we handle both the same
|
|
||||||
neutral_value = colorsort(fill_converted)
|
|
||||||
layer_name = "fill-" + self.options.parsecolors + "-" + fill_converted
|
|
||||||
elif "url" in fill_value: #okay we found a gradient. we put it to some group
|
|
||||||
layer_name = "fill-" + self.options.parsecolors + "-gradient"
|
|
||||||
else:
|
|
||||||
layer_name = "fill-" + self.options.parsecolors + "-none"
|
|
||||||
|
|
||||||
elif self.options.separateby == "fill_opacity":
|
|
||||||
fill_opacity = re.search('fill-opacity:(.*?)(;|$)', style)
|
|
||||||
if fill_opacity is not None:
|
|
||||||
fill_opacity = fill_opacity[0]
|
|
||||||
neutral_value = float(fill_opacity.split("fill-opacity:")[1].split(";")[0])
|
|
||||||
layer_name = fill_opacity
|
|
||||||
else:
|
|
||||||
layer_name = "fill-opacity-none"
|
|
||||||
|
|
||||||
else:
|
|
||||||
inkex.utils.debug("No proper option selected.")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if neutral_value is not None: #apply decimals filter
|
|
||||||
neutral_value = float(round(neutral_value, self.options.decimals))
|
|
||||||
|
|
||||||
if layer_name is not None:
|
|
||||||
layer_name = layer_name.split(";")[0] #cut off existing semicolons to avoid duplicated layers with/without semicolon
|
|
||||||
currentLayer = self.findLayer(layer_name)
|
|
||||||
if currentLayer is None: #layer does not exist, so create a new one
|
|
||||||
layerNodeList.append([self.createLayer(layerNodeList, layer_name), neutral_value, element, self.options.separateby])
|
|
||||||
else:
|
|
||||||
layerNodeList.append([currentLayer, neutral_value, element, self.options.separateby]) #layer is existent. append items to this later
|
|
||||||
|
|
||||||
contentlength = 0 #some counter to track if there are layers inside or if it is just a list with empty children
|
contentlength = 0 #some counter to track if there are layers inside or if it is just a list with empty children
|
||||||
for layerNode in layerNodeList:
|
for layerNode in layerNodeList:
|
||||||
|
@ -340,8 +340,8 @@ class vpypetools (inkex.EffectExtension):
|
|||||||
# save the vpype document to new svg file and close it afterwards
|
# save the vpype document to new svg file and close it afterwards
|
||||||
output_file = self.options.input_file + ".vpype.svg"
|
output_file = self.options.input_file + ".vpype.svg"
|
||||||
output_fileIO = open(output_file, "w", encoding="utf-8")
|
output_fileIO = open(output_file, "w", encoding="utf-8")
|
||||||
vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer', single_path = True)
|
#vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer', single_path = True)
|
||||||
#vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer')
|
vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer')
|
||||||
#vpype.write_svg(output_fileIO, doc, page_size=(self.svg.unittouu(self.document.getroot().get('width')), self.svg.unittouu(self.document.getroot().get('height'))), center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer')
|
#vpype.write_svg(output_fileIO, doc, page_size=(self.svg.unittouu(self.document.getroot().get('width')), self.svg.unittouu(self.document.getroot().get('height'))), center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer')
|
||||||
output_fileIO.close()
|
output_fileIO.close()
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user