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
|
||||
# https://github.com/Klowner/inkscape-applytransforms
|
||||
#
|
||||
import inkex
|
||||
import copy
|
||||
import math
|
||||
from lxml import etree
|
||||
import inkex
|
||||
from inkex.paths import CubicSuperPath, Path
|
||||
from inkex.transforms import Transform
|
||||
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]]):
|
||||
|
||||
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:
|
||||
del node.attrib['transform']
|
||||
@ -139,10 +141,56 @@ class ApplyTransform(inkex.EffectExtension):
|
||||
else:
|
||||
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'),
|
||||
inkex.addNS('text', 'svg'),
|
||||
inkex.addNS('image', 'svg'),
|
||||
inkex.addNS('use', 'svg')]:
|
||||
inkex.addNS('image', 'svg')]:
|
||||
inkex.utils.errormsg(
|
||||
"Shape %s (%s) not yet supported, try Object to path first"
|
||||
% (node.TAG, node.get("id"))
|
||||
|
@ -2,20 +2,43 @@
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Cleanup Styles</name>
|
||||
<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="stroke_units" gui-text="Units" type="optiongroup" appearance="combo">
|
||||
<param name="main_tabs" type="notebook">
|
||||
<page name="tab_active" gui-text="Cleanup Styles">
|
||||
<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="prefer_composed">Catch dedicated, but prefer composed (master) style</option>
|
||||
<option value="prefer_dedicated">Catch dedicated and prefer over composed (master) style</option>
|
||||
<option value="ignore">Ignore dedicated</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="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="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>
|
||||
<effect needs-live-preview="true">
|
||||
<object-type>all</object-type>
|
||||
<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
|
||||
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 re
|
||||
|
||||
class Cleanup(inkex.EffectExtension):
|
||||
|
||||
groups = []
|
||||
|
||||
def __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("--stroke_units", default="mm", help="Stroke unit")
|
||||
self.arg_parser.add_argument("--opacity", type=float, default="100.0", help="Opacity")
|
||||
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("--reset_fill_attributes", type=inkex.Boolean, help="Sets 'fill:none;' to style attribute")
|
||||
self.arg_parser.add_argument("--main_tabs")
|
||||
self.arg_parser.add_argument("--dedicated_style_attributes", default="ignore", help="Handling of dedicated style attributes")
|
||||
self.arg_parser.add_argument("--stroke_width_override", type=inkex.Boolean, default=False, help="Override stroke width")
|
||||
self.arg_parser.add_argument("--stroke_width", type=float, default=0.100, help="Stroke width")
|
||||
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_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):
|
||||
if len(self.svg.selected) == 0:
|
||||
@ -40,6 +57,11 @@ class Cleanup(inkex.EffectExtension):
|
||||
else:
|
||||
for id, node in self.svg.selected.items():
|
||||
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):
|
||||
self.changeStyle(node)
|
||||
@ -48,28 +70,55 @@ 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
|
||||
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'):
|
||||
|
||||
#we check/modify the style of all shapes (not groups)
|
||||
if isinstance(node, inkex.ShapeElement) and not isinstance(node, inkex.Group):
|
||||
# the final styles applied to this element (with influence from top level elements like groups)
|
||||
composed_style = node.composed_style()
|
||||
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]})
|
||||
|
||||
#three options to handle dedicated attributes (attributes not in the "style" attribute, but separate):
|
||||
# - 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)
|
||||
|
||||
#inkex.utils.debug("composedStyleAttributesDict = " + str(composedStyleAttributesDict))
|
||||
#inkex.utils.debug("dedicatedStyleAttributesDict = " + str(dedicatedStyleAttributesDict))
|
||||
|
||||
if self.options.dedicated_style_attributes == 'prefer_dedicated':
|
||||
composedStyleAttributesDict.update(dedicatedStyleAttributesDict)
|
||||
node.set('style', composedStyleAttributesDict)
|
||||
elif self.options.dedicated_style_attributes == 'prefer_composed':
|
||||
dedicatedStyleAttributesDict.update(composedStyleAttributesDict)
|
||||
node.set('style', dedicatedStyleAttributesDict)
|
||||
elif self.options.dedicated_style_attributes == 'ignore':
|
||||
pass
|
||||
|
||||
# now parse the style with possibly merged dedicated attributes modded style attribute (dedicatedStyleAttributes)
|
||||
if node.attrib.has_key('style') is False:
|
||||
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
|
||||
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.stroke_width_override is True and "stroke-width:" not in style:
|
||||
style += 'stroke-width:{:1.4f};'.format(self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_width_units))
|
||||
if self.options.stroke_opacity_override is True and "stroke-opacity:" not in style:
|
||||
style += 'stroke-opacity:{:1.1f};'.format(self.options.stroke_opacity / 100)
|
||||
|
||||
if self.options.apply_hairlines is True:
|
||||
if "vector-effect:non-scaling-stroke" not in style:
|
||||
@ -87,13 +136,13 @@ class Cleanup(inkex.EffectExtension):
|
||||
if len(parts) == 2:
|
||||
(prop, val) = parts
|
||||
prop = prop.strip().lower()
|
||||
if prop == 'stroke-width':
|
||||
new_val = self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_units)
|
||||
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':
|
||||
new_val = self.options.opacity / 100
|
||||
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_style_attributes is True:
|
||||
if self.options.reset_stroke_attributes is True:
|
||||
if prop == 'stroke-dasharray':
|
||||
declarations[i] = ''
|
||||
if prop == 'stroke-dashoffset':
|
||||
@ -113,7 +162,14 @@ class Cleanup(inkex.EffectExtension):
|
||||
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__':
|
||||
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>
|
||||
<separator />
|
||||
<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 appearance="url">https://fablabchemnitz.de</label>
|
||||
<label>License: GNU GPL v3</label>
|
||||
|
@ -39,19 +39,28 @@
|
||||
<separator/>
|
||||
<vbox>
|
||||
<label appearance="header">Objects / Misc</label>
|
||||
<hbox>
|
||||
<vbox>
|
||||
<!--<param name="svg" type="bool" gui-text="svg">true</param>-->
|
||||
<!--<param name="sodipodi" type="bool" gui-text="sodipodi">true</param>-->
|
||||
<param name="clipPath" type="bool" gui-text="clipPath">true</param>
|
||||
<param name="defs" type="bool" gui-text="defs">true</param>
|
||||
<param name="image" type="bool" gui-text="image">true</param>
|
||||
<param name="mask" type="bool" gui-text="mask">true</param>
|
||||
<param name="marker" type="bool" gui-text="marker">true</param>
|
||||
<param name="metadata" type="bool" gui-text="metadata">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>
|
||||
</hbox>
|
||||
<separator/>
|
||||
<param name="operationmode" type="optiongroup" appearance="combo" gui-text="Operation mode">
|
||||
<option value="ungroup_only">Ungroup (flatten) only</option>
|
||||
|
@ -47,6 +47,7 @@ class MigrateGroups(inkex.Effect):
|
||||
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("--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("--meshRow", 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("--symbol", 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("--flowRoot", 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}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}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}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}flowRoot") if self.options.flowRoot 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):
|
||||
"""Return a list of valid ids for inkscape path operations."""
|
||||
id_list = []
|
||||
if not len(self.svg.selected):
|
||||
if len(self.svg.selected) == 0:
|
||||
pass
|
||||
else:
|
||||
# level = 0: unlimited recursion into groups
|
||||
@ -346,14 +346,14 @@ class PathOps(inkex.Effect):
|
||||
defs = get_defs(self.document.getroot())
|
||||
inkscape_tagrefs = defs.findall(
|
||||
"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'):
|
||||
"""Check tagrefs for deleted objects."""
|
||||
defs = get_defs(self.document.getroot())
|
||||
inkscape_tagrefs = defs.findall(
|
||||
"inkscape:tag/inkscape:tagref", namespaces=inkex.NSS)
|
||||
if len(inkscape_tagrefs):
|
||||
if len(inkscape_tagrefs) > 0:
|
||||
for tagref in inkscape_tagrefs:
|
||||
href = tagref.get(inkex.addNS('href', 'xlink'))[1:]
|
||||
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="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>
|
||||
|
||||
<label>This extension will re-layer your selected items or the whole document according to their style (stroke or fill)</label>
|
||||
<param name="put_unfiltered" type="bool" gui-text="Put unfiltered elements to a separate layer">false</param>
|
||||
<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 appearance="url">https://fablabchemnitz.de</label>
|
||||
<effect>
|
||||
|
@ -8,7 +8,7 @@ Features
|
||||
Author: Mario Voigt / FabLab Chemnitz
|
||||
Mail: mario.voigt@stadtfabrikanten.org
|
||||
Date: 19.08.2020
|
||||
Last patch: 05.04.2021
|
||||
Last patch: 11.04.2021
|
||||
License: GNU GPL v3
|
||||
"""
|
||||
import inkex
|
||||
@ -40,23 +40,17 @@ class StylesToLayers(inkex.EffectExtension):
|
||||
return layer
|
||||
|
||||
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("--separateby", default = "stroke", help = "Separate 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("--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("--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):
|
||||
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)
|
||||
if self.options.parsecolors == "hexval":
|
||||
@ -69,6 +63,14 @@ class StylesToLayers(inkex.EffectExtension):
|
||||
return float(Color(stroke_value).to_hsl()[2])
|
||||
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
|
||||
layerNodeList = [] #list with layer, neutral_value, element and self.options.separateby type
|
||||
selected = [] #list of items to parse
|
||||
@ -80,8 +82,13 @@ class StylesToLayers(inkex.EffectExtension):
|
||||
selected = self.svg.selected.values()
|
||||
|
||||
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:
|
||||
applytransform.ApplyTransform().recursiveFuseTransform(element)
|
||||
|
||||
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
|
||||
|
||||
style = element.get('style')
|
||||
if style is not None:
|
||||
#if no style attributes or stroke/fill are set as extra attribute
|
||||
@ -181,6 +188,17 @@ class StylesToLayers(inkex.EffectExtension):
|
||||
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
|
||||
else: #if no style attribute in element and not a group
|
||||
if isinstance(element, inkex.Group) is False:
|
||||
if self.options.show_info:
|
||||
inkex.utils.debug(element.get('id') + ' has no style attribute')
|
||||
if self.options.put_unfiltered:
|
||||
layer_name = 'without-style-attribute'
|
||||
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), None, element, None])
|
||||
else:
|
||||
layerNodeList.append([currentLayer, None, element, None]) #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
|
||||
for layerNode in layerNodeList:
|
||||
|
@ -340,8 +340,8 @@ class vpypetools (inkex.EffectExtension):
|
||||
# save the vpype document to new svg file and close it afterwards
|
||||
output_file = self.options.input_file + ".vpype.svg"
|
||||
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')
|
||||
#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=(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()
|
||||
|
||||
|
Reference in New Issue
Block a user