diff --git a/extensions/fablabchemnitz/convert_vertical_horizontal_to_line/meta.json b/extensions/fablabchemnitz/convert_vertical_horizontal_to_line/meta.json index f7e2d23..ca40d1a 100644 --- a/extensions/fablabchemnitz/convert_vertical_horizontal_to_line/meta.json +++ b/extensions/fablabchemnitz/convert_vertical_horizontal_to_line/meta.json @@ -7,14 +7,14 @@ "original_name": "Convert Vertical/Horizontal To Line", "original_id": "fablabchemnitz.de.convert_vertical_horizontal_to_line", "license": "GNU GPL v3", - "license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/LICENSE", + "license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE", "comment": "Written by Mario Voigt", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/convert_vertical_horizontal_to_line", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/convert_vertical_horizontal_to_line", "fork_url": null, "documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=79626259", "inkscape_gallery_url": null, "main_authors": [ - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/dots_to_path_points/meta.json b/extensions/fablabchemnitz/dots_to_path_points/meta.json index bf945e1..9726991 100644 --- a/extensions/fablabchemnitz/dots_to_path_points/meta.json +++ b/extensions/fablabchemnitz/dots_to_path_points/meta.json @@ -9,13 +9,13 @@ "license": "GNU GPL v3", "license_url": "https://github.com/chrille69/Inkscape-Extension-pathpoints2dots/blob/master/LICENSE", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/dots_to_path_points", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/dots_to_path_points", "fork_url": "https://github.com/chrille69/Inkscape-Extension-pathpoints2dots", "documentation_url": "https://stadtfabrikanten.org/display/IFM/Dots+To+Path+Points", "inkscape_gallery_url": null, "main_authors": [ "github.com/chrille69", - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/edit_attributes/meta.json b/extensions/fablabchemnitz/edit_attributes/meta.json index a2f52ae..29fa2e5 100644 --- a/extensions/fablabchemnitz/edit_attributes/meta.json +++ b/extensions/fablabchemnitz/edit_attributes/meta.json @@ -9,13 +9,13 @@ "license": "GNU LGPL v2", "license_url": "https://inkscape.org/~MatheM/%E2%98%85simple-attribute-editor+1", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/edit_attributes", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/edit_attributes", "fork_url": "https://inkscape.org/~MatheM/%E2%98%85simple-attribute-editor+1", "documentation_url": "https://stadtfabrikanten.org/display/IFM/Edit+Attributes", "inkscape_gallery_url": null, "main_authors": [ "inkscape.org/MatheM", - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/filter_by_length_area/meta.json b/extensions/fablabchemnitz/filter_by_length_area/meta.json index f93f4d8..a48223e 100644 --- a/extensions/fablabchemnitz/filter_by_length_area/meta.json +++ b/extensions/fablabchemnitz/filter_by_length_area/meta.json @@ -10,14 +10,14 @@ "original_name": "Filter By Length/Area", "original_id": "com.filter_by_length_area", "license": "GNU GPL v3", - "license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/LICENSE", + "license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/filter_by_length_area", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/filter_by_length_area", "fork_url": null, "documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=74645969", "inkscape_gallery_url": null, "main_authors": [ - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/filter_to_layer/filter_to_layer.inx b/extensions/fablabchemnitz/filter_to_layer/filter_to_layer.inx new file mode 100644 index 0000000..96cab34 --- /dev/null +++ b/extensions/fablabchemnitz/filter_to_layer/filter_to_layer.inx @@ -0,0 +1,21 @@ + + + Filter To Layer + fablabchemnitz.de.filter_to_layer + + + + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/filter_to_layer/filter_to_layer.py b/extensions/fablabchemnitz/filter_to_layer/filter_to_layer.py new file mode 100644 index 0000000..05b5269 --- /dev/null +++ b/extensions/fablabchemnitz/filter_to_layer/filter_to_layer.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +''' +This extension adds filters to current layer or removes filters from current layer + +Copyright (C) 2012 Jabiertxo Arraiza, jabier.arraiza@marker.es + +Version 0.3 + +TODO: +Comment Better!!! + +CHANGE LOG +0.1 Start 30/07/2012 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +''' + +import inkex +import sys +import re + +class FilterToLayer(inkex.EffectExtension): + + def add_arguments(self, pars): + pars.add_argument('--type', default = 'Add', help = 'Add or remove filters to current layer') + + def selectTop(self): + selectedSorted = None + if self.svg.selected is not None: + for element in self.document.getroot().iter(): + if element.get("id") in self.svg.selection: + selectedSorted = element + return selectedSorted + + def effect(self): + svg = self.document.getroot() + typeOperation = self.options.type + xpathStr = '//sodipodi:namedview' + namedview = svg.xpath(xpathStr, namespaces=inkex.NSS) + idLayer = namedview[0].get('{http://www.inkscape.org/namespaces/inkscape}current-layer'); + xpathStr = '//svg:g[@id="'+idLayer+'"]' + layer = svg.xpath(xpathStr, namespaces=inkex.NSS) + + if typeOperation == "Add": #Add action + element = self.selectTop() + if element is not None: + if element.get('style'): + matchObj = re.search( r'filter:url\(#.*?[^\)]\)', element.get('style'), re.M|re.I) + if matchObj: + filter = matchObj.group() + element.set('style',element.get('style').replace(filter,"").replace(";;",";")) + if layer[0].get('style'): + matchObj = re.search( r'filter:url\(#.*?[^\)]\)', layer[0].get('style'), re.M|re.I) + if matchObj: + element.set('style',element.get('style').replace(matchObj.group(),"").replace(";;",";")) + style = layer[0].get('style')+ ";" + filter; + layer[0].set('style',style.replace(";;",";")) + else: + layer[0].set('style',filter) + else: + inkex.utils.debug("Nothing selected") + + else: #Remove action + if layer[0].get('style'): + matchObj = re.search( r'filter:url\(#.*?[^\)]\)', layer[0].get('style'), re.M|re.I) + if matchObj: + layer[0].set('style',layer[0].get('style').replace(matchObj.group(),"").replace(";;",";")) + +if __name__ == '__main__': + FilterToLayer().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/filter_to_layer/meta.json b/extensions/fablabchemnitz/filter_to_layer/meta.json new file mode 100644 index 0000000..9b46c7a --- /dev/null +++ b/extensions/fablabchemnitz/filter_to_layer/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "Filter To Layer", + "id": "fablabchemnitz.de.filter_to_layer", + "path": "filter_to_layer", + "dependent_extensions": null, + "original_name": "Filter to layer", + "original_id": "org.inkscape.filter.layer", + "license": "GNU GPL v2", + "license_url": "https://inkscape.org/~jabiertxof/%E2%98%85filter-layer", + "comment": "", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/filter_to_layer", + "fork_url": "https://inkscape.org/~jabiertxof/%E2%98%85filter-layer", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Filter+To+Layer", + "inkscape_gallery_url": null, + "main_authors": [ + "inkscape.org/jabiertxof", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/gpx_import/gpx_import.inx b/extensions/fablabchemnitz/gpx_import/gpx_import.inx new file mode 100644 index 0000000..b6bfd44 --- /dev/null +++ b/extensions/fablabchemnitz/gpx_import/gpx_import.inx @@ -0,0 +1,28 @@ + + + GPX Import + fablabchemnitz.de.gpx_import + + + 3000 + true + true + true + + + + + + + + + + .gpx + application/gpx+xml + GPS eXchange Format (*.gpx) + Import GPX Format + + + diff --git a/extensions/fablabchemnitz/gpx_import/gpx_import.py b/extensions/fablabchemnitz/gpx_import/gpx_import.py new file mode 100644 index 0000000..51f0f96 --- /dev/null +++ b/extensions/fablabchemnitz/gpx_import/gpx_import.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2012-2018 Tobias Leupold +# +# gpx2svg - Convert GPX formatted geodata to Scalable Vector Graphics (SVG) +# +# 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 in version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +# +# modified by monomono@disroot.org at 2019-08-05 for use on inkscape extension, +# 3to2 python converter and adjust parser_options for inkscape compatibility. + +from __future__ import division +from __future__ import absolute_import +from io import open +__version__ = u'@VERSION@' + +import argparse +import sys +import math +from xml.dom.minidom import parse as parseXml +from os.path import abspath + +def parseGpx(gpxFile): + u"""Get the latitude and longitude data of all track segments in a GPX file""" + + if gpxFile == u'/dev/stdin': + gpxFile = sys.stdin + + # Get the XML information + try: + gpx = parseXml(gpxFile) + except IOError as error: + print (sys.stderr, u'Error while reading file: {}. Terminating.'.format(error)) + sys.exit(1) + except: + print (sys.stderr, u'Error while parsing XML data:') + print (sys.stderr, sys.exc_info()) + print (sys.stderr, u'Terminating.') + sys.exit(1) + + # Iterate over all tracks, track segments and points + gpsData = [] + for track in gpx.getElementsByTagName(u'trk'): + for trackseg in track.getElementsByTagName(u'trkseg'): + trackSegData = [] + for point in trackseg.getElementsByTagName(u'trkpt'): + trackSegData.append( + (float(point.attributes[u'lon'].value), float(point.attributes[u'lat'].value)) + ) + # Leave out empty segments + if(trackSegData != []): + gpsData.append(trackSegData) + + return gpsData + +def calcProjection(gpsData): + u"""Calculate a plane projection for a GPS dataset""" + + projectedData = [] + for segment in gpsData: + projectedSegment = [] + for coord in segment: + # At the moment, we only have the Mercator projection + projectedSegment.append(mercatorProjection(coord)) + projectedData.append(projectedSegment) + + return projectedData + +def mercatorProjection(coord): + u"""Calculate the Mercator projection of a coordinate pair""" + + # Assuming we're on earth, we have (according to GRS 80): + r = 6378137.0 + + # As long as meridian = 0 and can't be changed, we don't need: + # meridian = meridian * math.pi / 180.0 + # x = r * ((coord[0] * math.pi / 180.0) - meridian) + + # Instead, we use this simplified version: + x = r * coord[0] * math.pi / 180.0 + y = r * math.log(math.tan((math.pi / 4.0) + ((coord[1] * math.pi / 180.0) / 2.0))) + return x, y + +def moveProjectedData(gpsData): + u"""Move a dataset to 0,0 and return it with the resulting width and height""" + + # Find the minimum and maximum x and y coordinates + minX = maxX = gpsData[0][0][0] + minY = maxY = gpsData[0][0][1] + for segment in gpsData: + for coord in segment: + if coord[0] < minX: + minX = coord[0] + if coord[0] > maxX: + maxX = coord[0] + if coord[1] < minY: + minY = coord[1] + if coord[1] > maxY: + maxY = coord[1] + + # Move the GPS data to 0,0 + movedGpsData = [] + for segment in gpsData: + movedSegment = [] + for coord in segment: + movedSegment.append((coord[0] - minX, coord[1] - minY)) + movedGpsData.append(movedSegment) + + # Return the moved data and it's width and height + return movedGpsData, maxX - minX, maxY - minY + +def searchCircularSegments(gpsData): + u"""Splits a GPS dataset to tracks that are circular and other tracks""" + + circularSegments = [] + straightSegments = [] + + for segment in gpsData: + if segment[0] == segment[len(segment) - 1]: + circularSegments.append(segment) + else: + straightSegments.append(segment) + + return circularSegments, straightSegments + +def combineSegmentPairs(gpsData): + u"""Combine segment pairs to one bigger segment""" + + combinedData = [] + + # Walk through the GPS data and search for segment pairs + # that end with the starting point of another track + while len(gpsData) > 0: + # Get one segment from the source GPS data + firstTrackData = gpsData.pop() + foundMatch = False + + # Try to find a matching segment + for i in xrange(len(gpsData)): + if firstTrackData[len(firstTrackData) - 1] == gpsData[i][0]: + # There is a matching segment, so break here + foundMatch = True + break + + if foundMatch == True: + # We found a pair of segments with one shared point, so pop the data of the second + # segment from the source GPS data and create a new segment containing all data, but + # without the overlapping point + firstTrackData.pop() + combinedData.append(firstTrackData + gpsData[i]) + gpsData.pop(i) + else: + # No segment with a shared point was found, so just append the data to the output + combinedData.append(firstTrackData) + + return searchCircularSegments(combinedData) + +def combineSegments(gpsData): + u"""Combine all segments of a GPS dataset that can be combined""" + + # Search for circular segments. We can't combine them with any other segment. + circularSegments, remainingSegments = searchCircularSegments(gpsData) + + # Search for segments that can be combined + while True: + # Look how many tracks we have now + segmentsBefore = len(remainingSegments) + + # Search for segments that can be combined + newCircularSegments, remainingSegments = combineSegmentPairs(remainingSegments) + + # Add newly found circular segments to processedSegments -- they can't be used anymore + circularSegments = circularSegments + newCircularSegments + + if segmentsBefore == len(remainingSegments): + # combineSegmentPairs() did not reduce the number of tracks anymore, + # so we can't combine more tracks and can stop here + break + + return circularSegments + remainingSegments + +def chronologyJoinSegments(gpsData): + u"""Join all segments to a big one in the order defined by the GPX file.""" + joinedSegment = [] + for segment in gpsData: + joinedSegment += segment + return [joinedSegment] + +def scaleCoords(coord, height, scale): + u"""Return a scaled pair of coordinates""" + return coord[0] * scale, (coord[1] * -1 + height) * scale + +def generateScaledSegment(segment, height, scale): + u"""Create the coordinate part of an SVG path string from a GPS data segment""" + for coord in segment: + yield scaleCoords(coord, height, scale) + +def writeSvgData(gpsData, width, height, maxPixels, dropSinglePoints, outfile): + u"""Output the SVG data -- quick 'n' dirty, without messing around with dom stuff ;-)""" + + # Calculate the scale factor we need to fit the requested maximal output size + if width <= maxPixels and height <= maxPixels: + scale = 1 + elif width > height: + scale = maxPixels / width + else: + scale = maxPixels / height + + # Open the requested output file or map to /dev/stdout + if outfile != u'/dev/stdout': + try: + fp = open(outfile, u'w') + except IOError as error: + print (sys.stderr, u"Can't open output file: {}. Terminating.".format(error)) + sys.exit(1) + else: + fp = sys.stdout + + # Header data + fp.write( u'\n') + fp.write((u'\n')) + fp.write( u'\n'.format(__version__)) + fp.write((u'\n').format(width * scale, height * scale)) + + # Process all track segments and generate ids and path drawing commands for them + + # First, we split the data to circular and straight segments + circularSegments, straightSegments = searchCircularSegments(gpsData) + realCircularSegments = [] + singlePoints = [] + for segment in circularSegments: + # We can leave out the last point, because it's equal to the first one + segment.pop() + if len(segment) == 1: + # It's a single point + if dropSinglePoints == False: + # We want to keep single points, so add it to singlePoints + singlePoints.append(segment) + else: + realCircularSegments.append(segment) + + circularSegments = realCircularSegments + + # Draw single points if requested + if len(singlePoints) > 0: + fp.write(u'\n') + for segment in singlePoints: + x, y = scaleCoords(segment[0], height, scale) + fp.write( + u'\n'.format(x, y) + ) + fp.write(u'\n') + + # Draw all circular segments + if len(circularSegments) > 0: + fp.write(u'\n') + for segment in circularSegments: + fp.write(u'\n') + fp.write(u'\n') + + # Draw all un-closed paths + if len(straightSegments) > 0: + fp.write(u'\n') + for segment in straightSegments: + fp.write(u'\n') + fp.write(u'\n') + + # Close the XML + fp.write(u'\n') + + # Close the file if necessary + if fp != sys.stdout: + fp.close() + +def main(): + # Setup the command line argument parser + cmdArgParser = argparse.ArgumentParser( + description = u'Convert GPX formatted geodata to Scalable Vector Graphics (SVG)', + epilog = u'gpx2svg {} - http://nasauber.de/opensource/gpx2svg/'.format(__version__) + ) + cmdArgParser.add_argument( + u'i', metavar = u'FILE', nargs = u'?', default = u'/dev/stdin', + help = u'GPX input file (default: read from STDIN)' + ) + cmdArgParser.add_argument( + u'--o', metavar = u'FILE', nargs = u'?', default = u'/dev/stdout', + help = u'SVG output file (default: write to STDOUT)' + ) + cmdArgParser.add_argument( + u'--m', metavar = u'PIXELS', nargs = u'?', type = int, default = 3000, + help = u'Maximum width or height of the SVG output in pixels (default: 3000)' + ) + cmdArgParser.add_argument( + u'--d', + help = u'Drop single points (default: draw a circle with 1px diameter)' + ) + cmdArgParser.add_argument( + u'--r', + help = (u'"Raw" conversion: Create one SVG path per track segment, don\'t try to combine ' + u'paths that end with the starting point of another path') + ) + cmdArgParser.add_argument( + u'--j', + help = (u'Join all segments to a big one in the order of the GPX file. This can create an ' + u'un-scattered path if the default combining algorithm does not work because there ' + u'are no matching points across segments (implies -r)') + ) + cmdArgParser.add_argument( + u'--tab', + help = (u'inkscape option') + ) + + + # Get the given arguments + cmdArgs = cmdArgParser.parse_args() + + # Map "-" to STDIN or STDOUT + if cmdArgs.i == u'-': + cmdArgs.i = u'/dev/stdin' + if cmdArgs.o == u'-': + cmdArgs.o = u'/dev/stdout' + + # Check if a given input or output file name is a relative representation of STDIN or STDOUT + if cmdArgs.i != u'/dev/stdin': + if abspath(cmdArgs.i) == u'/dev/stdin': + cmdArgs.i = u'/dev/stdin' + if cmdArgs.o != u'/dev/stdout': + if abspath(cmdArgs.o) == u'/dev/stdout': + cmdArgs.o = u'/dev/stdout' + + # Get the latitude and longitude data from the given GPX file or STDIN + gpsData = parseGpx(cmdArgs.i) + + # Check if we actually _have_ data + if gpsData == []: + print (sys.stderr, u'No data to convert. Terminating.') + sys.exit(1) + + # Join all segments if requested by "-j" + if bool(cmdArgs.j): + gpsData = chronologyJoinSegments(gpsData) + + # Try to combine all track segments that can be combined if not requested otherwise + # Don't execute if all segments are already joined with "-j" + if not bool(cmdArgs.r) and not bool(cmdArgs.j): + gpsData = combineSegments(gpsData) + + # Calculate a plane projection for a GPS dataset + # At the moment, we only have the Mercator projection + gpsData = calcProjection(gpsData) + + # Move the projected data to the 0,0 origin of a cartesial coordinate system + # and get the raw width and height of the resulting vector data + gpsData, width, height = moveProjectedData(gpsData) + + # Write the resulting SVG data to the requested output file or STDOUT + writeSvgData(gpsData, width, height, cmdArgs.m, bool(cmdArgs.d), cmdArgs.o) + +if __name__ == u'__main__': + main() diff --git a/extensions/fablabchemnitz/gpx_import/meta.json b/extensions/fablabchemnitz/gpx_import/meta.json new file mode 100644 index 0000000..0d42f3a --- /dev/null +++ b/extensions/fablabchemnitz/gpx_import/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "GPX Import", + "id": "fablabchemnitz.de.gpx_import", + "path": "gpx_import", + "dependent_extensions": null, + "original_name": "GPX Import", + "original_id": "mono.inkscape.pgx_input", + "license": "GNU GPL v2", + "license_url": "https://inkscape.org/de/~mono/%E2%98%85inkgpx2svg", + "comment": "", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/gpx_import", + "fork_url": "https://git.disroot.org/monomono/inkgpx2svg", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/GPX+Import", + "inkscape_gallery_url": null, + "main_authors": [ + "inkscape.org/mono", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/gradient_saver/GUI.glade b/extensions/fablabchemnitz/gradient_saver/GUI.glade new file mode 100644 index 0000000..31f26b0 --- /dev/null +++ b/extensions/fablabchemnitz/gradient_saver/GUI.glade @@ -0,0 +1,667 @@ + + + + + + False + Gradient Saver + False + center + 600 + 400 + icon.svg + north + + + + + + + True + True + + + + True + False + 400 + 250 + + + 600 + 180 + True + True + + + True + False + none + + + True + False + 30 + 10 + 2 + vertical + 20 + + + True + False + 10 + 18 + + + 150 + 42 + True + True + False + + + False + True + 0 + + + + + 325 + 42 + True + True + 25 + e.g Beautiful Color + + + False + True + 1 + + + + + False + False + 1 + + + + + + + + + 130 + + + + + 580 + 32 + True + False + 20 + 10 + end + + + Cancel + True + True + True + + + + True + True + 0 + + + + + Save + True + True + True + + + + True + True + 1 + + + + + 318 + + + + + 600 + 100 + True + False + 10 + top + + + 55 + True + False + 30 + 21 + gtk-info + 6 + + + False + True + 0 + + + + + 250 + True + False + 30 + 14 + <b>Hint:</b> +Select at least one object that use gradient color in order to add those gradient to your library. Selected gradient object will appear below. You can give a name for your gradients to help you recognize it later. + True + True + + + False + True + 1 + + + + + + + 550 + 20 + True + False + 45 + 50 + 10 + 100 + + + True + False + <b>Preview</b> + True + + + False + True + 0 + + + + + True + False + <b>Gradient Name</b> + True + + + False + True + 1 + + + + + 100 + + + + + + + True + False + Add New + + + False + + + + + True + False + + + 580 + 32 + True + False + 20 + 6 + bottom + end + + + Remove + True + True + True + True + + + + True + True + 0 + + + + + Load + True + True + True + True + + + + True + True + 1 + + + + + 318 + + + + + 600 + 100 + True + False + 10 + top + + + 55 + True + False + 30 + 21 + gtk-info + 6 + + + False + True + 0 + + + + + 250 + True + False + 30 + 14 + <b>Hint:</b> +Select at least one object that use gradient color in order to add those gradient to your library. Selected gradient object will appear below. You can give a name for your gradients to help you recognize it later. + True + True + + + False + True + 1 + + + + + + + 550 + 20 + True + False + 45 + 50 + 10 + 100 + + + True + False + <b>Your Current Gradient</b> + True + + + False + True + 0 + + + + + 100 + + + + + 600 + 180 + True + True + + + True + False + none + + + True + False + start + 10 + 10 + 30 + 10 + 2 + 2 + none + False + + + 60 + 42 + True + True + start + start + + + True + False + 5 + top + + + 42 + True + True + False + True + + + False + True + 0 + + + + + 100 + 42 + True + True + False + + + False + True + 1 + + + + + 42 + True + False + ijo trans + True + char + 25 + + + False + True + 2 + + + + + + + + + + + + + 130 + + + + + 1 + + + + + True + False + Load or Remove + + + 1 + False + + + + + True + False + + + 317 + 300 + True + False + <span font="12"><b>Version $VERSION</b></span> + True + True + + + 137 + -84 + + + + + 100 + 80 + True + False + icon.svg + + + 10 + 32 + + + + + 100 + 80 + True + False + <span font="12"><b>Gradient Saver</b></span> + True + + + 69 + 225 + + + + + 307 + 80 + True + False + <span>Gradient Saver is an extension for Inkscape that will help you to organize your favorite gradients. This extension created with love by <a href="#">Sofyan</a> &amp; <a href="https://raniaamina.id">Rania Amina</a> and fully supported by Gimpscape ID Community. + +Project Repository: +<a href="#"><b>https://github.com/artemtech/inkscape-gradient-saver</b></a></span> + True + True + + + 243 + 82 + + + + + 580 + 32 + True + False + 20 + 10 + end + + + Close + True + True + True + + + + True + True + 1 + + + + + 318 + + + + + 2 + + + + + True + False + About + + + 2 + False + + + + + + + False + Information + dialog-information + dialog + window + + + + + + False + 10 + 10 + 10 + 10 + vertical + 2 + + + False + 10 + 10 + center + + + Close + True + True + True + + + + True + True + 0 + + + + + False + False + 0 + + + + + True + False + + + 55 + True + False + gtk-info + 6 + + + False + True + 0 + + + + + True + False + label + + + False + True + 1 + + + + + False + True + 1 + + + + + + diff --git a/extensions/fablabchemnitz/gradient_saver/gradient_saver.inx b/extensions/fablabchemnitz/gradient_saver/gradient_saver.inx new file mode 100644 index 0000000..320813a --- /dev/null +++ b/extensions/fablabchemnitz/gradient_saver/gradient_saver.inx @@ -0,0 +1,16 @@ + + + Gradient Saver + fablabchemnitz.de.gradient_saver + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/gradient_saver/gradient_saver.py b/extensions/fablabchemnitz/gradient_saver/gradient_saver.py new file mode 100644 index 0000000..08589d5 --- /dev/null +++ b/extensions/fablabchemnitz/gradient_saver/gradient_saver.py @@ -0,0 +1,386 @@ +#! /usr/bin/env python3 + +# OS modules +import os +import sys + +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +# inkscape extension files +import cairo +from lxml import etree +import inkex # required +from inkex.elements import NSS +from inkex.utils import errormsg as show_errormsg +from inkex.styles import Style +from inkex.colors import Color + +__version__ = '1.0.0' + +saved_gradient_path = "../my-gradients.svg" + +def create_new_file(gradient_data): + root = etree.Element("svg", nsmap=NSS) + def_tree = etree.SubElement(root, "defs") + for i, item in enumerate(gradient_data): + gradient = etree.SubElement(def_tree, item.tag, attrib=item.attrib) + for j, gradient_stop in enumerate(item): + etree.SubElement(gradient, gradient_stop.tag, attrib=gradient_stop.attrib, id="stop%d%d" % (i, j)) + with open(saved_gradient_path, "w") as f: + f.write(etree.tostring( + root, encoding="utf-8", xml_declaration=True, pretty_print=True).decode("utf-8")) + +def save_to_file(data): + """ Wrapper for saving gradients to file. """ + if len(data) == 0: + return 1 + else: + try: + # read previous data then append it with current data + if os.path.exists(saved_gradient_path): + previous_data = load_gradients_from_file() + data = previous_data + data + create_new_file(data) + return 0 + except Exception as e: + import traceback + show_errormsg(e) + show_errormsg(traceback.print_exc()) + return -1 + +def load_gradients_from_file(): + """ Load gradients from saved gradient, returned as List """ + if os.path.exists(saved_gradient_path) and os.stat(saved_gradient_path).st_size != 0: + root = etree.parse(saved_gradient_path) + mygradients = root.xpath("//linearGradient", namespaces=NSS) + else: + mygradients = [] + return mygradients + +def read_stop_gradient(gradient): + stop_data = { + "id": gradient.attrib.get("id"), + "stops": [] + } + for stop in gradient: + offset = stop.attrib.get("offset") + style = Style(Style.parse_str(stop.attrib['style'])) + color = Color.parse_str(style.get("stop-color"))[1] + opacity = style.get("stop-opacity") + stop_data.get("stops").append( + tuple([float(offset)] + [x/256.0 for x in color] + [float(opacity)])) + return stop_data + +class MainWindow(Gtk.Builder): + def __init__(self, gradientSaver): + Gtk.Builder.__init__(self) + self.gradientSaver = gradientSaver + self.add_from_file("GUI.glade") + self.window = self.get_object("window") + self.app_version = self.get_object("extension_version") + self.app_version.set_label(self.app_version.get_label().replace("$VERSION",__version__)) + self.information_dialog = self.get_object("information_dialog") + # parsing components + # save gradient components + self.save_container = self.get_object("save_gradients_container") + save_template = self.get_object("save_gradient1") + self.save_container.remove(save_template) + for idx, item in enumerate(self.gradientSaver.doc_selected_gradients): + new_save_template = self.SaveGradientTemplate(item) + new_save_template.set_name("gradient%d" % idx) + self.save_container.add(new_save_template) + self.save_container.show_all() + # - end save gradient components + # load gradient components + self.load_container = self.get_object("load_gradients_container") + load_template = self.get_object("load_gradient1") + self.load_container.remove(load_template) + # - end load gradient components + # show the GUI + self.connect_signals(self.Handler(self)) + self.window.show() + + class SaveGradientTemplate(Gtk.HBox): + """ + Template for generating gradient name + and preview of selected object in the save page. + """ + + def __init__(self, gradient_data): + Gtk.HBox.__init__(self) + self.gradient_data = gradient_data + self.set_spacing(20) + preview = Gtk.DrawingArea() + preview.set_size_request(150, 42) + preview.set_app_paintable(True) + preview.connect("draw", self.on_draw, gradient_data) + self.pack_start(preview, False, True, 0) + self.input_entry = Gtk.Entry() + self.input_entry.set_placeholder_text("e.g Beautiful Color") + self.input_entry.set_size_request(250, 42) + self.input_entry.set_text(gradient_data.get("id")) + self.input_entry.set_max_length(25) + self.pack_start(self.input_entry, False, True, 1) + + def on_draw(self, wid, cr, data): + """ + Calllback for draw signal for rendering gradient. + params: + - wid :GtkWidget + - cr :Cairo + - data :list -> gradient data + """ + lg = cairo.LinearGradient(0.0, 20.0, 150.0, 20.0) + for stop in data["stops"]: + lg.add_color_stop_rgba( + stop[0], stop[1], stop[2], stop[3], stop[4]) + cr.rectangle(10.0, 0.0, 150.0, 42.0) + cr.set_source(lg) + cr.fill() + + def get_save_gradient_text(self): + return self.input_entry.get_text() + + def get_compiled_gradient(self, new_id): + # compiling gradient stops + root = etree.Element("linearGradient", id=new_id) + for idx, stop in enumerate(self.gradient_data["stops"]): + stop_id = self.get_name() + str(idx) + offset = stop[0] + color = Color([stop[1], stop[2], stop[3]],"rgb") + opacity = stop[4] + tmp_stops = { + "id": stop_id, + "offset": str(offset), + "style": Style({ + "stop-color": str(color), + "stop-opacity": str(opacity) + }).to_str() + } + current_stop = etree.SubElement(root, "stop", attrib=tmp_stops) + return root + + class LoadGradientTemplate(Gtk.FlowBoxChild): + """ + Template for generating gradient name + and preview of saved gradient in the load page. + """ + def __init__(self, gradient_data): + Gtk.FlowBoxChild.__init__(self) + self.gradient_data = gradient_data + self.set_size_request(60,32) + self.set_halign(Gtk.Align.START) + self.set_valign(Gtk.Align.START) + container = Gtk.HBox() + container.set_spacing(5) + container.set_baseline_position(Gtk.BaselinePosition.TOP) + self.checkbox = Gtk.CheckButton() + self.checkbox.draw_indicator = True + container.pack_start(self.checkbox,False,True,0) + preview = Gtk.DrawingArea() + preview.set_size_request(100, 32) + preview.set_app_paintable(True) + preview.connect("draw", self.on_draw, gradient_data) + container.pack_start(preview,False,True,0) + self.text_gradient = Gtk.Label() + self.text_gradient.set_text(gradient_data.get("id")) + self.text_gradient.set_line_wrap(True) + self.text_gradient.set_line_wrap_mode(1) + self.text_gradient.set_max_width_chars(25) + container.pack_start(self.text_gradient,False,True,0) + self.add(container) + + def on_draw(self, wid, cr, data): + """ + Calllback for draw signal for rendering gradient. + params: + - wid :GtkWidget + - cr :Cairo + - data :list -> gradient data + """ + lg = cairo.LinearGradient(0.0, 20.0, 100.0, 20.0) + for stop in data["stops"]: + lg.add_color_stop_rgba( + stop[0], stop[1], stop[2], stop[3], stop[4]) + cr.rectangle(10.0, 0.0, 110.0, 32.0) + cr.set_source(lg) + cr.fill() + + class Handler: + """ Signal Handler for GUI """ + + def __init__(self, main_window): + self.main_window = main_window + + def onDestroy(self, *args): + Gtk.main_quit() + + def onSwitchPage(self, notebook, page, page_num): + if page_num == 0: # save tab + pass + elif page_num == 1: # load/remove tab + self.main_window.gradients_to_load = [] + for children in self.main_window.load_container.get_children(): + self.main_window.load_container.remove(children) + loaded_gradients = load_gradients_from_file() + # TODO render with disabled checkbox if it already exists in current project doc + for idx,gradient in enumerate(loaded_gradients): + # parse gradient stops + stop_data = read_stop_gradient(gradient) + gradient_info = self.main_window.LoadGradientTemplate(stop_data) + gradient_info.checkbox.connect("toggled",self.onLoadGradientToggled, gradient) + gradient_info.set_name("gradient%d" % idx) + self.main_window.load_container.add(gradient_info) + self.main_window.load_container.show_all() + else: + pass + + def onSaveGradientClicked(self, button): + text = "" + gradient_to_save = [] + # get all gradient data in save_container + for item in self.main_window.save_container.get_children(): + # get new gradient name + new_name_gradient = item.get_save_gradient_text() + # strip all special chars + if not new_name_gradient.isalnum(): + new_name_gradient = ''.join(e for e in new_name_gradient if e.isalnum()) + # get gradient data + gradient_data = item.get_compiled_gradient(new_name_gradient) + text += "{0}\n-----\n".format(etree.tostring(gradient_data)) + gradient_to_save.append(gradient_data) + # save to file + status = save_to_file(gradient_to_save) + if status == 0: + info = "%d gradients saved successfully!" % len(gradient_to_save) + # reload current document info with saved gradients + self.main_window.gradientSaver.reload_current_gradients(gradient_to_save) + elif status == 1: + info = "Nothing to save, there is no object with gradient selected. Exiting..." + elif status == -1: + info = "Internal Error (-1)! " + # showing popup information + self.main_window.get_object("information_text").set_text(info) + self.main_window.information_dialog.set_title("Save Gradient Information") + self.main_window.information_dialog.show_all() + + def onLoadGradientToggled(self, togglebutton, gradient): + # if active, queue gradient, otherwise pop it + if togglebutton.get_active(): + self.main_window.gradients_to_load.append(gradient) + else: + self.main_window.gradients_to_load.remove(gradient) + + def onLoadGradientClicked(self, button): + if len(self.main_window.gradients_to_load) > 0: + self.main_window.gradientSaver.insert_new_gradients_to_current_doc(self.main_window.gradients_to_load) + teks = "Successfully loading these gradients:\n" + teks += "".join(["- "+gradient.attrib["id"]+"\n" for gradient in self.main_window.gradients_to_load]) + else: + teks = "No gradient(s) selected to load. Exiting..." + self.main_window.get_object("information_text").set_text(teks) + self.main_window.information_dialog.set_title("Load Gradient Information") + self.main_window.information_dialog.show_all() + + def onRemoveGradientClicked(self, button): + loaded_gradients = load_gradients_from_file() + if len(self.main_window.gradients_to_load) > 0: + gradient_to_remove = [gradient.attrib["id"] for gradient in self.main_window.gradients_to_load] + new_gradient_after = [gradient for gradient in loaded_gradients if gradient.attrib["id"] not in gradient_to_remove] + create_new_file(new_gradient_after) + teks = "Successfully removing these gradients:\n" + teks += "".join(["- "+gradient+"\n" for gradient in gradient_to_remove]) + else: + teks = "No gradient(s) selected to load. Exiting..." + self.main_window.get_object("information_text").set_text(teks) + self.main_window.information_dialog.set_title("Remove Gradient Information") + self.main_window.information_dialog.show_all() + + +class GradientSaver(inkex.Effect): + def __init__(self): + " define how the options are mapped from the inx file " + inkex.Effect.__init__(self) # initialize the super class + try: + self.tty = open("/dev/tty", 'w') + except: + self.tty = open(os.devnull, 'w') + self.doc_selected_gradients = [] + + def insert_new_gradients_to_current_doc(self, gradients): + defs_node = self.svg.getElement("//svg:defs") + for item in gradients: + gradient = etree.SubElement(defs_node,item.tag,attrib=item.attrib) + for stop in item: + etree.SubElement(gradient,stop.tag,attrib=stop.attrib) + + def reload_current_gradients(self, new_data): + " reload gradients information in current project with stored gradient " + for idx,gradient in enumerate(self.doc_selected_gradients): + # set old gradient id to new id + real_node = self.svg.getElement("//*[@id='%s']" % gradient["id"]) + # remove inkscape collect first + real_node.attrib.pop("{"+NSS["inkscape"]+"}collect", None) + real_node.attrib["id"] = new_data[idx].attrib["id"] + # set old xlink:href to new id + node_href = self.svg.getElement("//*[@xlink:href='#%s']" % gradient["id"]) + node_href.attrib["{"+NSS["xlink"]+"}href"] = "#"+new_data[idx].attrib["id"] + # last set up inkscape collect again + real_node.attrib["{"+NSS["inkscape"]+"}collect"] = "always" + + def get_all_doc_gradients(self): + """TODO + retrieve all gradient sources of current project document + """ + pass + + def get_selected_gradients_data(self): + selected_objects = self.svg.selected + gradient_list = [] + if len(selected_objects) > 0: + for item in selected_objects.values(): + style = Style(Style.parse_str(item.get('style'))) + fill = stroke = "None" + if style.get("fill"): + fill = style.get("fill")[5:-1] if "url" in style.get("fill") else "None" + if style.get("stroke"): + stroke = style("stroke")[5:-1] if "url" in style.get("stroke") else "None" + if fill == "None" and stroke == "None": + continue + # read fill data + if "radialGradient" in fill or "linearGradient" in fill: + real_fill = self.svg.getElementById(fill).attrib["{"+NSS["xlink"]+"}href"][1:] + real_fill_node = self.svg.getElementById(real_fill) + if real_fill_node not in gradient_list: + gradient_list.append(real_fill_node) + # read stroke data + if "radialGradient" in stroke or "linearGradient" in stroke: + real_stroke = self.svg.getElementById(stroke).attrib["{"+NSS["xlink"]+"}href"][1:] + real_stroke_node = self.svg.getElementById(real_stroke) + if real_stroke_node not in gradient_list: + gradient_list.append(real_stroke_node) + data = [] + # read gradients data + for gradient in gradient_list: + # parse gradient stops + stop_data = read_stop_gradient(gradient) + inkex.utils.debug(gradient) + data.append(stop_data) + return data + + # called when the extension is running. + def effect(self): + self.doc_selected_gradients = self.get_selected_gradients_data() + try: + app = MainWindow(self) + Gtk.main() + except Exception as e: + import traceback + show_errormsg(e) + show_errormsg(traceback.print_exc()) + + +if __name__ == '__main__': + GradientSaver().run() diff --git a/extensions/fablabchemnitz/gradient_saver/icon.svg b/extensions/fablabchemnitz/gradient_saver/icon.svg new file mode 100644 index 0000000..1ef4270 --- /dev/null +++ b/extensions/fablabchemnitz/gradient_saver/icon.svg @@ -0,0 +1,678 @@ + + + + + Gradient Saver Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Gradient Saver Icon + + + Hadiid Pratama + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/fablabchemnitz/gradient_saver/meta.json b/extensions/fablabchemnitz/gradient_saver/meta.json new file mode 100644 index 0000000..2d17495 --- /dev/null +++ b/extensions/fablabchemnitz/gradient_saver/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "Gradient Saver", + "id": "fablabchemnitz.de.gradient_saver", + "path": "gradient_saver", + "dependent_extensions": null, + "original_name": "Gradient Saver", + "original_id": "id.artemtech.gradient_saver", + "license": "GNU GPL v3", + "license_url": "https://github.com/artemtech/inkscape-gradient-saver/blob/inkscape-1.0/LICENSE", + "comment": "", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/gradient_saver", + "fork_url": "https://github.com/artemtech/inkscape-gradient-saver/", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Gradient+Saver", + "inkscape_gallery_url": null, + "main_authors": [ + "github.com/artemtech", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/grey_to_monoalpha/grey_to_monoalpha.inx b/extensions/fablabchemnitz/grey_to_monoalpha/grey_to_monoalpha.inx new file mode 100644 index 0000000..50bf83a --- /dev/null +++ b/extensions/fablabchemnitz/grey_to_monoalpha/grey_to_monoalpha.inx @@ -0,0 +1,47 @@ + + + Grey to MonoAlpha + fablabchemnitz.de.grey_to_monoalpha + + + + + 0x000000ff + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + path + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/grey_to_monoalpha/grey_to_monoalpha.py b/extensions/fablabchemnitz/grey_to_monoalpha/grey_to_monoalpha.py new file mode 100644 index 0000000..1bbe1ae --- /dev/null +++ b/extensions/fablabchemnitz/grey_to_monoalpha/grey_to_monoalpha.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# +# Copyright (C) [2021] [Matt Cottam], [mpcottam@raincloud.co.uk] +# +# 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. +# + +############################################################################## +# Grey To Mono Alpha *** Convert Greys to Monochrome with varying Opacity +############################################################################## + +import math + +import inkex +from inkex import Color + +# Python Standard Libary + +from statistics import mean + + +def get_attributes(self): + for att in dir(self): + inkex.errormsg((att, getattr(self, att))) + + +def rgba_to_bw_rgba(self, my_objects): + apply_to = self.options.apply_to_type_radio + mono_color = self.options.color_picker_mono.to_rgba() + opacity_lower_threshold = self.options.opacity_lower_threshold + opacity_upper_threshold = self.options.opacity_upper_threshold + opacity_range = opacity_upper_threshold - opacity_lower_threshold + + for my_object in my_objects: + + if 'fill' in apply_to and ('fill:none' not in str(my_object.style)): + my_fill_color = my_object.style.get_color(name='fill').to_rgba() + my_fill_color_red = my_fill_color[0] + my_fill_color_green = my_fill_color[1] + my_fill_color_blue = my_fill_color[2] + mean_fill_component_value = mean([my_fill_color_red, my_fill_color_blue, my_fill_color_green]) + + if mean_fill_component_value > 0: + mono_opacity = (1 - (mean_fill_component_value / 256)) * opacity_range + mono_opacity = mono_opacity + opacity_lower_threshold + else: + mono_opacity = opacity_upper_threshold + + my_object.style['fill'] = str(mono_color) + my_object.style['fill-opacity'] = str(mono_opacity) + + if 'stroke' in apply_to and (';stroke:none' not in str(my_object.style)) and ('stroke:' in str(my_object.style)): + my_stroke_color = my_object.style.get_color(name='stroke').to_rgba() + my_stroke_color_red = my_stroke_color[0] + my_stroke_color_green = my_stroke_color[1] + my_stroke_color_blue = my_stroke_color[2] + mean_stroke_component_value = mean([my_stroke_color_red, my_stroke_color_blue, my_stroke_color_green]) + + if mean_stroke_component_value > 0: + mono_opacity = (1 - (mean_stroke_component_value / 256)) * opacity_range + mono_opacity = mono_opacity + opacity_lower_threshold + else: + mono_opacity = opacity_upper_threshold + + my_object.style['stroke'] = str(mono_color) + my_object.style['stroke-opacity'] = str(mono_opacity) + + +class GreyToMonoAlpha(inkex.EffectExtension): + + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--color_picker_mono", type=inkex.colors.Color, default=0) + pars.add_argument("--apply_to_type_radio", default=None) + pars.add_argument("--opacity_lower_threshold", type=float, default=0) + pars.add_argument("--opacity_upper_threshold", type=float, default=1) + + def effect(self): + my_objects = self.svg.selected + if len(my_objects) < 1: + self.msg('Please select some paths first.') + return + rgba_to_bw_rgba(self, my_objects) + + +if __name__ == '__main__': + GreyToMonoAlpha().run() diff --git a/extensions/fablabchemnitz/grey_to_monoalpha/meta.json b/extensions/fablabchemnitz/grey_to_monoalpha/meta.json new file mode 100644 index 0000000..cba48c9 --- /dev/null +++ b/extensions/fablabchemnitz/grey_to_monoalpha/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "Grey To MonoAlpha", + "id": "fablabchemnitz.de.grey_to_monoalpha", + "path": "grey_to_monoalpha", + "dependent_extensions": null, + "original_name": "Grey to MonoAlpha", + "original_id": "org.inkscape.grey_to_monoalpha", + "license": "GNU GPL v3", + "license_url": "https://gitlab.com/inklinea/grey-to-mono-alpha/-/blob/main/LICENSE", + "comment": "", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/grey_to_monoalpha", + "fork_url": "https://gitlab.com/inklinea/grey-to-mono-alpha", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Grey+to+MonoAlpha", + "inkscape_gallery_url": null, + "main_authors": [ + "gitlab.com/inklinea", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/image_triangulation/meta.json b/extensions/fablabchemnitz/image_triangulation/meta.json index e6537e9..c367e09 100644 --- a/extensions/fablabchemnitz/image_triangulation/meta.json +++ b/extensions/fablabchemnitz/image_triangulation/meta.json @@ -9,13 +9,13 @@ "license": "GNU GPL v2", "license_url": "https://github.com/nicolaromano/triangulate/blob/master/triangulation.py", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/image_triangulation", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/image_triangulation", "fork_url": "https://github.com/nicolaromano/triangulate", "documentation_url": "https://stadtfabrikanten.org/display/IFM/Image+Triangulation", "inkscape_gallery_url": null, "main_authors": [ "github.com/nicolaromano", - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/inset_alignment/meta.json b/extensions/fablabchemnitz/inset_alignment/meta.json index 2aa3326..fa2c239 100644 --- a/extensions/fablabchemnitz/inset_alignment/meta.json +++ b/extensions/fablabchemnitz/inset_alignment/meta.json @@ -9,13 +9,13 @@ "license": "GNU GPL v2", "license_url": "https://sourceforge.net/p/razorfoss/svn/HEAD/tree/trunk/Inkscape/InsetAlignmentExtension/InsetAlignment.py", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/inset_alignment", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/inset_alignment", "fork_url": "https://sourceforge.net/p/razorfoss/svn/HEAD/tree/trunk/Inkscape/InsetAlignmentExtension/", "documentation_url": "https://stadtfabrikanten.org/display/IFM/Inset+Alignment", "inkscape_gallery_url": null, "main_authors": [ "Luke Phillips:lukerazor@hotmail.com", - "github.com/vmario89" + "github.com/eridur-de" ] } ] diff --git a/extensions/fablabchemnitz/my-gradients.svg b/extensions/fablabchemnitz/my-gradients.svg new file mode 100644 index 0000000..d49e971 --- /dev/null +++ b/extensions/fablabchemnitz/my-gradients.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/extensions/fablabchemnitz/output_pro/meta.json b/extensions/fablabchemnitz/output_pro/meta.json new file mode 100644 index 0000000..aa60977 --- /dev/null +++ b/extensions/fablabchemnitz/output_pro/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "Inkscape Output Pro", + "id": "fablabchemnitz.de.output_pro", + "path": "output_pro", + "dependent_extensions": null, + "original_name": "Inkscape Output Pro", + "original_id": "org.inkscape.outputpro", + "license": "GNU GPL v2", + "license_url": "https://github.com/jonata/Inkscape-OUTPUT-PRO/blob/master/LICENSE.txt", + "comment": "ported to Inkscape v1 by Mario Voigt", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/output_pro", + "fork_url": "https://github.com/jonata/Inkscape-OUTPUT-PRO", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Output+Pro+for+Inkscape", + "inkscape_gallery_url": null, + "main_authors": [ + "github.com/jonata", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/output_pro/output_pro.inx b/extensions/fablabchemnitz/output_pro/output_pro.inx new file mode 100644 index 0000000..c0d7ce0 --- /dev/null +++ b/extensions/fablabchemnitz/output_pro/output_pro.inx @@ -0,0 +1,16 @@ + + + Inkscape Output Pro + fablabchemnitz.de.output_pro + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/output_pro/output_pro.py b/extensions/fablabchemnitz/output_pro/output_pro.py new file mode 100644 index 0000000..9c4c5b5 --- /dev/null +++ b/extensions/fablabchemnitz/output_pro/output_pro.py @@ -0,0 +1,1091 @@ +#!/usr/bin/env python3 + +import inkex, re, os, random, sys, subprocess, shutil +from inkex.command import inkscape + +from outputpro import cmyk, cutmarks + +from PyQt5 import QtGui, QtCore, uic, QtWidgets +from PyQt5.QtWidgets import QMainWindow, QApplication +from PyQt5.QtCore import * + +import gettext +_ = gettext.gettext + +import tempfile + +dirpathTempFolder = tempfile.TemporaryDirectory(suffix=str(random.randint(0,9)), prefix="output-") +dirpathSoftware = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'outputpro') +uuconv = { + "in": 96.0, + "pt": 1.33333333333, + "px": 1.0, + "mm": 3.77952755913, + "cm": 37.7952755913, + "pc": 16.0} + + +def unittouu(string): + '''Returns userunits given a string representation of units in another system''' + unit = re.compile('(%s)$' % '|'.join(uuconv.keys())) + param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)') + + p = param.match(string) + u = unit.search(string) + if p: + retval = float(p.string[p.start():p.end()]) + else: + retval = 0.0 + if u: + try: + return retval * uuconv[u.string[u.start():u.end()]] + except KeyError: + pass + return retval + +class OutputPro(inkex.EffectExtension): + + def effect(self): + if "nt" in os.name: + icc_dir = 'C:\\Windows\\System32\\spool\\drivers\\color' + else: + icc_dir = '/usr/share/color/icc/colord/' + #might be also ~/.local/share/icc/ + + try: + with open(os.path.join(os.path.abspath(inkex.utils.get_user_directory() + "/../"), "preferences.xml"), 'r') as f: + inkscape_config = f.read() + + list_of_export_formats = ['JPEG'] + list_of_format_tips = {'JPEG':'The JPEG format always has some loss of quality. Although it supports CMYK, it is not recommended for use in printed graphics.'} + list_of_color_modes_jpeg = ['CMYK','RGB','Gray','CMY','HSB','HSL','HWB','Lab','Log', 'OHTA','Rec601Luma','Rec601YCbCr','Rec709Luma','Rec709YCbCr','sRGB','XYZ','YCbCr','YCC','YIQ','YPbPr','YUV'] + list_of_interlacing_jpeg = {u'None':'none', u'Line':'line', u'Plane':'plane', u'Partition':'partition'} + list_of_noise_jpeg = {u'Gaussian':'Gaussian-noise', u'Impulse':'Impulse-noise', u'Laplacian':'Laplacian-noise', u'Multiplicative':'Multiplicative-noise', u'Poisson':'Poisson-noise', u'Uniform':'Uniform-noise'} + list_of_subsampling_jpeg = ['1x1, 1x1, 1x1', '2x1, 1x1, 1x1', '1x2, 1x1, 1x1', '2x2, 1x1, 1x1'] + list_of_dct_jpeg = {u'Integer':'int', u'Integer (fast)':'fast', u'Floating point':'float'} + list_of_area_to_export = [_(u"Page"), _(u"Drawing"), _(u"Object")]#, _(u"Área definida")] + + if "nt" in os.name: + shell = True + else: + shell = False + + selected_screen_profile = inkscape_config.split('id="displayprofile"')[1].split('uri="')[1].split('" />')[0].split('/')[-1] + #if selected_screen_profile == '': + # inkex.utils.debug("Configured icc color profile (Inkscape) is not set. Configure it in preferences and restart Inkscape to apply changes.") + selected_print_profile = inkscape_config.split('id="softproof"')[1].split('uri="')[1].split('" />')[0].split('/')[-1] + + list_of_selected_objects = [] + for id, node in self.svg.selected.items(): + list_of_selected_objects.append(node.get('id')) + if len(list_of_selected_objects) >= 1: + selected_object = list_of_selected_objects[0] + else: + selected_object = 'layer1' + + resolution = '96' + + shutil.copy2(self.options.input_file, os.path.join(dirpathTempFolder.name, "original.svg")) # complete target filename given + + svg = self.document.getroot() + page_width = unittouu(svg.get('width')) + page_height = unittouu(svg.get('height')) + + class mainWindow(QtWidgets.QWidget): + + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent) + self.resize(950, 600) + self.setMaximumSize(QtCore.QSize(950, 600)) + self.setMinimumSize(QtCore.QSize(950, 600)) + self.setWindowTitle(_(u'Inkscape Output Pro Bitmap')) + self.move(int((QtWidgets.QDesktopWidget().screenGeometry().width()-self.geometry().width())/2), int((QtWidgets.QDesktopWidget().screenGeometry().height()-self.geometry().height())/2)) + + self.preview_zoom = 1.0 + + self.top_title_bitmap = QtWidgets.QLabel(parent=self) + self.top_title_bitmap.setGeometry(0, 0, 950, 60) + self.top_title_bitmap.setPixmap(QtGui.QPixmap(os.path.join(dirpathSoftware, 'top.png'))) + + self.preview_panel = QtWidgets.QWidget(parent=self) + self.preview_panel.setGeometry(0, 0, 320, 600) + + self.preview_bitmap = QtWidgets.QLabel(parent=self.preview_panel) + self.preview_bitmap.setGeometry(10, 70, 300, 300) + self.preview_bitmap.setPixmap(QtGui.QPixmap(os.path.join(dirpathTempFolder.name, 'preview.png'))) + #self.preview_bitmap.setStyleSheet("QWidget { background: url(alpha.png)}") + #self.preview_bitmap.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignCenter) + + self.preview_original_title = QtWidgets.QLabel(parent=self.preview_panel) + self.preview_original_title.setText(_(u"Original").upper()) + self.preview_original_title.setGeometry(255, 355, 50, 10) + self.preview_original_title.setAlignment(QtCore.Qt.AlignCenter) + self.preview_original_title.setStyleSheet('QFrame{font:6pt;border-radius: 2px;padding: 2px;background-color:rgba(0,0,0,128);color:white}') + + self.preview_result_title = QtWidgets.QLabel(parent=self.preview_panel) + self.preview_result_title.setText(_(u"Result").upper()) + self.preview_result_title.setGeometry(15, 75, 50, 10) + self.preview_result_title.setAlignment(QtCore.Qt.AlignCenter) + self.preview_result_title.setStyleSheet('QFrame{font:6pt;border-radius: 2px;padding: 2px;background-color:rgba(0,0,0,128);color:white}') + + self.zoom_out_button = QtWidgets.QPushButton(QtGui.QIcon.fromTheme("zoom-out"), '', parent=self.preview_panel) + self.zoom_out_button.setGeometry(10, 371, 16, 16) + self.zoom_out_button.setIconSize(QtCore.QSize(12,12)) + self.zoom_out_button.setFlat(True) + self.zoom_out_button.clicked.connect(self.zoom_out) + + self.zoom_in_button = QtWidgets.QPushButton(QtGui.QIcon.fromTheme("zoom-in"), '', parent=self.preview_panel) + self.zoom_in_button.setGeometry(26, 371, 16, 16) + self.zoom_in_button.setIconSize(QtCore.QSize(12,12)) + self.zoom_in_button.setFlat(True) + self.zoom_in_button.clicked.connect(self.zoom_in) + + self.preview_zoom_title = QtWidgets.QLabel(parent=self.preview_panel) + self.preview_zoom_title.setGeometry(44, 371, 256, 16) + self.preview_zoom_title.setText('100%') + self.preview_zoom_title.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft) + self.preview_zoom_title.setFont(QtGui.QFont('Ubuntu', 8)) + #self.preview_result_title.setStyleSheet('QFrame{font:7pt;border-radius: 2px;padding: 2px;background-color:rgba(0,0,0,128);color:white}') + + self.view_c_button = QtWidgets.QPushButton('C', parent=self.preview_panel) + self.view_c_button.setGeometry(246, 371, 16, 16) + self.view_c_button.setIconSize(QtCore.QSize(12,12)) + self.view_c_button.setFlat(True) + self.view_c_button.setCheckable(True) + self.view_c_button.setChecked(True) + self.view_c_button.setVisible(False) + self.view_c_button.clicked.connect(self.cmyk_advanced_manipulation_view_separations) + + self.view_m_button = QtWidgets.QPushButton('M', parent=self.preview_panel) + self.view_m_button.setGeometry(262, 371, 16, 16) + self.view_m_button.setIconSize(QtCore.QSize(12,12)) + self.view_m_button.setFlat(True) + self.view_m_button.setCheckable(True) + self.view_m_button.setChecked(True) + self.view_m_button.setVisible(False) + self.view_m_button.clicked.connect(self.cmyk_advanced_manipulation_view_separations) + + self.view_y_button = QtWidgets.QPushButton('Y', parent=self.preview_panel) + self.view_y_button.setGeometry(278, 371, 16, 16) + self.view_y_button.setIconSize(QtCore.QSize(12,12)) + self.view_y_button.setFlat(True) + self.view_y_button.setCheckable(True) + self.view_y_button.setChecked(True) + self.view_y_button.setVisible(False) + self.view_y_button.clicked.connect(self.cmyk_advanced_manipulation_view_separations) + + self.view_k_button = QtWidgets.QPushButton('K', parent=self.preview_panel) + self.view_k_button.setGeometry(294, 371, 16, 16) + self.view_k_button.setIconSize(QtCore.QSize(12,12)) + self.view_k_button.setFlat(True) + self.view_k_button.setCheckable(True) + self.view_k_button.setChecked(True) + self.view_k_button.setVisible(False) + self.view_k_button.clicked.connect(self.cmyk_advanced_manipulation_view_separations) + + self.view_image_info = QtWidgets.QLabel(parent=self.preview_panel) + self.view_image_info.setGeometry(10, 400, 300, 190) + self.view_image_info.setFont(QtGui.QFont('Ubuntu', 8)) + self.view_image_info.setWordWrap(True) + self.view_image_info.setAlignment(QtCore.Qt.AlignTop) + + self.format_title = QtWidgets.QLabel(parent=self) + self.format_title.setText(_(u"Format").upper()) + self.format_title.setGeometry(320, 70, 200, 15) + self.format_title.setFont(QtGui.QFont('Ubuntu', 8, 75)) + + self.format_choice = QtWidgets.QComboBox(parent=self) + self.format_choice.setGeometry(320, 85, 200, 25) + self.format_choice.addItems(list_of_export_formats) + self.format_choice.activated.connect(self.change_format) + + self.format_preview_check = QtWidgets.QCheckBox(parent=self) + self.format_preview_check.setGeometry(540, 85, 200, 25) + self.format_preview_check.setText(_(u"Preview")) + self.format_preview_check.setChecked(True) + self.format_preview_check.clicked.connect(self.format_preview_change) + + self.option_box = QtWidgets.QTabWidget(parent=self) + self.option_box.setGeometry(320, 120, 620, 435) + + self.general_options_panel = QtWidgets.QWidget(parent=self) + self.general_geometry_panel = QtWidgets.QWidget(parent=self) + self.general_prepress_panel = QtWidgets.QWidget(parent=self) + self.general_imposition_panel = QtWidgets.QWidget(parent=self) + self.option_box.addTab(self.general_options_panel, _(u"Options")) + self.option_box.addTab(self.general_geometry_panel, _(u"Size")) + self.option_box.addTab(self.general_prepress_panel, _(u"Prepress")) + self.option_box.addTab(self.general_imposition_panel, _(u"Imposition")) + + self.option_box.currentChanged.connect(self.generate_preview) + + self.general_options_panel_jpeg = QtWidgets.QWidget(parent=self.general_options_panel) + self.general_options_panel_jpeg.setVisible(False) + + self.icc_dir_textbox_label = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.icc_dir_textbox_label.setText(_(u"ICC profile folder")) + self.icc_dir_textbox_label.setGeometry(10, 280, 120, 25) + + self.icc_dir_textbox = QtWidgets.QLineEdit(parent=self.general_options_panel_jpeg) + self.icc_dir_textbox.setReadOnly(True) + self.icc_dir_textbox.setGeometry(130, 280, 260, 25) + self.icc_dir_textbox.setText(icc_dir) + + self.icc_dir_button = QtWidgets.QPushButton(_("Change"), parent=self.general_options_panel_jpeg) + self.icc_dir_button.setGeometry(403, 280, 70, 25) + self.icc_dir_button.clicked.connect(self.change_icc_dir) + + self.color_mode_title_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.color_mode_title_jpeg.setText(_(u"Color mode").upper()) + self.color_mode_title_jpeg.setGeometry(10, 10, 260, 15) + self.color_mode_title_jpeg.setFont(QtGui.QFont('Ubuntu', 8)) + + self.color_mode_choice_jpeg = QtWidgets.QComboBox(parent=self.general_options_panel_jpeg) + self.color_mode_choice_jpeg.setGeometry(10, 25, 260, 25) + self.color_mode_choice_jpeg.addItems(list_of_color_modes_jpeg) + self.color_mode_choice_jpeg.activated.connect(self.change_color_mode_jpeg) + + self.color_mode_title_tip_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.color_mode_title_tip_jpeg.setGeometry(10, 50, 260, 15) + self.color_mode_title_tip_jpeg.setFont(QtGui.QFont('Ubuntu', 7)) + + self.quality_title_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.quality_title_jpeg.setText(_(u"Quality").upper()) + self.quality_title_jpeg.setGeometry(285, 10, 100, 15) + self.quality_title_jpeg.setFont(QtGui.QFont('Ubuntu', 8)) + + jpeg_quality = 100 + self.quality_percent_title_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.quality_percent_title_jpeg.setText('{}%'.format(jpeg_quality)) + self.quality_percent_title_jpeg.setGeometry(505, 10, 100, 40) + self.quality_percent_title_jpeg.setFont(QtGui.QFont('Ubuntu', 12, 75)) + self.quality_percent_title_jpeg.setAlignment(QtCore.Qt.AlignRight) + + self.quality_percent_title_left_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.quality_percent_title_left_jpeg.setText('Lower quality\nSmaller file') + self.quality_percent_title_left_jpeg.setGeometry(285, 40, 160, 35) + self.quality_percent_title_left_jpeg.setFont(QtGui.QFont('Ubuntu', 7)) + self.quality_percent_title_left_jpeg.setAlignment(QtCore.Qt.AlignLeft) + + self.quality_percent_title_right_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.quality_percent_title_right_jpeg.setText('Higher quality
Larger file') + self.quality_percent_title_right_jpeg.setGeometry(445, 40, 160, 35) + self.quality_percent_title_right_jpeg.setFont(QtGui.QFont('Ubuntu', 7)) + self.quality_percent_title_right_jpeg.setAlignment(QtCore.Qt.AlignRight) + + self.quality_choice_dial_jpeg = QtWidgets.QDial(parent=self.general_options_panel_jpeg) + self.quality_choice_dial_jpeg.setRange(1,100) + self.quality_choice_dial_jpeg.setGeometry(415, 10, 60, 60) + self.quality_choice_dial_jpeg.setNotchesVisible(True) + self.quality_choice_dial_jpeg.setValue(jpeg_quality) + self.quality_choice_dial_jpeg.sliderReleased.connect(self.generate_preview) + self.quality_choice_dial_jpeg.valueChanged.connect(self.change_quality_live_jpeg) + + self.color_profile_choice_jpeg = QtWidgets.QCheckBox(_(u"Use Inkscape color profile"), parent=self.general_options_panel_jpeg) + self.color_profile_choice_jpeg.setChecked(False) + self.color_profile_choice_jpeg.setGeometry(283, 150, 325, 25) + self.color_profile_choice_jpeg.clicked.connect(self.generate_preview) + + self.document_color_profile_title_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.document_color_profile_title_jpeg.setGeometry(290, 175, 320, 30) + self.document_color_profile_title_jpeg.setWordWrap(True) + self.document_color_profile_title_jpeg.setFont(QtGui.QFont('Ubuntu', 7)) + self.document_color_profile_title_jpeg.setAlignment(QtCore.Qt.AlignLeft) + + if selected_print_profile == '': + self.document_color_profile_title_jpeg.setEnabled(False) + self.color_profile_choice_jpeg.setEnabled(False) + self.document_color_profile_title_jpeg.setText(_(u"This document is not using a color profile.")) + else: + self.document_color_profile_title_jpeg.setText(_(u"The profile used by Inkscape is") + ' ' + selected_print_profile[:-4]) + + self.jpeg_interlace_option_jpeg = QtWidgets.QCheckBox(_(u"Interlace"), parent=self.general_options_panel_jpeg) + self.jpeg_interlace_option_jpeg.setGeometry(10, 80, 120, 25) + self.jpeg_interlace_option_jpeg.toggled.connect(self.jpeg_interlace_click_jpeg) + + self.jpeg_interlace_choice_jpeg = QtWidgets.QComboBox(parent=self.general_options_panel_jpeg) + self.jpeg_interlace_choice_jpeg.setGeometry(130, 80, 140, 25) + self.jpeg_interlace_choice_jpeg.addItems(list_of_interlacing_jpeg.keys()) + self.jpeg_interlace_choice_jpeg.setCurrentIndex(1) + self.jpeg_interlace_choice_jpeg.setEnabled(False) + self.jpeg_interlace_choice_jpeg.activated.connect(self.generate_preview) + + self.jpeg_optimize_option_jpeg = QtWidgets.QCheckBox(_(u"Optimize"), parent=self.general_options_panel_jpeg) + self.jpeg_optimize_option_jpeg.setGeometry(10, 115, 260, 25) + self.jpeg_optimize_option_jpeg.setChecked(True) + + self.jpeg_noise_option_jpeg = QtWidgets.QCheckBox(_(u"Noise"), parent=self.general_options_panel_jpeg) + self.jpeg_noise_option_jpeg.setGeometry(10, 150, 120, 25) + self.jpeg_noise_option_jpeg.toggled.connect(self.jpeg_noise_click_jpeg) + + self.jpeg_noise_choice_jpeg = QtWidgets.QComboBox(parent=self.general_options_panel_jpeg) + self.jpeg_noise_choice_jpeg.setGeometry(130, 150, 140, 25) + self.jpeg_noise_choice_jpeg.addItems(list_of_noise_jpeg.keys()) + self.jpeg_noise_choice_jpeg.setCurrentIndex(1) + self.jpeg_noise_choice_jpeg.setEnabled(False) + self.jpeg_noise_choice_jpeg.activated.connect(self.generate_preview) + + self.jpeg_noise_ammount_jpeg = QtWidgets.QSlider(QtCore.Qt.Horizontal, parent=self.general_options_panel_jpeg) + self.jpeg_noise_ammount_jpeg.setGeometry(10, 170, 260, 25) + self.jpeg_noise_ammount_jpeg.setRange(0,100) + self.jpeg_noise_ammount_jpeg.setEnabled(False) + self.jpeg_noise_ammount_jpeg.setValue(0) + self.jpeg_noise_ammount_jpeg.sliderReleased.connect(self.generate_preview) + + self.jpeg_subsampling_option_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.jpeg_subsampling_option_jpeg.setText(_(u"Sub-sampling")) + self.jpeg_subsampling_option_jpeg.setGeometry(10, 210, 140, 25) + + self.jpeg_subsampling_choice_jpeg = QtWidgets.QComboBox(parent=self.general_options_panel_jpeg) + self.jpeg_subsampling_choice_jpeg.setGeometry(150, 210, 120, 25) + self.jpeg_subsampling_choice_jpeg.addItems(list_of_subsampling_jpeg) + self.jpeg_subsampling_choice_jpeg.setCurrentIndex(0) + self.jpeg_subsampling_choice_jpeg.activated.connect(self.generate_preview) + + self.jpeg_dct_option_jpeg = QtWidgets.QLabel(parent=self.general_options_panel_jpeg) + self.jpeg_dct_option_jpeg.setText(_(u"DCT Method")) + self.jpeg_dct_option_jpeg.setGeometry(10, 245, 120, 25) + + self.jpeg_dct_choice_jpeg = QtWidgets.QComboBox(parent=self.general_options_panel_jpeg) + self.jpeg_dct_choice_jpeg.setGeometry(130, 245, 140, 25) + self.jpeg_dct_choice_jpeg.addItems(list_of_dct_jpeg.keys()) + self.jpeg_dct_choice_jpeg.activated.connect(self.generate_preview) + + self.cmyk_advanced_manipulation_option_jpeg = QtWidgets.QCheckBox(_(u"Accurate color handling"), parent=self.general_options_panel_jpeg) + self.cmyk_advanced_manipulation_option_jpeg.setGeometry(283, 80, 325, 25) + self.cmyk_advanced_manipulation_option_jpeg.clicked.connect(self.cmyk_advanced_manipulation_click_jpeg) + + self.cmyk_overblack_jpeg = QtWidgets.QCheckBox(_(u"Black overlay"), parent=self.general_options_panel_jpeg) + self.cmyk_overblack_jpeg.setGeometry(283, 115, 325, 25) + self.cmyk_overblack_jpeg.setEnabled(False) + self.cmyk_overblack_jpeg.clicked.connect(self.cmyk_advanced_manipulation_click_jpeg) + + self.area_to_export_title = QtWidgets.QLabel(parent=self.general_geometry_panel) + self.area_to_export_title.setText(_(u"Area to export").upper()) + self.area_to_export_title.setGeometry(10, 20, 250, 15) + self.area_to_export_title.setFont(QtGui.QFont('Ubuntu', 8)) + + self.area_to_export_choice = QtWidgets.QComboBox(parent=self.general_geometry_panel) + self.area_to_export_choice.setGeometry(10, 35, 250, 25) + self.area_to_export_choice.addItems(list_of_area_to_export) + self.area_to_export_choice.activated.connect(self.change_area_to_export) + + self.dpi_title = QtWidgets.QLabel(parent=self.general_geometry_panel) + self.dpi_title.setText(_(u"Dots per inch").upper()) + self.dpi_title.setGeometry(270, 20, 200, 15) + self.dpi_title.setFont(QtGui.QFont('Ubuntu', 8)) + + self.dpi_choice = QtWidgets.QSpinBox(parent=self.general_geometry_panel) + self.dpi_choice.setValue(96) + self.dpi_choice.setGeometry(270, 35, 100, 25) + self.dpi_choice.setRange(1, 99999) + self.dpi_choice.editingFinished.connect(self.change_area_to_export) + + self.dpi_text_title = QtWidgets.QLabel(parent=self.general_geometry_panel) + self.dpi_text_title.setText('dpi') + self.dpi_text_title.setGeometry(380, 35, 80, 25) + self.dpi_text_title.setFont(QtGui.QFont('Ubuntu', 8)) + + self.x0_value = QtWidgets.QSpinBox(parent=self.general_geometry_panel) + self.x0_value.setGeometry(10, 100, 80, 25) + self.x0_value.setRange(1, 2147483647) + self.x0_value.editingFinished.connect(self.change_area_to_export) + + self.y0_value = QtWidgets.QSpinBox(parent=self.general_geometry_panel) + self.y0_value.setGeometry(100, 130, 80, 25) + self.y0_value.setRange(1, 2147483647) + self.y0_value.editingFinished.connect(self.change_area_to_export) + + self.x1_value = QtWidgets.QSpinBox(parent=self.general_geometry_panel) + self.x1_value.setGeometry(100, 70, 80, 25) + self.x1_value.setRange(1, 2147483647) + self.x1_value.editingFinished.connect(self.change_area_to_export) + + self.y1_value = QtWidgets.QSpinBox(parent=self.general_geometry_panel) + self.y1_value.setGeometry(190, 100, 80, 25) + self.y1_value.setRange(1, 2147483647) + self.y1_value.editingFinished.connect(self.change_area_to_export) + + self.area_to_export_id_title = QtWidgets.QLabel(parent=self.general_geometry_panel) + self.area_to_export_id_title.setText(_(u"Object to be exported").upper()) + self.area_to_export_id_title.setGeometry(10, 70, 300, 15) + self.area_to_export_id_title.setFont(QtGui.QFont('Ubuntu', 8)) + + self.area_to_export_id_name = QtWidgets.QLineEdit(parent=self.general_geometry_panel) + self.area_to_export_id_name.setGeometry(10, 85, 300, 25) + + self.area_to_export_idonly_check = QtWidgets.QCheckBox(parent=self.general_geometry_panel) + self.area_to_export_idonly_check.setGeometry(10, 120, 400, 25) + self.area_to_export_idonly_check.setText(_(u"Export only object")) + + self.prepress_paper_settings_label = QtWidgets.QLabel(parent=self.general_prepress_panel) + self.prepress_paper_settings_label.setGeometry(10, 10, 300, 15) + self.prepress_paper_settings_label.setText(_(u"Paper or film setting").upper()) + self.prepress_paper_settings_label.setFont(QtGui.QFont('Ubuntu', 8)) + + self.prepress_paper_settings_invert = QtWidgets.QCheckBox(parent=self.general_prepress_panel) + self.prepress_paper_settings_invert.setGeometry(10, 25, 300, 25) + self.prepress_paper_settings_invert.setText(_(u"Invert")) + self.prepress_paper_settings_invert.setChecked(False) + self.prepress_paper_settings_invert.clicked.connect(self.generate_preview) + + self.prepress_paper_settings_mirror = QtWidgets.QCheckBox(parent=self.general_prepress_panel) + self.prepress_paper_settings_mirror.setGeometry(10, 50, 300, 25) + self.prepress_paper_settings_mirror.setText(_(u"Mirror")) + self.prepress_paper_settings_mirror.setChecked(False) + self.prepress_paper_settings_mirror.clicked.connect(self.generate_preview) + + self.prepress_paper_cutmarks_label = QtWidgets.QLabel(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_label.setGeometry(10, 85, 300, 15) + self.prepress_paper_cutmarks_label.setText(_(u"Crop marks").upper()) + self.prepress_paper_cutmarks_label.setFont(QtGui.QFont('Ubuntu', 8)) + + self.prepress_paper_cutmarks_check = QtWidgets.QCheckBox(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_check.setGeometry(10, 100, 300, 25) + self.prepress_paper_cutmarks_check.setText(_(u"Insert crop marks")) + self.prepress_paper_cutmarks_check.setChecked(False) + self.prepress_paper_cutmarks_check.clicked.connect(self.cut_marks_insert_change) + + self.prepress_paper_cutmarks_strokewidth_label = QtWidgets.QLabel(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_strokewidth_label.setGeometry(10, 125, 200, 25) + self.prepress_paper_cutmarks_strokewidth_label.setText(_(u"Mark thickness:")) + self.prepress_paper_cutmarks_strokewidth_label.setEnabled(False) + + self.prepress_paper_cutmarks_strokewidth_value = QtWidgets.QLineEdit(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_strokewidth_value.setGeometry(210, 125, 50, 25) + self.prepress_paper_cutmarks_strokewidth_value.setText('1') + self.prepress_paper_cutmarks_strokewidth_value.setEnabled(False) + self.prepress_paper_cutmarks_strokewidth_value.editingFinished.connect(self.generate_preview) + + self.prepress_paper_cutmarks_strokewidth_choice = QtWidgets.QComboBox(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_strokewidth_choice.setGeometry(260,125,50,25) + self.prepress_paper_cutmarks_strokewidth_choice.addItems(uuconv.keys()) + self.prepress_paper_cutmarks_strokewidth_choice.setCurrentIndex(5) + self.prepress_paper_cutmarks_strokewidth_choice.activated.connect(self.generate_preview) + self.prepress_paper_cutmarks_strokewidth_choice.setEnabled(False) + + self.prepress_paper_cutmarks_bleedsize_label = QtWidgets.QLabel(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_bleedsize_label.setGeometry(10, 150, 200, 25) + self.prepress_paper_cutmarks_bleedsize_label.setText(_(u"Bleed:")) + self.prepress_paper_cutmarks_bleedsize_label.setEnabled(False) + + self.prepress_paper_cutmarks_bleedsize_value = QtWidgets.QLineEdit(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_bleedsize_value.setGeometry(210, 150, 50, 25) + self.prepress_paper_cutmarks_bleedsize_value.setText('5') + self.prepress_paper_cutmarks_bleedsize_value.setEnabled(False) + self.prepress_paper_cutmarks_bleedsize_value.editingFinished.connect(self.generate_preview) + + self.prepress_paper_cutmarks_bleedsize_choice = QtWidgets.QComboBox(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_bleedsize_choice.setGeometry(260,150,50,25) + self.prepress_paper_cutmarks_bleedsize_choice.addItems(uuconv.keys()) + self.prepress_paper_cutmarks_bleedsize_choice.setCurrentIndex(5) + self.prepress_paper_cutmarks_bleedsize_choice.activated.connect(self.generate_preview) + self.prepress_paper_cutmarks_bleedsize_choice.setEnabled(False) + + self.prepress_paper_cutmarks_marksize_label = QtWidgets.QLabel(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_marksize_label.setGeometry(10, 175, 200, 25) + self.prepress_paper_cutmarks_marksize_label.setText(_(u"Mark size:")) + self.prepress_paper_cutmarks_marksize_label.setEnabled(False) + + self.prepress_paper_cutmarks_marksize_value = QtWidgets.QLineEdit(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_marksize_value.setGeometry(210, 175, 50, 25) + self.prepress_paper_cutmarks_marksize_value.setText('5') + self.prepress_paper_cutmarks_marksize_value.setEnabled(False) + self.prepress_paper_cutmarks_marksize_value.editingFinished.connect(self.generate_preview) + + self.prepress_paper_cutmarks_marksize_choice = QtWidgets.QComboBox(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_marksize_choice.setGeometry(260,175,50,25) + self.prepress_paper_cutmarks_marksize_choice.addItems(uuconv.keys()) + self.prepress_paper_cutmarks_marksize_choice.setCurrentIndex(5) + self.prepress_paper_cutmarks_marksize_choice.activated.connect(self.generate_preview) + self.prepress_paper_cutmarks_marksize_choice.setEnabled(False) + + self.prepress_paper_cutmarks_inside_check = QtWidgets.QCheckBox(parent=self.general_prepress_panel) + self.prepress_paper_cutmarks_inside_check.setGeometry(10, 200, 300, 25) + self.prepress_paper_cutmarks_inside_check.setText(_(u"No internal marks")) + self.prepress_paper_cutmarks_inside_check.setChecked(False) + self.prepress_paper_cutmarks_inside_check.setEnabled(False) + self.prepress_paper_cutmarks_inside_check.clicked.connect(self.generate_preview) + + self.imposition_label = QtWidgets.QLabel(parent=self.general_imposition_panel) + self.imposition_label.setGeometry(10, 10, 300, 15) + self.imposition_label.setText(_(u"Amount of impositions").upper()) + self.imposition_label.setFont(QtGui.QFont('Ubuntu', 8)) + + self.imposition_vertical_number_label = QtWidgets.QLabel(parent=self.general_imposition_panel) + self.imposition_vertical_number_label.setGeometry(10, 25, 200, 25) + self.imposition_vertical_number_label.setText(_(u"Lines:")) + + self.imposition_vertical_number_value = QtWidgets.QSpinBox(parent=self.general_imposition_panel) + self.imposition_vertical_number_value.setGeometry(210, 25, 50, 25) + self.imposition_vertical_number_value.setValue(1) + self.imposition_vertical_number_value.setRange(1, 999) + self.imposition_vertical_number_value.editingFinished.connect(self.generate_preview) + + self.imposition_horizontal_number_label = QtWidgets.QLabel(parent=self.general_imposition_panel) + self.imposition_horizontal_number_label.setGeometry(10, 60, 200, 25) + self.imposition_horizontal_number_label.setText(_(u"Columns:")) + + self.imposition_horizontal_number_value = QtWidgets.QSpinBox(parent=self.general_imposition_panel) + self.imposition_horizontal_number_value.setGeometry(210, 60, 50, 25) + self.imposition_horizontal_number_value.setValue(1) + self.imposition_horizontal_number_value.setRange(1, 999) + self.imposition_horizontal_number_value.editingFinished.connect(self.generate_preview) + + self.imposition_space_label = QtWidgets.QLabel(parent=self.general_imposition_panel) + self.imposition_space_label.setGeometry(10, 90, 200, 25) + self.imposition_space_label.setText(_(u"Space between marks:")) + + self.imposition_space_value = QtWidgets.QLineEdit(parent=self.general_imposition_panel) + self.imposition_space_value.setGeometry(210, 90, 50, 25) + self.imposition_space_value.setText('5') + self.imposition_space_value.editingFinished.connect(self.generate_preview) + + self.imposition_space_choice = QtWidgets.QComboBox(parent=self.general_imposition_panel) + self.imposition_space_choice.setGeometry(260,90,50,25) + self.imposition_space_choice.addItems(uuconv.keys()) + self.imposition_space_choice.setCurrentIndex(5) + self.imposition_space_choice.activated.connect(self.generate_preview) + + self.export_button = QtWidgets.QPushButton(QtGui.QIcon.fromTheme("document-export"), _("Export"), parent=self) + self.export_button.setGeometry(740, 560, 200, 30) + self.export_button.setIconSize(QtCore.QSize(20,20)) + self.export_button.clicked.connect(self.export) + + self.change_area_to_export() + self.change_format() + + def generate_preview(self): + if self.format_preview_check.isChecked(): + self.generate_final_file() + + if self.option_box.currentIndex() == 0: + self.preview_original_title.setVisible(True) + self.preview_result_title.setVisible(True) + + final_command = ['convert'] + final_command.append(os.path.join(dirpathTempFolder.name, 'result-imp.') + list_of_export_formats[self.format_choice.currentIndex()].lower()) + + if self.color_profile_choice_jpeg.isChecked(): + final_command.append('-profile') + final_command.append(os.path.join(self.icc_dir_textbox.text(), selected_screen_profile)) + + final_command.append(os.path.join(dirpathTempFolder.name, 'result.png')) + subprocess.Popen(final_command, shell=shell).wait() + + file_info = subprocess.Popen(['identify', os.path.join(dirpathTempFolder.name, 'source.png')], shell=shell, stdout=subprocess.PIPE).communicate()[0].decode('UTF-8') + image_width = int(file_info.split(' ')[2].split('x')[0]) + image_height = int(file_info.split(' ')[2].split('x')[1]) + + marksize = (self.dpi_choice.value() / 96) * unittouu(str(self.prepress_paper_cutmarks_marksize_value.text()) + str(self.prepress_paper_cutmarks_marksize_choice.currentText())) + imposition_space = (self.dpi_choice.value() / 96) * unittouu(str(self.imposition_space_value.text()) + str(self.imposition_space_choice.currentText())) + + file_info = subprocess.Popen(['identify', '-verbose', os.path.join(dirpathTempFolder.name, 'result-imp.') + list_of_export_formats[self.format_choice.currentIndex()].lower()], shell=shell, stdout=subprocess.PIPE).communicate()[0].decode('UTF-8') + file_info_final = '' + for line in file_info.split('\n'): + if ' Format: ' in line: + file_info_final += 'Image Format: ' + line.replace(' Format: ', '') + '
' + if ' Geometry: ' in line: + file_info_final += 'Width and height: ' + line.replace(' Geometry: ', '').split('+')[0] + '
' + if ' Resolution: ' in line: + file_info_final += 'Resolution: ' + line.replace(' Resolution: ', '') + if ' Units: ' in line: + file_info_final += ' ' + line.replace(' Units: ', '').replace('Per', ' per ').replace('Pixels', 'pixels').replace('Centimeter', 'centimeter').replace('Inch', 'inch') + '
' + if ' Colorspace: ' in line: + file_info_final += 'Colorspace: ' + line.replace(' Colorspace: ', '') + '
' + if ' Depth: ' in line: + file_info_final += 'Depth: ' + line.replace(' Depth: ', '') + '
' + if ' Quality: ' in line: + file_info_final += 'Quality: ' + line.replace(' Quality: ', '') + '%
' + if ' Filesize: ' in line: + file_info_final += 'Filesize: ' + line.replace(' Filesize: ', '') + '
' + if ' jpeg:sampling-factor: ' in line: + file_info_final += 'Sampling: ' + line.replace(' jpeg:sampling-factor: ', '') + '
' + + if self.prepress_paper_cutmarks_check.isChecked(): + margin = marksize + else: + margin = imposition_space + + if image_width < 300 or image_height < 300: + what_show = '-extent ' + str(int(300 * self.preview_zoom)) + 'x' + str(int(300 * self.preview_zoom)) + '-' + str(int(150 * self.preview_zoom) - int(image_width / 2)) + '-' + str(int(150 * self.preview_zoom) - int(image_height / 2)) + else: + what_show = '-crop ' + str(int(300 * self.preview_zoom)) + 'x' + str(int(300 * self.preview_zoom)) + '+' + str(int(image_width / 2) - int(150 * self.preview_zoom)) + '+' + str(int(image_height / 2) - int(150 * self.preview_zoom)) + os.system('convert "' + os.path.join(dirpathTempFolder.name, 'source.png') + '" ' + what_show + ' "' + os.path.join(dirpathTempFolder.name, 'original.png') + '"' ) + + if image_width < 300 or image_height < 300: + what_show = '-extent ' + str(int(300 * self.preview_zoom)) + 'x' + str(int(300 * self.preview_zoom)) + '-' + str(int(150 * self.preview_zoom) - int(image_width / 2) - margin) + '-' + str(int(150 * self.preview_zoom) - int(image_height / 2) - margin) + else: + what_show = '-crop ' + str(int(300 * self.preview_zoom)) + 'x' + str(int(300 * self.preview_zoom)) + '+' + str(int(image_width / 2) - int(150 * self.preview_zoom) + margin) + '+' + str(int(image_height / 2) - int(150 * self.preview_zoom) + margin) + + os.system('convert "' + os.path.join(dirpathTempFolder.name, 'result.png') + '" ' + what_show + ' "' + os.path.join(dirpathTempFolder.name, 'result.png') + '"' ) + + if not self.preview_zoom == 1: + os.system('convert "' + os.path.join(dirpathTempFolder.name, 'original.png') + '" -filter box -resize 300x300 "' + os.path.join(dirpathTempFolder.name, 'original.png') + '"' ) + os.system('convert "' + os.path.join(dirpathTempFolder.name, 'result.png') + '" -filter box -resize 300x300 "' + os.path.join(dirpathTempFolder.name, 'result.png') + '"' ) + + os.system( + 'convert "' + + os.path.join(dirpathTempFolder.name, 'original.png') + + '" "' + + os.path.join(dirpathTempFolder.name, 'result.png') + + '" "' + + os.path.join(dirpathSoftware, 'preview_mask.png') + #static file from extension directory + '" -composite "' + + os.path.join(dirpathTempFolder.name, 'preview.png') + + '"' + ) + + self.view_image_info.setText(file_info_final + '
' + list_of_format_tips[list_of_export_formats[self.format_choice.currentIndex()]] + '') + + elif self.option_box.currentIndex() == 1: + self.preview_original_title.setVisible(False) + self.preview_result_title.setVisible(False) + + subprocess.Popen(['convert', os.path.join(dirpathTempFolder.name, 'result-imp.') + list_of_export_formats[self.format_choice.currentIndex()].lower(), '-resize', '300x300', os.path.join(dirpathTempFolder.name, 'preview.png')], shell=shell).wait() + + elif self.option_box.currentIndex() == 2: + None + + elif self.option_box.currentIndex() == 3: + None + + self.preview_bitmap.setPixmap(QtGui.QPixmap(os.path.join(dirpathTempFolder.name, 'preview.png'))) + + def generate_final_file(self): + if list_of_export_formats[self.format_choice.currentIndex()] == 'JPEG': + jpeg_command = ['convert'] + + if not self.cmyk_advanced_manipulation_option_jpeg.isChecked(): + pre_command = ['convert'] + pre_command.append(os.path.join(dirpathTempFolder.name, 'source.tiff')) + + if list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()] == 'CMYK' or list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()] == 'RGB': + if self.color_profile_choice_jpeg.isChecked(): + pre_command.append('-profile') + pre_command.append(os.path.join(self.icc_dir_textbox.text(), selected_screen_profile)) + + if list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()] == 'RGB': + pre_command.append(os.path.join(dirpathTempFolder.name, 'result.tiff')) + subprocess.Popen(pre_command, shell=shell).wait() + del pre_command[:] + pre_command.append('convert') + pre_command.append(os.path.join(dirpathTempFolder.name, 'result.tiff')) + pre_command.append('-profile') + pre_command.append(os.path.join(self.icc_dir_textbox.text(), selected_screen_profile)) + + pre_command.append(os.path.join(dirpathTempFolder.name, 'result.tiff')) + subprocess.Popen(pre_command, shell=shell).wait() + if not os.path.isfile(os.path.join(dirpathTempFolder.name, 'result.tiff')): + inkex.utils.debug("Error. Missing result.tiff") + + else: + if self.color_profile_choice_jpeg.isChecked(): + pre_command = ['convert'] + pre_command.append(os.path.join(dirpathTempFolder.name, 'result.tiff')) + pre_command.append('-profile') + pre_command.append(os.path.join(self.icc_dir_textbox.text(), selected_screen_profile)) + pre_command.append(os.path.join(dirpathTempFolder.name, 'result.tiff')) + subprocess.Popen(pre_command, shell=shell).wait() + + file_info = subprocess.Popen(['identify', os.path.join(dirpathTempFolder.name, 'source.png')], shell=shell, stdout=subprocess.PIPE).communicate()[0].decode('UTF-8') + if self.prepress_paper_cutmarks_check.isChecked(): + bleedsize = (self.dpi_choice.value() / 96) * unittouu(str(self.prepress_paper_cutmarks_bleedsize_value.text()) + str(self.prepress_paper_cutmarks_bleedsize_choice.currentText())) + marksize = (self.dpi_choice.value() / 96) * unittouu(str(self.prepress_paper_cutmarks_marksize_value.text()) + str(self.prepress_paper_cutmarks_marksize_choice.currentText())) + else: + bleedsize = 0 + marksize = 0 + + imposition_space = (self.dpi_choice.value() / 96) *unittouu(str(self.imposition_space_value.text()) + str(self.imposition_space_choice.currentText())) + + image_width = [] + for i in range(self.imposition_vertical_number_value.value()): + image_width.append(int(file_info.split(' ')[2].split('x')[0])) + + image_height = [] + for i in range(self.imposition_horizontal_number_value.value()): + image_height.append(int(file_info.split(' ')[2].split('x')[1])) + + imposition_command = ['convert'] + imposition_command.append(os.path.join(dirpathTempFolder.name, 'result.tiff')) + imposition_command.append('-extent') + imposition_command.append(str(sum(image_width) + (marksize*2) + (imposition_space * (len(image_width) -1))) + 'x' + str(sum(image_height) + (marksize*2) + (imposition_space * (len(image_height) -1))) + '-' + str(marksize) + '-' + str(marksize)) + imposition_command.append(os.path.join(dirpathTempFolder.name, 'result-imp.tiff')) + subprocess.Popen(imposition_command, shell=shell).wait() + + last_width = 0 + last_height = 0 + last_marksize = marksize + for width in image_width: + for height in image_height: + if not (last_width == 0 and last_height == 0): + imposition_command = ['composite'] + imposition_command.append('-geometry') + imposition_command.append('+' + str(last_width + marksize) + '+' + str(last_height + marksize)) + imposition_command.append(os.path.join(dirpathTempFolder.name, 'result.tiff')) + imposition_command.append(os.path.join(dirpathTempFolder.name, 'result-imp.tiff')) + imposition_command.append(os.path.join(dirpathTempFolder.name, 'result-imp.tiff')) + subprocess.Popen(imposition_command, shell=shell).wait() + + last_height += height + imposition_space + last_marksize = 0 + last_width += width + imposition_space + last_height = 0 + + if self.prepress_paper_cutmarks_check.isChecked(): + cutmarks.generate_final_file(False, + self.prepress_paper_cutmarks_inside_check.isChecked(), + list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()], + image_width, + image_height, + imposition_space, + unittouu(str(self.prepress_paper_cutmarks_strokewidth_value.text()) + str(self.prepress_paper_cutmarks_strokewidth_choice.currentText())), + bleedsize, + marksize, + dirpathTempFolder.name) + + cut_marks_command = ['composite'] + cut_marks_command.append('-compose') + cut_marks_command.append('Multiply') + cut_marks_command.append('-gravity') + cut_marks_command.append('center') + cut_marks_command.append(os.path.join(dirpathTempFolder.name, 'cut_mark.tiff')) + cut_marks_command.append(os.path.join(dirpathTempFolder.name, 'result-imp.tiff')) + cut_marks_command.append(os.path.join(dirpathTempFolder.name, 'result-imp.tiff')) + subprocess.Popen(cut_marks_command, shell=shell).wait() + + if not os.path.isfile(os.path.join(dirpathTempFolder.name, 'result-imp.tiff')): + inkex.utils.debug("Error. Missing result-imp.tiff") + + jpeg_command.append(os.path.join(dirpathTempFolder.name, 'result-imp.tiff')) + + if self.prepress_paper_settings_invert.isChecked(): + jpeg_command.append('-negate') + + if self.prepress_paper_settings_mirror.isChecked(): + jpeg_command.append('-flop') + + jpeg_command.append('-quality') + jpeg_command.append(str(self.quality_choice_dial_jpeg.value())) + + jpeg_command.append('-colorspace') + jpeg_command.append(list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()]) + + if self.jpeg_interlace_option_jpeg.isChecked(): + jpeg_command.append('-interlace') + jpeg_command.append(list_of_interlacing_jpeg[self.jpeg_interlace_choice_jpeg.currentText()]) + + if self.jpeg_optimize_option_jpeg.isChecked(): + jpeg_command.append('-type') + jpeg_command.append('optimize') + + if self.jpeg_noise_option_jpeg.isChecked(): + jpeg_command.append('-evaluate') + jpeg_command.append(list_of_noise_jpeg[self.jpeg_noise_choice_jpeg.currentText()]) + jpeg_command.append(str(self.jpeg_noise_ammount_jpeg.value())) + + jpeg_command.append('-sampling-factor') + jpeg_command.append(self.jpeg_subsampling_choice_jpeg.currentText()) + + jpeg_command.append('-define') + jpeg_command.append('jpeg:dct-method=' + list_of_dct_jpeg[self.jpeg_dct_choice_jpeg.currentText()]) + + jpeg_command.append(os.path.join(dirpathTempFolder.name, 'result-imp.jpeg')) + + subprocess.Popen(jpeg_command, shell=shell).wait() + + def change_format(self): + self.general_options_panel_jpeg.setVisible(False) + + if list_of_export_formats[self.format_choice.currentIndex()] == 'JPEG': + self.general_options_panel_jpeg.setVisible(True) + + self.generate_preview() + + def change_color_mode_jpeg(self): + if list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()] == 'CMYK': + self.color_mode_title_tip_jpeg.setText(u'Recommended for graphic printing') + self.cmyk_advanced_manipulation_option_jpeg.setChecked(False) + self.cmyk_advanced_manipulation_option_jpeg.setEnabled(True) + self.cmyk_overblack_jpeg.setEnabled(False) + self.cmyk_overblack_jpeg.setChecked(False) + self.color_profile_choice_jpeg.setEnabled(True) + self.color_profile_choice_jpeg.setChecked(False) + self.document_color_profile_title_jpeg.setEnabled(True) + self.general_prepress_panel.setEnabled(True) + else: + self.cmyk_advanced_manipulation_option_jpeg.setEnabled(False) + self.cmyk_overblack_jpeg.setEnabled(False) + self.cmyk_overblack_jpeg.setChecked(False) + #self.color_profile_choice_jpeg.setEnabled(False) + self.color_profile_choice_jpeg.setChecked(False) + self.document_color_profile_title_jpeg.setEnabled(False) + self.general_prepress_panel.setEnabled(False) + if list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()] == 'CMY': + self.color_mode_title_tip_jpeg.setText(u'Recommended for specific print cases') + elif list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()] == 'RGB': + self.color_mode_title_tip_jpeg.setText(u'Recommended for use on screens') + elif list_of_color_modes_jpeg[self.color_mode_choice_jpeg.currentIndex()] == 'Gray': + self.color_mode_title_tip_jpeg.setText(u'Grayscale image') + + self.generate_preview() + + def change_quality_live_jpeg(self): + self.quality_percent_title_jpeg.setText(str(self.quality_choice_dial_jpeg.value()) + '%') + + def jpeg_interlace_click_jpeg(self): + if self.jpeg_interlace_option_jpeg.isChecked(): + self.jpeg_interlace_choice_jpeg.setEnabled(True) + else: + self.jpeg_interlace_choice_jpeg.setEnabled(False) + self.generate_preview() + + def jpeg_noise_click_jpeg(self): + if self.jpeg_noise_option_jpeg.isChecked(): + self.jpeg_noise_choice_jpeg.setEnabled(True) + self.jpeg_noise_ammount_jpeg.setEnabled(True) + else: + self.jpeg_noise_choice_jpeg.setEnabled(False) + self.jpeg_noise_ammount_jpeg.setEnabled(False) + self.generate_preview() + + def cmyk_advanced_manipulation_click_jpeg(self): + if self.cmyk_advanced_manipulation_option_jpeg.isChecked(): + self.cmyk_overblack_jpeg.setEnabled(True) + self.view_c_button.setVisible(True) + self.view_m_button.setVisible(True) + self.view_y_button.setVisible(True) + self.view_k_button.setVisible(True) + self.cmyk_overprint_black() + self.cmyk_advanced_manipulation() + + else: + self.cmyk_overblack_jpeg.setEnabled(False) + self.cmyk_overblack_jpeg.setChecked(False) + self.view_c_button.setVisible(False) + self.view_m_button.setVisible(False) + self.view_y_button.setVisible(False) + self.view_k_button.setVisible(False) + self.generate_preview() + + def cmyk_overprint_black(self): + with open(os.path.join(dirpathTempFolder.name, 'original.svg'), 'r') as f: + if self.cmyk_overblack_jpeg.isChecked(): + cmyk.generate_svg_separations(dirpathTempFolder.name, f.read(), True) + else: + cmyk.generate_svg_separations(dirpathTempFolder.name, f.read(), False) + + def cmyk_advanced_manipulation(self): + area_to_export = self.area_to_export() + cmyk.generate_png_separations(dirpathTempFolder.name, self.area_to_export(), self.dpi_choice.value(), False) + + for color in ['C', 'M', 'Y', 'K']: + cmd = ['convert', + os.path.join(dirpathTempFolder.name, 'separated' + area_to_export.replace(' ', '') + color + ".png"), + '-colorspace', + 'CMYK', + '-channel', + color, + '-separate', + os.path.join(dirpathTempFolder.name, 'separated' + area_to_export.replace(' ', '') + color + ".png")] + #inkex.utils.debug(cmd) + p = subprocess.Popen(cmd, shell=shell).wait() + + self.cmyk_advanced_manipulation_view_separations() + + def cmyk_advanced_manipulation_view_separations(self): + area_to_export = self.area_to_export() + + file_info = subprocess.Popen(['identify', os.path.join(dirpathTempFolder.name, 'source.png')], shell=shell, stdout=subprocess.PIPE).communicate()[0].decode('UTF-8') + + image_size = file_info.split(' ')[2] + + subprocess.Popen(['convert', '-size', image_size, 'xc:black', os.path.join(dirpathTempFolder.name, 'empty.png')], shell=shell).wait() + + final_command = ['convert'] + + if self.view_c_button.isChecked(): + final_command.append(os.path.join(dirpathTempFolder.name, 'separated') + area_to_export.replace(' ', '') + 'C' + ".png") + else: + final_command.append(os.path.join(dirpathTempFolder.name, 'empty.png')) + + if self.view_m_button.isChecked(): + final_command.append(os.path.join(dirpathTempFolder.name, 'separated') + area_to_export.replace(' ', '') + 'M' + ".png") + else: + final_command.append(os.path.join(dirpathTempFolder.name, 'empty.png')) + + if self.view_y_button.isChecked(): + final_command.append(os.path.join(dirpathTempFolder.name, 'separated') + area_to_export.replace(' ', '') + 'Y' + ".png") + else: + final_command.append(os.path.join(dirpathTempFolder.name, 'empty.png')) + + if self.view_k_button.isChecked(): + final_command.append(os.path.join(dirpathTempFolder.name, 'separated') + area_to_export.replace(' ', '') + 'K' + ".png") + else: + final_command.append(os.path.join(dirpathTempFolder.name, 'empty.png')) + + if not os.path.isfile(os.path.join(dirpathTempFolder.name, 'empty.png')): + inkex.utils.debug("Error. Missing empty.png") + + final_command.extend(['-set', 'colorspace', 'cmyk']) + final_command.extend(['-combine', os.path.join(dirpathTempFolder.name, 'result.tiff')]) + subprocess.Popen(final_command, shell=shell).wait() + + self.generate_preview() + + def area_to_export(self): + if self.area_to_export_choice.currentIndex() == 1: + return 'export-area-drawing' + + elif self.area_to_export_choice.currentIndex() == 2: + if self.area_to_export_idonly_check.isChecked(): + return 'export-id:' + str(self.area_to_export_id_name.text()) + 'export-id-only' + else: + return 'export-id:' + str(self.area_to_export_id_name.text()) + + elif self.area_to_export_choice.currentIndex() == 3: + return 'export-area:' + str(self.x0_value.value()) + ':' + str(self.y0_value.value()) + ':' + str(self.x1_value.value()) + ':' + str(self.y1_value.value()) + + else: + return 'export-area-page' + + def change_area_to_export(self): + self.x0_value.setVisible(False) + self.y0_value.setVisible(False) + self.x1_value.setVisible(False) + self.y1_value.setVisible(False) + self.area_to_export_id_title.setVisible(False) + self.area_to_export_id_name.setVisible(False) + self.area_to_export_idonly_check.setVisible(False) + + if self.area_to_export_choice.currentIndex() == 2: + self.area_to_export_id_name.setText(selected_object) + self.area_to_export_id_title.setVisible(True) + self.area_to_export_id_name.setVisible(True) + self.area_to_export_idonly_check.setVisible(True) + + elif self.area_to_export_choice.currentIndex() == 3: + self.x0_value.setVisible(True) + self.y0_value.setVisible(True) + self.x1_value.setVisible(True) + self.y1_value.setVisible(True) + + cmd = self.area_to_export() + ';export-dpi:' + str(self.dpi_choice.value()) + ';export-background-opacity:1;export-filename:' + os.path.join(dirpathTempFolder.name, 'source.png') + ';export-do' + cli_output = inkscape(os.path.join(dirpathTempFolder.name, 'original.svg'), actions=cmd) + #inkex.utils.debug(cmd) + if len(cli_output) > 0: + self.debug(_("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")) + self.debug(cli_output) + + subprocess.Popen(['convert', os.path.join(dirpathTempFolder.name, 'source.png'), os.path.join(dirpathTempFolder.name, 'source.tiff')], shell=shell).wait() + + if not os.path.isfile(os.path.join(dirpathTempFolder.name, 'source.tiff')): + inkex.utils.debug("Error. Missing source.tiff") + + self.generate_preview() + + def zoom_out(self): + self.preview_zoom += 0.1 + self.generate_preview() + + if int(self.preview_zoom * 100) == 200: + self.zoom_out_button.setEnabled(False) + self.zoom_in_button.setEnabled(True) + + self.preview_zoom_title.setText(str(int(self.preview_zoom * 100)) + '%') + + def zoom_in(self): + self.preview_zoom -= 0.1 + self.generate_preview() + + if int(self.preview_zoom * 100) == 10: + self.zoom_in_button.setEnabled(False) + self.zoom_out_button.setEnabled(True) + + self.preview_zoom_title.setText(str(int(self.preview_zoom * 100)) + '%') + + def cut_marks_insert_change(self): + if self.prepress_paper_cutmarks_check.isChecked(): + self.prepress_paper_cutmarks_strokewidth_label.setEnabled(True) + self.prepress_paper_cutmarks_strokewidth_value.setEnabled(True) + self.prepress_paper_cutmarks_strokewidth_choice.setEnabled(True) + self.prepress_paper_cutmarks_bleedsize_label.setEnabled(True) + self.prepress_paper_cutmarks_bleedsize_value.setEnabled(True) + self.prepress_paper_cutmarks_bleedsize_choice.setEnabled(True) + self.prepress_paper_cutmarks_marksize_label.setEnabled(True) + self.prepress_paper_cutmarks_marksize_value.setEnabled(True) + self.prepress_paper_cutmarks_marksize_choice.setEnabled(True) + self.prepress_paper_cutmarks_inside_check.setEnabled(True) + + else: + self.prepress_paper_cutmarks_strokewidth_label.setEnabled(False) + self.prepress_paper_cutmarks_strokewidth_value.setEnabled(False) + self.prepress_paper_cutmarks_strokewidth_choice.setEnabled(False) + self.prepress_paper_cutmarks_bleedsize_label.setEnabled(False) + self.prepress_paper_cutmarks_bleedsize_value.setEnabled(False) + self.prepress_paper_cutmarks_bleedsize_choice.setEnabled(False) + self.prepress_paper_cutmarks_marksize_label.setEnabled(False) + self.prepress_paper_cutmarks_marksize_value.setEnabled(False) + self.prepress_paper_cutmarks_marksize_choice.setEnabled(False) + self.prepress_paper_cutmarks_inside_check.setEnabled(False) + + self.generate_preview() + + def format_preview_change(self): + if self.format_preview_check.isChecked(): + self.resize(950, 600) + self.setMaximumSize(QtCore.QSize(950, 600)) + self.setMinimumSize(QtCore.QSize(950, 600)) + self.preview_panel.setVisible(True) + self.option_box.setGeometry(320, 120, 620, 435) + self.format_title.setGeometry(320, 70, 200, 15) + self.format_choice.setGeometry(320, 85, 200, 25) + self.export_button.setGeometry(740, 560, 200, 30) + self.format_preview_check.setGeometry(540, 85, 200, 25) + else: + self.resize(640, 600) + self.setMaximumSize(QtCore.QSize(640, 600)) + self.setMinimumSize(QtCore.QSize(640, 600)) + self.preview_panel.setVisible(False) + self.option_box.setGeometry(10, 120, 620, 435) + self.format_title.setGeometry(10, 70, 200, 15) + self.format_choice.setGeometry(10, 85, 200, 25) + self.export_button.setGeometry(430, 560, 200, 30) + self.format_preview_check.setGeometry(230, 85, 200, 25) + + self.move(int((QtWidgets.QDesktopWidget().screenGeometry().width()-self.geometry().width())/2), int((QtWidgets.QDesktopWidget().screenGeometry().height()-self.geometry().height())/2)) + + def export(self): + self.location_path = QtWidgets.QFileDialog.getSaveFileName(self, _(u"Save image"), os.environ.get('HOME', None), list_of_export_formats[self.format_choice.currentIndex()], options=QtWidgets.QFileDialog.DontConfirmOverwrite) + + if not self.format_preview_check.isChecked(): + self.generate_final_file() + + if not str(self.location_path) == '': + result_imp = os.path.join(dirpathTempFolder.name, 'result-imp.' + list_of_export_formats[self.format_choice.currentIndex()].lower()) + target_imp = os.path.abspath(self.location_path[0] + "." + self.location_path[1].lower()) + if not os.path.isfile(result_imp): + inkex.utils.debug("Error. No result generated to export. The following files were created in temp dir:") + inkex.utils.debug(os.listdir(dirpathTempFolder.name)) + else: + shutil.copy2(result_imp, target_imp) + + def change_icc_dir(self): + self.icc_dir_textbox.setText(QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Folder')) + + app = QtWidgets.QApplication(sys.argv) + app.main = mainWindow() + getattr(app.main, "raise")() + app.main.show() + app.main.activateWindow() #bring to front (required for Windows; but not for Linux) + sys.exit(app.exec_()) + + + except Exception as e: + self.msg(e) + finally: + #inkex.utils.debug(os.listdir(dirpathTempFolder.name)) + dirpathTempFolder.cleanup() #close temp dir + +if __name__ == '__main__': + OutputPro().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/output_pro/outputpro/__init__.py b/extensions/fablabchemnitz/output_pro/outputpro/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extensions/fablabchemnitz/output_pro/outputpro/alpha.png b/extensions/fablabchemnitz/output_pro/outputpro/alpha.png new file mode 100644 index 0000000..c30c1c2 Binary files /dev/null and b/extensions/fablabchemnitz/output_pro/outputpro/alpha.png differ diff --git a/extensions/fablabchemnitz/output_pro/outputpro/cmyk.py b/extensions/fablabchemnitz/output_pro/outputpro/cmyk.py new file mode 100644 index 0000000..d939175 --- /dev/null +++ b/extensions/fablabchemnitz/output_pro/outputpro/cmyk.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +import re +import os +import inkex +from inkex.command import inkscape + +def calculateCMYK(red, green, blue): + C = float() + M = float() + Y = float() + K = float() + + if 1.00 - red < 1.00 - green: + K = 1.00 - red + else: + K = 1.00 - green + + if 1.00 - blue < K: + K = 1.00 - blue + + if K != 1.00: + C = ( 1.00 - red - K ) / ( 1.00 - K ) + M = ( 1.00 - green - K ) / ( 1.00 - K ) + Y = ( 1.00 - blue - K ) / ( 1.00 - K ) + + return [C, M, Y, K] + +def clean_svg_color_definitions(svg): + def change_colors(origin, color_type): + for i in range(len(str(origin).split(color_type + ':'))): + if str(str(origin).split(color_type + ':')[i].split(';')[0]) in inkex.colors.SVG_COLOR.keys(): + color_numbers = str(inkex.Color(inkex.Color(str(str(origin).split(color_type + ':')[i].split(';')[0])).to_rgb())) + origin = str(origin).replace(':' + str(str(origin).split(color_type + ':')[i].split(';')[0]) + ';', ':' + color_numbers + ';') + return origin + + colortypes = ['fill', 'stop-color', 'flood-color', 'lighting-color', 'stroke'] + for i in range(len(colortypes)): + svg = change_colors(svg, colortypes[i]) + + return svg + +def removeK(origin): + def reset_opacity(value): + return str(value.group()).split('opacity:')[0] + "opacity:0;" + #return re.sub("#000000;fill-opacity:[0-9.]+;", reset_opacity, re.sub("#000000;stop-opacity:[0-9.]+;", reset_opacity, re.sub("#000000;stroke-opacity:[0-9.]+;", reset_opacity, re.sub("#000000;flood-opacity:[0-9.]+;", reset_opacity, re.sub("#000000;lighting-opacity:[0-9.]+;", reset_opacity, origin))))) + return re.sub("#000000;fill-opacity:[0-9.?]+", reset_opacity, re.sub("#000000;stop-opacity:[0-9.?]+", reset_opacity, re.sub("#000000;stroke-opacity:[0-9.?]+", reset_opacity, re.sub("#000000;flood-opacity:[0-9.?]+", reset_opacity, re.sub("#000000;lighting-opacity:[0-9.?]+", reset_opacity, origin))))) + +def representC(value): + # returns CMS color if available + if (re.search("icc-color", value.group())): + return str(inkex.Color((float(1.00 - float(re.split(r'[,\)\s]+',value.group())[2])), float(1.00), float(1.00)))) + else: + red = float(inkex.Color(str(value.group())).to_rgb()[0]/255.00) + green = float(inkex.Color(str(value.group())).to_rgb()[1]/255.00) + blue = float(inkex.Color(str(value.group())).to_rgb()[2]/255.00) + return str(inkex.Color((float(1.00 - calculateCMYK(red, green, blue)[0]), float(1.00), float(1.00)))) + +def representM(value): + # returns CMS color if available + if ( re.search("icc-color", value.group()) ): + return str(inkex.Color((float(1.00), float(1.00 - float(re.split(r'[,\)\s]+',value.group())[3])), float(1.00)))) + else: + red = float(inkex.Color(str(value.group())).to_rgb()[0]/255.00) + green = float(inkex.Color(str(value.group())).to_rgb()[1]/255.00) + blue = float(inkex.Color(str(value.group())).to_rgb()[2]/255.00) + return str(inkex.Color((float(1.00), float(1.00 - calculateCMYK(red, green, blue)[1]), float(1.00)))) + +def representY(value): + # returns CMS color if available + if (re.search("icc-color", value.group()) ): + return str(inkex.Color((float(1.00), float(1.00), float(1.00 - float(re.split(r'[,\)\s]+',value.group())[4]))))) + else: + red = float(inkex.Color(str(value.group())).to_rgb()[0]/255.00) + green = float(inkex.Color(str(value.group())).to_rgb()[1]/255.00) + blue = float(inkex.Color(str(value.group())).to_rgb()[2]/255.00) + return str(inkex.Color((float(1.00), float(1.00), float(1.00 - calculateCMYK(red, green, blue)[2])))) + +def representK(value): + # returns CMS color if available + if (re.search("icc-color", value.group()) ): + return str(inkex.Color((float(1.00 - float(re.split(r'[,\)\s]+',value.group())[5])), float(1.00 - float(re.split(r'[,\)\s]+',value.group())[5])), float(1.00 - float(re.split(r'[,\)\s]+',value.group())[5]))))) + else: + red = float(inkex.Color(str(value.group())).to_rgb()[0]/255.00) + green = float(inkex.Color(str(value.group())).to_rgb()[1]/255.00) + blue = float(inkex.Color(str(value.group())).to_rgb()[2]/255.00) + return str(inkex.Color((float(1.00 - calculateCMYK(red, green, blue)[3]), float(1.00 - calculateCMYK(red, green, blue)[3]), float(1.00 - calculateCMYK(red, green, blue)[3])))) + + +def generate_svg_separations(temp_dir, original_source, overblack): + svg_ready = clean_svg_color_definitions(original_source) + + with open(os.path.join(temp_dir, "separationK.svg"), "w") as f: + f.write(re.sub(r"#[a-fA-F0-9]{6}( icc-color\(.*?\))?", representK, svg_ready)) + + if overblack: + svg_ready = removeK(svg_ready) + + with open(os.path.join(temp_dir, "separationC.svg"), "w") as f: + f.write(re.sub(r"#[a-fA-F0-9]{6}( icc-color\(.*?\))?", representC, svg_ready)) + with open(os.path.join(temp_dir, "separationM.svg"), "w") as f: + f.write(re.sub(r"#[a-fA-F0-9]{6}( icc-color\(.*?\))?", representM, svg_ready)) + with open(os.path.join(temp_dir, "separationY.svg"), "w") as f: + f.write(re.sub(r"#[a-fA-F0-9]{6}( icc-color\(.*?\))?", representY, svg_ready)) + +def generate_png_separations(temp_dir, area_to_export, resolution, alpha): + if alpha: + alpha_command = "" + else: + alpha_command = ";export-background:white" + for color in ['C', 'M', 'Y', 'K']: + cmd = area_to_export + alpha_command + ';export-dpi:' + str(resolution) + ';export-background-opacity:1;export-filename:' + os.path.join(temp_dir, "separated" + area_to_export.replace(' ', '') + color + ".png") + ';export-do' + #inkex.utils.debug(cmd) + cli_output = inkscape(os.path.join(temp_dir, "separation" + color + ".svg"), actions=cmd) + if len(cli_output) > 0: + inkex.utils.debug(cli_output) + #inkex.utils.debug(os.listdir(temp_dir)) + \ No newline at end of file diff --git a/extensions/fablabchemnitz/output_pro/outputpro/cutmarks.py b/extensions/fablabchemnitz/output_pro/outputpro/cutmarks.py new file mode 100644 index 0000000..5ddbf16 --- /dev/null +++ b/extensions/fablabchemnitz/output_pro/outputpro/cutmarks.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +import subprocess +import os + +def generate_final_file(isvector, hide_inside_marks, colormode, width, height, space, strokewidth, bleedsize, marksize, temp_dir): + if not isvector: + + if "nt" in os.name: + shell = True + else: + shell = False + + command = [] + final_command = ['convert'] + + for color in colormode: + command.append('convert') + command.append('-size') + command.append(str(sum(width) + (marksize*2) + (space * (len(width) -1))) + 'x' + str(sum(height) + (marksize*2) + (space * (len(height) -1)))) + command.append('xc:white') + command.append('-stroke') + command.append('black') + command.append('-strokewidth') + command.append(str(strokewidth)) + + width_value = 0 + number_of_column = 1 + + for column in width: + height_value = 0 + number_of_line = 1 + + for line in height: + with open(os.path.join(temp_dir, 'str.txt'), 'a') as f: + f.write(str(width.index(column))) + + if not hide_inside_marks or (hide_inside_marks and number_of_column == 1): + command.append('-draw') + command.append('line ' + str(width_value + marksize) + ',' + str(height_value + marksize + bleedsize) + ', ' + str(width_value) + ',' + str(height_value + marksize + bleedsize)) + command.append('-draw') + command.append('line ' + str(width_value + marksize) + ',' + str(height_value + line + marksize - bleedsize) + ', ' + str(width_value) + ',' + str(height_value + line + marksize - bleedsize)) + + if not hide_inside_marks or (hide_inside_marks and number_of_line == 1): + command.append('-draw') + command.append('line ' + str(width_value + marksize + bleedsize) + ',' + str(height_value + marksize) + ', ' + str(width_value + marksize + bleedsize) + ',' + str(height_value)) + command.append('-draw') + command.append('line ' + str(width_value + column + marksize - bleedsize) + ',' + str(height_value + marksize) + ', ' + str(width_value + column + marksize - bleedsize) + ',' + str(height_value)) + + if not hide_inside_marks or (hide_inside_marks and number_of_column == len(width)): + command.append('-draw') + command.append('line ' + str(width_value + marksize + column) + ',' + str(height_value + marksize + bleedsize) + ', ' + str(width_value + (marksize*2) + column) + ',' + str(height_value + marksize + bleedsize)) + command.append('-draw') + command.append('line ' + str(width_value + marksize + column) + ',' + str(height_value + line + marksize - bleedsize) + ', ' + str(width_value + (marksize*2) + column) + ',' + str(height_value + marksize + line - bleedsize)) + + if not hide_inside_marks or (hide_inside_marks and number_of_line == len(height)): + command.append('-draw') + command.append('line ' + str(width_value + marksize + bleedsize) + ',' + str(height_value + line + marksize) + ', ' + str(width_value + marksize + bleedsize) + ',' + str(height_value + line + (marksize*2))) + command.append('-draw') + command.append('line ' + str(width_value + column + marksize - bleedsize) + ',' + str(height_value + line + marksize) + ', ' + str(width_value + marksize + column - bleedsize) + ',' + str(height_value + line + (marksize*2))) + + height_value += line + space + number_of_line += 1 + width_value += column + space + number_of_column += 1 + command.append(os.path.join(temp_dir, 'cut_mark_' + color + '.png')) + subprocess.Popen(command, shell=shell).wait() + del command[:] + + command.append('convert') + command.append(os.path.join(temp_dir, 'cut_mark_' + color + '.png')) + command.append('-colorspace') + command.append(str(colormode).lower()) + command.append('-channel') + command.append('K') + command.append('-separate') + command.append(os.path.join(temp_dir, 'cut_mark_' + color + '.png')) + subprocess.Popen(command, shell=shell).wait() + del command[:] + + final_command.append(os.path.join(temp_dir, 'cut_mark_' + color + '.png')) + + final_command.extend(['-set', 'colorspace', colormode, '-combine', os.path.join(temp_dir, 'cut_mark.tiff')]) + subprocess.Popen(final_command, shell=shell).wait() diff --git a/extensions/fablabchemnitz/output_pro/outputpro/preview_mask.png b/extensions/fablabchemnitz/output_pro/outputpro/preview_mask.png new file mode 100644 index 0000000..9932f77 Binary files /dev/null and b/extensions/fablabchemnitz/output_pro/outputpro/preview_mask.png differ diff --git a/extensions/fablabchemnitz/output_pro/outputpro/top.png b/extensions/fablabchemnitz/output_pro/outputpro/top.png new file mode 100644 index 0000000..25dce74 Binary files /dev/null and b/extensions/fablabchemnitz/output_pro/outputpro/top.png differ diff --git a/extensions/fablabchemnitz/output_pro/result-imp.jpeg b/extensions/fablabchemnitz/output_pro/result-imp.jpeg new file mode 100644 index 0000000..e551156 Binary files /dev/null and b/extensions/fablabchemnitz/output_pro/result-imp.jpeg differ diff --git a/extensions/fablabchemnitz/paths_to_openscad/meta.json b/extensions/fablabchemnitz/paths_to_openscad/meta.json new file mode 100644 index 0000000..187a392 --- /dev/null +++ b/extensions/fablabchemnitz/paths_to_openscad/meta.json @@ -0,0 +1,22 @@ +[ + { + "name": "Paths To OpenSCAD", + "id": "fablabchemnitz.de.paths_to_openscad", + "path": "paths_to_openscad", + "dependent_extensions": null, + "original_name": "aths to OpenSCAD<", + "original_id": "command.extrude.openscad", + "license": "GNU GPL v2", + "license_url": "https://github.com/fablabnbg/inkscape-paths2openscad/blob/master/LICENSE", + "comment": "", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/paths_to_openscad", + "fork_url": "https://github.com/fablabnbg/inkscape-paths2openscad", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Paths+to+OpenSCAD", + "inkscape_gallery_url": null, + "main_authors": [ + "github.com/l0b0", + "github.com/fablabnbg", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/paths_to_openscad/paths_to_openscad.inx b/extensions/fablabchemnitz/paths_to_openscad/paths_to_openscad.inx new file mode 100644 index 0000000..49922eb --- /dev/null +++ b/extensions/fablabchemnitz/paths_to_openscad/paths_to_openscad.inx @@ -0,0 +1,95 @@ + + + Paths To OpenSCAD + fablabchemnitz.de.paths_to_openscad + + + {NAME}.scad + 5.0 + true + false + false + false + false + + + 0.2 + 0 + + + + + + + + + 1.0 + 100.0 + + + + + + + false + + + + + openscad "{NAME}.scad" + openscad "{NAME}.scad" -o "{NAME}.stl" + cura "{NAME}.stl" & + + + + + + + + + + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/paths_to_openscad/paths_to_openscad.py b/extensions/fablabchemnitz/paths_to_openscad/paths_to_openscad.py new file mode 100644 index 0000000..94fc69e --- /dev/null +++ b/extensions/fablabchemnitz/paths_to_openscad/paths_to_openscad.py @@ -0,0 +1,1487 @@ +#!/usr/bin/env python3 +# +# paths2openscad.py +# +# This is an Inkscape extension to output paths to extruded OpenSCAD polygons +# The Inkscape objects must first be converted to paths (Path > Object to +# Path). Some paths may not work well -- the paths have to be polygons. As +# such, paths derived from text may meet with mixed results. + +# Written by Daniel C. Newman ( dan dot newman at mtbaldy dot us ) +# +# 2020-06-18 +# Updated by Sillyfrog (https://github.com/sillyfrog) to support +# Inkscape v1.0 (exclusively, prior versions) are no longer supported). +# Updated to run under python3 now python2 is end of life. +# +# 10 June 2012 +# +# 15 June 2012 +# Updated by Dan Newman to handle a single level of polygon nesting. +# This is sufficient to handle most fonts. +# If you want to nest two polygons, combine them into a single path +# within Inkscape with "Path > Combine Path". +# +# 15 August 2014 +# Updated by Josef Skladanka to automatically set extruded heights +# +# 2017-03-11, juergen@fabmail.org +# 0.12 parse svg width="400mm" correctly. Came out downscaled by 3... +# +# 2017-04-08, juergen@fabmail.org +# 0.13 allow letter 'a' prefix on zsize values for anti-matter. +# All anti-matter objects are subtracted from all normal objects. +# raise: Offset along Z axis, to make cut-outs and balconies. +# Refactored object_merge_extrusion_values() from convertPath(). +# Inheriting extrusion values from enclosing groups. +# +# 2017-04-10, juergen@fabmail.org +# 0.14 Started merging V7 outline mode by Neon22. +# (http://www.thingiverse.com/thing:1065500) +# Toplevel object from http://www.thingiverse.com/thing:1286041 +# is already included. +# +# 2017-04-16, juergen@fabmail.org +# 0.15 Fixed https://github.com/fablabnbg/inkscape-paths2openscad/ +# issues/1#issuecomment-294257592 +# Line width of V7 code became a minimum line width, +# rendering is now based on stroke-width +# Refactored LengthWithUnit() from getLength() +# Finished merge with v7 code. +# Subpath in subpath are now handled very nicely. +# Added msg_extrude_by_hull_and_paths() outline mode with nested paths. +# +# 2017-06-12, juergen@fabmail.org +# 0.16 Feature added: scale: XXX to taper the object while extruding. + +# 2017-06-15, juergen@fabmail.org +# 0.17 scale is now centered on each path. and supports an optional second +# value for explicit Y scaling. Renamed the autoheight command line +# option to 'parsedesc' with default true. Renamed dict auto to +# extrusion. Rephrased all prose to refer to extrusion syntax rather +# than auto zsize. +# 2017-06-18, juergen@fabmail.org +# 0.18 pep8 relaxed. all hard 80 cols line breaks removed. +# Refactored the commands into a separate tab in the inx. +# Added 'View in OpenSCAD' feature with pidfile for single instance. +# +# 2017-08-10, juergen@fabmail.org +# 0.19 fix style="" elements. +# +# 2017-11-14, juergen@fabmail.org +# 0.20 do not traverse into objects with style="display:none" +# some precondition checks had 'pass' but should have 'continue'. +# +# 2018-01-21, juergen@fabmail.org +# 0.21 start a new openscad instance if the command has changed. +# +# 2018-01-27, juergen@fabmail.org +# 0.22 command comparison fixed. do not use 0.21 ! +# +# 2018-02-18, juergen@fabmail.org +# 0.23 fixed rect with x=0 not rendered. +# FIXME: should really use inksvg.py here too! +# +# 2018.09-09, juergen@fabmail.org +# 0.24 merged module feature, renamed Height,Raise to Zsize,Zoffset +# +# 2019-01-18, juergen@fabmail.org +# 0.25 Allow Depth,Offset instead of Zsize,Zoffset +# Simplify the syntax description page. +# Added parameter line_width_scale. +# Added parameter chamfer, and module chamfer_sphere for doing minkowski +# +# 2020-03-12, juergen@fabmail.org +# 0.26 DEB: relax dependency on 'openscad' to 'openscad | bash' +# +# 2020-04-05, juergen@fabmail.org +# 0.27 Make pep8 happy again. Give proper error message, when file was not saved. +# +# CAUTION: keep the version number in sync with paths2openscad.inx about page + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import sys +import os.path +import inkex +import inkex.paths +import inkex.bezier +from inkex.transforms import Transform +import re +import time +import string +import tempfile +import gettext +import subprocess + +VERSION = "0.27" # CAUTION: Keep in sync with all *.inx files +DEFAULT_WIDTH = 100 +DEFAULT_HEIGHT = 100 +# Parse all these as 56.7 mm zsize: +# "path1234_56_7_mm", "pat1234____57.7mm", "path1234_57.7__mm" +# +# The verbs Height and Raise are deprecated. Use Zsize and Zoffset, (or Depth and Offset) instead. +RE_AUTO_ZSIZE_ID = re.compile(r".*?_+([aA]?\d+(?:[_\.]\d+)?)_*mm$") +RE_AUTO_ZSIZE_DESC = re.compile( + r"^(?:[Hh]eight|[Dd]epth|[Zz]-?size):\s*([aA]?\d+(?:\.\d+)?) ?mm$", re.MULTILINE +) +RE_AUTO_SCALE_DESC = re.compile( + r"^(?:sc|[Ss]cale|[Tt]aper):\s*(\d+(?:\.\d+)?(?: ?, ?\d+(?:\.\d+)?)?) ?%$", + re.MULTILINE, +) +RE_AUTO_ZOFFSET_DESC = re.compile( + r"^(?:[Rr]aise|[Zz]-?offset|[Oo]ffset):\s*(\d+(?:\.\d+)?) ?mm$", re.MULTILINE +) +DESC_TAGS = ["desc", inkex.addNS("desc", "svg")] + +# CAUTION: keep these defaults in sync with paths2openscad.inx +INX_SCADVIEW = os.getenv("INX_SCADVIEW", "openscad \"{NAME}.scad\"") +INX_SCAD2STL = os.getenv("INX_SCAD2STL", "openscad \"{NAME}.scad\" -o \"{NAME}.stl\"") +INX_STL_POSTPROCESSING = os.getenv("INX_STL_POSTPROCESSING", "cura \"{NAME}.stl\" &") + + +def IsProcessRunning(pid): + """ + Windows code from https://stackoverflow.com/questions/7647167/check-if-a-process-is-running-in-python-in-linux-unix + """ + sys_platform = sys.platform.lower() + if sys_platform.startswith("win"): + with subprocess.Popen(r'tasklist.exe /NH /FI "PID eq %d"' % (pid), shell=True, stdout=subprocess.PIPE) as ps: + output = ps.stdout.read() + ps.wait() + if str(pid) in output: + return True + return False + else: + # OSX sys_platform.startswith('darwin'): + # and Linux + try: + os.kill(pid, 0) + return True + except OSError: + return False + + +def parseLengthWithUnits(str, default_unit="px"): + """ + Parse an SVG value which may or may not have units attached + This version is greatly simplified in that it only allows: no units, + units of px, and units of %. Everything else, it returns None for. + There is a more general routine to consider in scour.py if more + generality is ever needed. + With inkscape 0.91 we need other units too: e.g. svg:width="400mm" + """ + + u = default_unit + s = str.strip() + if s[-2:] in ("px", "pt", "pc", "mm", "cm", "in", "ft"): + u = s[-2:] + s = s[:-2] + elif s[-1:] in ("m", "%"): + u = s[-1:] + s = s[:-1] + + try: + v = float(s) + except Exception: + return None, None + + return v, u + + +def pointInBBox(pt, bbox): + """ + Determine if the point pt=[x, y] lies on or within the bounding + box bbox=[xmin, xmax, ymin, ymax]. + """ + + # if ( x < xmin ) or ( x > xmax ) or ( y < ymin ) or ( y > ymax ) + if (pt[0] < bbox[0]) or (pt[0] > bbox[1]) or (pt[1] < bbox[2]) or (pt[1] > bbox[3]): + return False + else: + return True + + +def bboxInBBox(bbox1, bbox2): + """ + Determine if the bounding box bbox1 lies on or within the + bounding box bbox2. NOTE: we do not test for strict enclosure. + + Structure of the bounding boxes is + + bbox1 = [ xmin1, xmax1, ymin1, ymax1 ] + bbox2 = [ xmin2, xmax2, ymin2, ymax2 ] + """ + + # if ( xmin1 < xmin2 ) or ( xmax1 > xmax2 ) or + # ( ymin1 < ymin2 ) or ( ymax1 > ymax2 ) + + if ( + (bbox1[0] < bbox2[0]) + or (bbox1[1] > bbox2[1]) + or (bbox1[2] < bbox2[2]) + or (bbox1[3] > bbox2[3]) + ): + return False + else: + return True + + +def pointInPoly(p, poly, bbox=None): + """ + Use a ray casting algorithm to see if the point p = [x, y] lies within + the polygon poly = [[x1,y1],[x2,y2],...]. Returns True if the point + is within poly, lies on an edge of poly, or is a vertex of poly. + """ + + if (p is None) or (poly is None): + return False + + # Check to see if the point lies outside the polygon's bounding box + if bbox is not None: + if not pointInBBox(p, bbox): + return False + + # Check to see if the point is a vertex + if p in poly: + return True + + # Handle a boundary case associated with the point + # lying on a horizontal edge of the polygon + x = p[0] + y = p[1] + p1 = poly[0] + p2 = poly[1] + for i in range(len(poly)): + if i != 0: + p1 = poly[i - 1] + p2 = poly[i] + if ( + (y == p1[1]) + and (p1[1] == p2[1]) + and (x > min(p1[0], p2[0])) + and (x < max(p1[0], p2[0])) + ): + return True + + n = len(poly) + inside = False + + p1_x, p1_y = poly[0] + for i in range(n + 1): + p2_x, p2_y = poly[i % n] + if y > min(p1_y, p2_y): + if y <= max(p1_y, p2_y): + if x <= max(p1_x, p2_x): + if p1_y != p2_y: + intersect = p1_x + (y - p1_y) * (p2_x - p1_x) / (p2_y - p1_y) + if x <= intersect: + inside = not inside + else: + inside = not inside + p1_x, p1_y = p2_x, p2_y + + return inside + + +def polyInPoly(poly1, bbox1, poly2, bbox2): + """ + Determine if polygon poly2 = [[x1,y1],[x2,y2],...] + contains polygon poly1. + + The bounding box information, bbox=[xmin, xmax, ymin, ymax] + is optional. When supplied it can be used to perform rejections. + Note that one bounding box containing another is not sufficient + to imply that one polygon contains another. It's necessary, but + not sufficient. + """ + + # See if poly1's bboundin box is NOT contained by poly2's bounding box + # if it isn't, then poly1 cannot be contained by poly2. + + if (bbox1 is not None) and (bbox2 is not None): + if not bboxInBBox(bbox1, bbox2): + return False + + # To see if poly1 is contained by poly2, we need to ensure that each + # vertex of poly1 lies on or within poly2 + + for p in poly1: + if not pointInPoly(p, poly2, bbox2): + return False + + # Looks like poly1 is contained on or in Poly2 + + return True + + +def subdivideCubicPath(sp, flat, i=1): + """ + [ Lifted from eggbot.py with impunity ] + + Break up a bezier curve into smaller curves, each of which + is approximately a straight line within a given tolerance + (the "smoothness" defined by [flat]). + + This is a modified version of cspsubdiv.cspsubdiv(): rewritten + because recursion-depth errors on complicated line segments + could occur with cspsubdiv.cspsubdiv(). + """ + + while True: + while True: + if i >= len(sp): + return + + p0 = sp[i - 1][1] + p1 = sp[i - 1][2] + p2 = sp[i][0] + p3 = sp[i][1] + + b = (p0, p1, p2, p3) + + if inkex.bezier.maxdist(b) > flat: + break + + i += 1 + + one, two = inkex.bezier.beziersplitatt(b, 0.5) + sp[i - 1][2] = one[1] + sp[i][0] = two[2] + p = [one[2], one[3], two[1]] + sp[i:1] = [p] + + +def msg_linear_extrude(id, prefix): + msg = ( + " translate (%s_%d_center) linear_extrude(height=h, convexity=10, scale=0.01*s)\n" + + " translate (-%s_%d_center) polygon(%s_%d_points);\n" + ) + return msg % (id, prefix, id, prefix, id, prefix) + + +def msg_linear_extrude_by_paths(id, prefix): + msg = ( + " translate (%s_%d_center) linear_extrude(height=h, convexity=10, scale=0.01*s)\n" + + " translate (-%s_%d_center) polygon(%s_%d_points, %s_%d_paths);\n" + ) + return msg % (id, prefix, id, prefix, id, prefix, id, prefix) + + +def msg_extrude_by_hull(id, prefix): + msg = ( + " for (t = [0: len(%s_%d_points)-2]) {\n" % (id, prefix) + + " hull() {\n" + + " translate(%s_%d_points[t]) \n" % (id, prefix) + + " cylinder(h=h, r=w/2, $fn=res);\n" + + " translate(%s_%d_points[t + 1]) \n" % (id, prefix) + + " cylinder(h=h, r=w/2, $fn=res);\n" + + " }\n" + + " }\n" + ) + return msg + + +def msg_extrude_by_hull_and_paths(id, prefix): + msg = ( + " for (p = [0: len(%s_%d_paths)-1]) {\n" % (id, prefix) + + " pp = %s_%d_paths[p];\n" % (id, prefix) + + " for (t = [0: len(pp)-2]) {\n" + + " hull() {\n" + + " translate(%s_%d_points[pp[t]])\n" % (id, prefix) + + " cylinder(h=h, r=w/2, $fn=res);\n" + + " translate(%s_%d_points[pp[t+1]])\n" % (id, prefix) + + " cylinder(h=h, r=w/2, $fn=res);\n" + + " }\n" + + " }\n" + + " }\n" + ) + return msg + + +def remove_umlaut(string): + """ + Removes umlauts from strings and replaces them with the letter+e convention + :param string: string to remove umlauts from + :return: unumlauted string + """ + u = 'ü'.encode() + U = 'Ü'.encode() + a = 'ä'.encode() + A = 'Ä'.encode() + o = 'ö'.encode() + O = 'Ö'.encode() + ss = 'ß'.encode() + + string = string.encode() + string = string.replace(u, b'ue') + string = string.replace(U, b'Ue') + string = string.replace(a, b'ae') + string = string.replace(A, b'Ae') + string = string.replace(o, b'oe') + string = string.replace(O, b'Oe') + string = string.replace(ss, b'ss') + + string = string.decode('utf-8') + return string + +class PathsToOpenSCAD(inkex.EffectExtension): + + def add_arguments(self, pars): + inkex.localization.localize() # does not help for localizing my *.inx file + + pars.add_argument( "--tab", default="splash", help="The active tab when Apply was pressed", ) + pars.add_argument( "--smoothness", type=float, default=float(0.2), help="Curve smoothing (less for more)", ) + pars.add_argument( "--chamfer", type=float, default=float(1.), help="Add a chamfer radius, displacing all object walls outwards [mm]", ) + pars.add_argument( "--chamfer_fn", type=int, default=int(4), help="Chamfer precision ($fn when generating the minkowski sphere)", ) + pars.add_argument( "--zsize", default="5", help="Depth (Z-size) [mm]", ) + pars.add_argument( "--min_line_width", type=float, default=float(1), help="Line width for non closed curves [mm]", ) + pars.add_argument( "--line_width_scale_perc", type=float, default=float(1), help="Percentage of SVG line width. Use e.g. 26.46 to compensate a px/mm confusion. Default: 100 [%]", ) + pars.add_argument( "--line_fn", type=int, default=int(4), help="Line width precision ($fn when constructing hull)", ) + pars.add_argument( "--force_line", type=inkex.utils.Boolean, default=False, help="Force outline mode.", ) + pars.add_argument( "--fname", default="{NAME}.scad", help="openSCAD output file derived from the svg file name.", ) + pars.add_argument( "--parsedesc", type=inkex.utils.Boolean, default=True, help="Parse zsize and other parameters from object descriptions", ) + pars.add_argument( "--scadview", type=inkex.utils.Boolean, default=False, help="Open the file with openscad ( details see --scadviewcmd option )", ) + pars.add_argument( "--scadviewcmd", default=INX_SCADVIEW, help="Command used start an openscad viewer. Use {SCAD} for the openSCAD input.", ) + pars.add_argument( "--scad2stl", type=inkex.utils.Boolean, default=False, help="Also convert to STL ( details see --scad2stlcmd option )", ) + pars.add_argument( "--scad2stlcmd", default=INX_SCAD2STL, help="Command used to convert to STL. You can use {NAME}.scad for the openSCAD file to read and " + + "{NAME}.stl for the STL file to write.", ) + pars.add_argument( "--stlpost", type=inkex.utils.Boolean, default=False, help="Start e.g. a slicer. This implies the --scad2stl option. ( see --stlpostcmd )", ) + pars.add_argument( "--stlpostcmd", default=INX_STL_POSTPROCESSING, help="Command used for post processing an STL file (typically a slicer). You can use {NAME}.stl for the STL file.", ) + pars.add_argument( "--stlmodule", type=inkex.utils.Boolean, default=False, help="Output configured to comment out final rendering line, to create a module file for import.", ) + + self.userunitsx = 1.0 # Move to pure userunits per mm for v1.0 + self.userunitsy = 1.0 + self.px_used = False # raw px unit depends on correct dpi. + self.cx = float(DEFAULT_WIDTH) / 2.0 + self.cy = float(DEFAULT_HEIGHT) / 2.0 + self.xmin, self.xmax = (1.0E70, -1.0E70) + self.ymin, self.ymax = (1.0E70, -1.0E70) + + # Dictionary of paths we will construct. It's keyed by the SVG node + # it came from. Such keying isn't too useful in this specific case, + # but it can be useful in other applications when you actually want + # to go back and update the SVG document + self.paths = {} + + # Output file handling + self.call_list = [] + self.call_list_neg = [] # anti-matter (holes via difference) + self.pathid = int(0) + + # Output file + outfile = None + + # For handling an SVG viewbox attribute, we will need to know the + # values of the document's width and height attributes as well + # as establishing a transform from the viewbox to the display. + + self.docWidth = float(DEFAULT_WIDTH) + self.docHeight = float(DEFAULT_HEIGHT) + self.docTransform = Transform(None) + + # Dictionary of warnings issued. This to prevent from warning + # multiple times about the same problem + self.warnings = {} + + def getLength(self, name, default): + + """ + Get the attribute with name "name" and default value "default" + Parse the attribute into a value and associated units. Then, accept + units of cm, ft, in, m, mm, pc, or pt. Convert to pixels. + + Note that SVG defines 90 px = 1 in = 25.4 mm. + Note: Since inkscape 0.92 we use the CSS standard of 96 px = 1 in. + """ + str = self.document.getroot().get(name) + if str: + return self.LengthWithUnit(str) + else: + # No width specified; assume the default value + return float(default) + + def LengthWithUnit(self, strn, default_unit="px"): + v, u = parseLengthWithUnits(strn, default_unit) + if v is None: + # Couldn't parse the value + return None + elif u == "mm": + return float(v) * (self.userunitsx) + elif u == "cm": + return float(v) * (self.userunitsx * 10.0) + elif u == "m": + return float(v) * (self.userunitsx * 1000.0) + elif u == "in": + return float(v) * self.userunitsx * 25.4 + elif u == "ft": + return float(v) * 12.0 * self.userunitsx * 25.4 + elif u == "pt": + # Use modern "Postscript" points of 72 pt = 1 in instead + # of the traditional 72.27 pt = 1 in + return float(v) * (self.userunitsx * 25.4 / 72.0) + elif u == "pc": + return float(v) * (self.userunitsx * 25.4 / 6.0) + elif u == "px": + self.px_used = True + return float(v) + else: + # Unsupported units + return None + + def getDocProps(self): + """ + Get the document's height and width attributes from the tag. + Use a default value in case the property is not present or is + expressed in units of percentages. + """ + self.inkscape_version = self.document.getroot().get( + "{http://www.inkscape.org/namespaces/inkscape}version" + ) + sodipodi_docname = self.document.getroot().get( + "{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}docname" + ) + if sodipodi_docname is None: + sodipodi_docname = "inkscape" + # the document was not saved. We can assume it is v1 inkscape + self.basename = re.sub(r"\.SVG", "", sodipodi_docname, flags=re.I).rsplit('/', 1)[-1] + self.docHeight = self.getLength("height", DEFAULT_HEIGHT) + self.docWidth = self.getLength("width", DEFAULT_WIDTH) + + if (self.docHeight is None) or (self.docWidth is None): + return False + else: + return True + + def handleViewBox(self): + """ + Set up the document-wide transform in the event that the document has + an SVG viewbox, which it should as of v1.0 + For details, see https://wiki.inkscape.org/wiki/index.php/Units_In_Inkscape + """ + + if self.getDocProps(): + viewbox = self.document.getroot().get("viewBox") + if viewbox: + vinfo = viewbox.strip().replace(",", " ").split() + vinfo = [float(i) for i in vinfo] + unitsx = abs(vinfo[0] - vinfo[2]) + # unitsy = abs(vinfo[1] - vinfo[3]) + self.userunitsx = self.docWidth / unitsx + # The above wiki page suggests that x and y scaling maybe different + # however in practice they are not + self.userunitsy = self.userunitsx + self.docTransform = Transform( + "scale(%f,%f)" % (self.userunitsx, self.userunitsy) + ) + + def getPathVertices(self, path, node=None, transform=None): + """ + Decompose the path data from an SVG element into individual + subpaths, each subpath consisting of absolute move to and line + to coordinates. Place these coordinates into a list of polygon + vertices. + """ + if not path: + # Path must have been devoid of any real content + return None + + # Get a cubic super path + p = inkex.paths.CubicSuperPath(path) + if (not p) or (len(p) == 0): + # Probably never happens, but... + return None + + if transform: + p = p.transform(transform) + + # Now traverse the cubic super path + subpath_list = [] + subpath_vertices = [] + + sp_xmin = None + sp_xmax = None + sp_ymin = None + sp_ymax = None + for sp in p: + + # We've started a new subpath + # See if there is a prior subpath and whether we should keep it + if len(subpath_vertices): + subpath_list.append( + [subpath_vertices, [sp_xmin, sp_xmax, sp_ymin, sp_ymax]] + ) + + subpath_vertices = [] + subdivideCubicPath(sp, float(self.options.smoothness)) + + # Note the first point of the subpath + first_point = sp[0][1] + subpath_vertices.append(first_point) + sp_xmin = first_point[0] + sp_xmax = first_point[0] + sp_ymin = first_point[1] + sp_ymax = first_point[1] + + n = len(sp) + + # Traverse each point of the subpath + for csp in sp[1:n]: + + # Append the vertex to our list of vertices + pt = csp[1] + subpath_vertices.append(pt) + + # Track the bounding box of this subpath + if pt[0] < sp_xmin: + sp_xmin = pt[0] + elif pt[0] > sp_xmax: + sp_xmax = pt[0] + if pt[1] < sp_ymin: + sp_ymin = pt[1] + elif pt[1] > sp_ymax: + sp_ymax = pt[1] + + # Track the bounding box of the overall drawing + # This is used for centering the polygons in OpenSCAD around the + # (x,y) origin + if sp_xmin < self.xmin: + self.xmin = sp_xmin + if sp_xmax > self.xmax: + self.xmax = sp_xmax + if sp_ymin < self.ymin: + self.ymin = sp_ymin + if sp_ymax > self.ymax: + self.ymax = sp_ymax + + # Handle the final subpath + if len(subpath_vertices): + subpath_list.append( + [subpath_vertices, [sp_xmin, sp_xmax, sp_ymin, sp_ymax]] + ) + + if len(subpath_list) > 0: + self.paths[node] = subpath_list + + def getPathStyle(self, node): + style = node.get("style", "") + # .get default is not reliable, ensure value is a string + if not style: + style = "" + ret = {} + # fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1 + for elem in style.split(";"): + if len(elem): + try: + (key, val) = elem.strip().split(":") + except Exception: + inkex.errormsg( + "unparsable element '{1}' in style '{0}'".format(elem, style) + ) + ret[key] = val + return ret + + def convertPath(self, node, outfile): + def object_merge_extrusion_values(extrusion, node): + + """ Parser for description and ID fields for extrusion parameters. + This recurse into parents, to inherit values from enclosing + groups. + """ + p = node.getparent() + if p is not None and p.tag in (inkex.addNS("g", "svg"), "g"): + object_merge_extrusion_values(extrusion, p) + + # let the node override inherited values + rawid = node.get("id", "") + if rawid is not None: + zsize = RE_AUTO_ZSIZE_ID.findall(rawid) + if zsize: + extrusion["zsize"] = zsize[-1].replace("_", ".") + # let description contents override id contents. + for tagname in DESC_TAGS: + desc_node = node.find("./%s" % tagname) + if desc_node is not None: + zsize = RE_AUTO_ZSIZE_DESC.findall(desc_node.text) + if zsize: + extrusion["zsize"] = zsize[-1] + zscale = RE_AUTO_SCALE_DESC.findall(desc_node.text) + if zscale: + if "," in zscale[-1]: + extrusion["scale"] = "[" + zscale[-1] + "]" + else: + extrusion["scale"] = zscale[-1] + zoffset = RE_AUTO_ZOFFSET_DESC.findall(desc_node.text) + if zoffset: + extrusion["zoffset"] = zoffset[-1] + if extrusion["zsize"][0] in ("a", "A"): + extrusion["neg"] = True + extrusion["zsize"] = extrusion["zsize"][1:] + # END object_merge_extrusion_values + + path = self.paths[node] + if (path is None) or (len(path) == 0): + return + + # Determine which polys contain which + + contains = [[] for i in range(len(path))] + contained_by = [[] for i in range(len(path))] + + for i in range(0, len(path)): + for j in range(i + 1, len(path)): + if polyInPoly(path[j][0], path[j][1], path[i][0], path[i][1]): + # subpath i contains subpath j + contains[i].append(j) + # subpath j is contained in subpath i + contained_by[j].append(i) + elif polyInPoly(path[i][0], path[i][1], path[j][0], path[j][1]): + # subpath j contains subpath i + contains[j].append(i) + # subpath i is contained in subpath j + contained_by[i].append(j) + + # Generate an OpenSCAD module for this path + rawid = node.get("id", "") + if (rawid is None) or (rawid == ""): + id = str(self.pathid) + "x" + rawid = id + self.pathid += 1 + else: + id = re.sub("[^A-Za-z0-9_]+", "", rawid) + + style = self.getPathStyle(node) + stroke_width = style.get("stroke-width", "1") + + # FIXME: works with document units == 'mm', but otherwise untested.. + stroke_width_mm = self.LengthWithUnit(stroke_width, default_unit="mm") + stroke_width_mm = str(stroke_width_mm * self.userunitsx) # px to mm + fill_color = style.get("fill", "#FFF") + if fill_color == "none": + filled = False + else: + filled = True + if filled is False and style.get("stroke", "none") == "none": + inkex.errormsg( + "WARNING: " + rawid + " has fill:none and stroke:none, object ignored." + ) + return + + # #### global data for msg_*() functions. #### + # fold subpaths into a single list of points and index paths. + prefix = 0 + for i in range(0, len(path)): + # Skip this subpath if it is contained by another one + if len(contained_by[i]) != 0: + continue + subpath = path[i][0] + bbox = path[i][1] # [xmin, xmax, ymin, ymax] + + # + polycenter = ( + id + + "_" + + str(prefix) + + "_center = [%f,%f]" + % ( + (bbox[0] + bbox[1]) * .5 - self.cx, + (bbox[2] + bbox[3]) * .5 - self.cy, + ) + ) + polypoints = id + "_" + str(prefix) + "_points = [" + # polypaths = [[0,1,2], [3,4,5]] # this path is two triangle + polypaths = id + "_" + str(prefix) + "_paths = [[" + if len(contains[i]) == 0: + # This subpath does not contain any subpaths + for point in subpath: + polypoints += "[%f,%f]," % ( + (point[0] - self.cx), + (point[1] - self.cy), + ) + polypoints = polypoints[:-1] + polypoints += "];\n" + outfile.write(polycenter + ";\n") + outfile.write(polypoints) + prefix += 1 + else: + # This subpath contains other subpaths + # collect all points into polypoints + # also collect the indices into polypaths + for point in subpath: + polypoints += "[%f,%f]," % ( + (point[0] - self.cx), + (point[1] - self.cy), + ) + count = len(subpath) + for k in range(0, count): + polypaths += "%d," % (k) + polypaths = polypaths[:-1] + "],\n\t\t\t\t[" + # The nested paths + for j in contains[i]: + for point in path[j][0]: + polypoints += "[%f,%f]," % ( + (point[0] - self.cx), + (point[1] - self.cy), + ) + for k in range(count, count + len(path[j][0])): + polypaths += "%d," % k + count += len(path[j][0]) + polypaths = polypaths[:-1] + "],\n\t\t\t\t[" + polypoints = polypoints[:-1] + polypoints += "];\n" + polypaths = polypaths[:-7] + "];\n" + # write the polys and paths + outfile.write(polycenter + ";\n") + outfile.write(polypoints) + outfile.write(polypaths) + prefix += 1 + # #### end global data for msg_*() functions. #### + + outfile.write("module poly_" + id + "(h, w, s, res=line_fn)\n{\n") + # Element is transformed to correct size, so scale is now just for the user to + # tweak after the fact + outfile.write(" scale([custom_scale_x, -custom_scale_y, 1]) union()\n {\n") + + # And add the call to the call list + # Z-size is set by the overall module parameter + # unless an extrusion zsize is parsed from the description or ID. + extrusion = {"zsize": "h", "zoffset": "0", "scale": 100.0, "neg": False} + if self.options.parsedesc is True: + object_merge_extrusion_values(extrusion, node) + + call_item = "translate ([0,0,%s]) poly_%s(%s, min_line_mm(%s), %s);\n" % ( + extrusion["zoffset"], + id, + extrusion["zsize"], + stroke_width_mm, + extrusion["scale"], + ) + + if extrusion["neg"]: + self.call_list_neg.append(call_item) + else: + self.call_list.append(call_item) + + prefix = 0 + for i in range(0, len(path)): + + # Skip this subpath if it is contained by another one + if len(contained_by[i]) != 0: + continue + + subpath = path[i][0] + bbox = path[i][1] + + if filled and not self.options.force_line: + + if len(contains[i]) == 0: + # This subpath does not contain any subpaths + poly = msg_linear_extrude(id, prefix) + else: + # This subpath contains other subpaths + poly = msg_linear_extrude_by_paths(id, prefix) + + else: # filled == False -> outline mode + + if len(contains[i]) == 0: + # This subpath does not contain any subpaths + poly = msg_extrude_by_hull(id, prefix) + else: + # This subpath contains other subpaths + poly = msg_extrude_by_hull_and_paths(id, prefix) + + outfile.write(poly) + prefix += 1 + + # End the module + outfile.write(" }\n}\n") + + def recursivelyTraverseSvg( + self, aNodeList, matCurrent=Transform(None), parent_visibility="visible" + ): + + """ + [ This too is largely lifted from eggbot.py ] + + Recursively walk the SVG document, building polygon vertex lists + for each graphical element we support. + + Rendered SVG elements: + , , , , , , + + Supported SVG elements: + , + + Ignored SVG elements: + , , , , , + processing directives + + All other SVG elements trigger an error (including ) + """ + + for node in aNodeList: + + # Ignore invisible nodes + v = node.get("visibility", parent_visibility) + if v == "inherit": + v = parent_visibility + if v == "hidden" or v == "collapse": + continue + + s = node.get("style", "") + if s == "display:none": + continue + + # First apply the current matrix transform to this node's transform + matNew = matCurrent @ Transform(node.get("transform")) + + if node.tag == inkex.addNS("g", "svg") or node.tag == "g": + + self.recursivelyTraverseSvg(node, matNew, v) + + elif node.tag == inkex.addNS("use", "svg") or node.tag == "use": + + # A element refers to another SVG element via an + # xlink:href="#blah" attribute. We will handle the element by + # doing an XPath search through the document, looking for the + # element with the matching id="blah" attribute. We then + # recursively process that element after applying any necessary + # (x,y) translation. + # + # Notes: + # 1. We ignore the height and width attributes as they do not + # apply to path-like elements, and + # 2. Even if the use element has visibility="hidden", SVG + # still calls for processing the referenced element. The + # referenced element is hidden only if its visibility is + # "inherit" or "hidden". + + refid = node.get(inkex.addNS("href", "xlink")) + if not refid: + continue + + # [1:] to ignore leading '#' in reference + path = '//*[@id="%s"]' % refid[1:] + refnode = node.xpath(path) + if refnode: + x = float(node.get("x", "0")) + y = float(node.get("y", "0")) + # Note: the transform has already been applied + if (x != 0) or (y != 0): + matNew2 = matNew * Transform("translate(%f,%f)" % (x, y)) + else: + matNew2 = matNew + v = node.get("visibility", v) + self.recursivelyTraverseSvg(refnode, matNew2, v) + + elif node.tag == inkex.addNS("path", "svg"): + + path_data = node.get("d") + if path_data: + self.getPathVertices(path_data, node, matNew) + + elif node.tag == inkex.addNS("rect", "svg") or node.tag == "rect": + + # Manually transform + # + # + # + # into + # + # + # + # I.e., explicitly draw three sides of the rectangle and the + # fourth side implicitly + + # Create a path with the outline of the rectangle + x = float(node.get("x")) + y = float(node.get("y")) + w = float(node.get("width", "0")) + h = float(node.get("height", "0")) + a = [] + a.append(["M", [x, y]]) + a.append(["l", [w, 0]]) + a.append(["l", [0, h]]) + a.append(["l", [-w, 0]]) + a.append(["Z", []]) + self.getPathVertices(a, node, matNew) + + elif node.tag == inkex.addNS("line", "svg") or node.tag == "line": + + # Convert + # + # + + x1 = float(node.get("x1")) + y1 = float(node.get("y1")) + x2 = float(node.get("x2")) + y2 = float(node.get("y2")) + if (not x1) or (not y1) or (not x2) or (not y2): + continue + a = [] + a.append(["M", [x1, y1]]) + a.append(["L", [x2, y2]]) + self.getPathVertices(a, node, matNew) + + elif node.TAG in ["polygon", "polyline"]: + + # Convert + # + # + # + # to + # + # + # + # Note: we ignore polylines with no points + + pl = node.get("points", "").strip() + if not pl: + continue + + pa = pl.split() + d = "".join( + [ + "M " + pa[i] if i == 0 else " L " + pa[i] + for i in range(0, len(pa)) + ] + ) + d = [] + first = True + for part in pl.split(): + x, y = part.split(",") + coords = [float(x), float(y)] + if first: + d.append(["M", coords]) + first = False + else: + d.append(["L", coords]) + if node.TAG == "polygon": + d.append(["Z", []]) + self.getPathVertices(d, node, matNew) + + elif ( + node.tag == inkex.addNS("ellipse", "svg") + or node.tag == "ellipse" + or node.tag == inkex.addNS("circle", "svg") + or node.tag == "circle" + ): + + # Convert circles and ellipses to a path with two 180 degree + # arcs. In general (an ellipse), we convert + # + # + # + # to + # + # + # + # where + # + # X1 = CX - RX + # X2 = CX + RX + # + # Note: ellipses or circles with a radius attribute of value 0 + # are ignored + + if node.tag == inkex.addNS("ellipse", "svg") or node.tag == "ellipse": + rx = float(node.get("rx", "0")) + ry = float(node.get("ry", "0")) + else: + rx = float(node.get("r", "0")) + ry = rx + if rx == 0 or ry == 0: + continue + + cx = float(node.get("cx", "0")) + cy = float(node.get("cy", "0")) + x1 = cx - rx + x2 = cx + rx + d = [ + ["M", (x1, cy)], + ["A", (rx, ry, 0, 1, 0, x2, cy)], + ["A", (rx, ry, 0, 1, 0, x1, cy)], + ] + self.getPathVertices(d, node, matNew) + + elif node.tag == inkex.addNS("pattern", "svg") or node.tag == "pattern": + pass + + elif node.tag == inkex.addNS("metadata", "svg") or node.tag == "metadata": + pass + + elif node.tag == inkex.addNS("defs", "svg") or node.tag == "defs": + pass + + elif node.tag == inkex.addNS("desc", "svg") or node.tag == "desc": + pass + + elif ( + node.tag == inkex.addNS("namedview", "sodipodi") + or node.tag == "namedview" + ): + pass + + elif node.tag == inkex.addNS("eggbot", "svg") or node.tag == "eggbot": + pass + + elif node.tag == inkex.addNS("text", "svg") or node.tag == "text": + texts = [] + plaintext = "" + for tnode in node.iterfind(".//"): # all subtree + if tnode is not None and tnode.text is not None: + texts.append(tnode.text) + if len(texts): + plaintext = "', '".join(texts).encode("latin-1") + inkex.errormsg('Warning: text "%s"' % plaintext) + inkex.errormsg( + "Warning: unable to draw text, please convert it to a path first." + ) + + elif node.tag == inkex.addNS("title", "svg") or node.tag == "title": + pass + + elif node.tag == inkex.addNS("image", "svg") or node.tag == "image": + if "image" not in self.warnings: + inkex.errormsg( + gettext.gettext( + "Warning: unable to draw bitmap images; please convert them to line art first. " + 'Consider using the "Trace bitmap..." tool of the "Path" menu. Mac users please ' + "note that some X11 settings may cause cut-and-paste operations to paste in bitmap copies." + ) + ) + self.warnings["image"] = 1 + + elif node.tag == inkex.addNS("pattern", "svg") or node.tag == "pattern": + pass + + elif ( + node.tag == inkex.addNS("radialGradient", "svg") + or node.tag == "radialGradient" + ): + # Similar to pattern + pass + + elif ( + node.tag == inkex.addNS("linearGradient", "svg") + or node.tag == "linearGradient" + ): + # Similar in pattern + pass + + elif node.tag == inkex.addNS("style", "svg") or node.tag == "style": + # This is a reference to an external style sheet and not the + # value of a style attribute to be inherited by child elements + pass + + elif node.tag == inkex.addNS("cursor", "svg") or node.tag == "cursor": + pass + + elif ( + node.tag == inkex.addNS("color-profile", "svg") + or node.tag == "color-profile" + ): + # Gamma curves, color temp, etc. are not relevant to single + # color output + pass + + elif not isinstance(node.tag, (str, bytes)): + # This is likely an XML processing instruction such as an XML + # comment. lxml uses a function reference for such node tags + # and as such the node tag is likely not a printable string. + # Further, converting it to a printable string likely won't + # be very useful. + pass + + else: + inkex.errormsg( + "Warning: unable to draw object <%s>, please convert it to a path first." + % node.tag + ) + pass + + def recursivelyGetEnclosingTransform(self, node): + # Determine the cumulative transform which node inherits from its chain of ancestors. + node = node.getparent() + if node is not None: + parent_transform = self.recursivelyGetEnclosingTransform(node) + node_transform = node.get("transform", None) + if node_transform is None: + return parent_transform + else: + tr = Transform(node_transform) + if parent_transform is None: + return tr + else: + return parent_transform * tr + else: + return self.docTransform + + def effect(self): + # Viewbox handling + self.handleViewBox() + + # First traverse the document (or selected items), reducing + # everything to line segments. If working on a selection, + # then determine the selection's bounding box in the process. + # (Actually, we just need to know its extrema on the x-axis.) + + if self.options.ids: + # Traverse the selected objects + for id in self.options.ids: + transform = self.recursivelyGetEnclosingTransform(self.svg.selected[id]) + self.recursivelyTraverseSvg([self.svg.selected[id]], transform) + else: + # Traverse the entire document building new, transformed paths + self.recursivelyTraverseSvg(self.document.getroot(), self.docTransform) + + # Determine the center of the drawing's bounding box + self.cx = self.xmin + (self.xmax - self.xmin) / 2.0 + self.cy = self.ymin + (self.ymax - self.ymin) / 2.0 + + # Determine which polygons lie entirely within other polygons + try: + self.options.fname = self.options.fname.format(**{"NAME": self.basename}) + if os.sep not in self.options.fname and "PWD" in os.environ: + # current working directory of an extension seems to be the extension dir. + # Workaround using PWD, if available... + self.options.fname = os.environ["PWD"] + "/" + self.options.fname + scad_fname = os.path.expanduser(self.options.fname) + if "/" != os.sep: + scad_fname = scad_fname.replace("/", os.sep) + + with open(scad_fname, 'w') as outfile: + outfile = open(scad_fname, "w") + outfile.write( + "// Generated by inkscape %s + inkscape-paths2openscad %s\n" + % (self.inkscape_version, VERSION) + ) + outfile.write('// %s from "%s.svg"\n' % (time.ctime(), self.basename)) + # for use in options.fname basename is derived from the sodipodi_docname by + # stripping the svg extension - or if there is no sodipodi_docname basename is 'inkscape'. + # for use in scadviewcmd, scad2stlcmd and stlpostcmd basename is rederived from + # options.fname by stripping an scad extension. + self.basename = re.sub(r"\.scad", "", scad_fname, flags=re.I) + + outfile.write( + """ +// Module names are of the form poly_(). As a result, +// you can associate a polygon in this OpenSCAD program with the corresponding +// SVG element in the Inkscape document by looking for the XML element with +// the attribute id=\"inkscape-path-id\". + +// fudge value is used to ensure that subtracted solids are a tad taller +// in the z dimension than the polygon being subtracted from. This helps +// keep the resulting .stl file manifold. +fudge = 0.1; +""" + ) + if self.options.chamfer < 0.001: + self.options.chamfer = None + + outfile.write("user_unit_scale_x = %s;\n" % (self.userunitsx)) + outfile.write("user_unit_scale_y = %s;\n" % (self.userunitsy)) + outfile.write("custom_scale_x = 1;\n") + outfile.write("custom_scale_y = 1;\n") + + # writeout users parameters + outfile.write("zsize = %s;\n" % (self.options.zsize)) + outfile.write("line_fn = %d;\n" % (self.options.line_fn)) + if self.options.chamfer: + outfile.write("chamfer = %s;\n" % (self.options.chamfer)) + outfile.write("chamfer_fn = %d;\n" % (self.options.chamfer_fn)) + outfile.write("min_line_width = %s;\n" % (self.options.min_line_width)) + outfile.write( + "line_width_scale = %s;\n" % (self.options.line_width_scale_perc * 0.01) + ) + outfile.write( + "function min_line_mm(w) = max(min_line_width, w * line_width_scale) * %g;\n\n" + % self.userunitsx + ) + + for key in self.paths: + outfile.write("\n") + self.convertPath(key, outfile) + + if self.options.chamfer: + outfile.write( + """ +module chamfer_sphere(rad=chamfer, res=chamfer_fn) +{ + if(res <= 4) + { + // octaeder: 3 sided faces = 8 + polyhedron( + points = [ [.0, .0, rad], [.0, .0, -rad], [ rad, .0, .0], [-rad, .0, .0], [.0, rad, .0], [.0, -rad, .0] ], + faces = [ [4, 2, 0], [3, 4, 0], [5, 3, 0], [2, 5, 0], [5, 2, 1], [3, 5, 1], [4, 3, 1], [2 , 4, 1] ]); + } + else + { + sphere(r=rad, center=true, $fn=res); + } +} +""" + ) + + # Come up with a name for the module based on the file name. + name = os.path.splitext(os.path.basename(self.options.fname))[0] + # Remove all punctuation except underscore. + badchars = string.punctuation.replace("_", "") + " " + name = re.sub("[" + badchars + "]", "_", name) + name = remove_umlaut(name) + + outfile.write("\nmodule %s(h)\n{\n" % name) + mi = "" + if self.options.chamfer: + mi = " " + outfile.write(" minkowski()\n {\n") + + # Now output the list of modules to call + outfile.write( + "%s difference()\n%s {\n%s union()\n%s {\n" % (mi, mi, mi, mi) + ) + for call in self.call_list: + outfile.write("%s %s" % (mi, call)) + outfile.write("%s }\n%s union()\n%s {\n" % (mi, mi, mi)) + for call in self.call_list_neg: + outfile.write("%s %s" % (mi, call)) + outfile.write("%s }\n%s }\n" % (mi, mi)) + if self.options.chamfer: + outfile.write(" chamfer_sphere();\n }\n") + + # The module that calls all the other ones. + if self.options.stlmodule is True: + self.options.scad2stl = False #otherwise program will fail because modules are not renderable + outfile.write("}\n\n//%s(zsize);\n" % (name)) + else: + outfile.write("}\n\n%s(zsize);\n" % (name)) + + outfile.close() + except IOError as e: + inkex.errormsg("Unable to write file " + self.options.fname) + inkex.errormsg("ERROR: " + str(e)) + + ################################################################ + # Call OpenSCAD + ################################################################ + if self.options.scadview is True: + #inkex.utils.debug("Calling OpenSCAD ...") + pidfile = os.path.join(tempfile.gettempdir(), "paths2openscad.pid") + running = False + cmd = self.options.scadviewcmd.format(**{"SCAD": scad_fname, "NAME": self.basename}) + try: + with open(pidfile) as pfile: + m = re.match(r"(\d+)\s+(.*)", pfile.read()) + oldpid = int(m.group(1)) + oldcmd = m.group(2) + # print >> sys.stderr, "pid {1} seen in {0}".format(pidfile, oldpid) + # print >> sys.stderr, "cmd {0}, oldcmd {1}".format(cmd, oldcmd) + if cmd == oldcmd: + # we found a pidfile and the cmd in there is still identical. + # If we change the filename in the inkscape extension gui, the cmd differs, and + # the still running openscad would not pick up our changes. + # If the command is identical, we check if the pid in the pidfile is alive. + # If so, we assume, the still running openscad will pick up the changes. + # + # WARNING: too much magic here. We cannot really test, if the last assumption holds. + # Comment out the next line to always start a new instance of openscad. + running = IsProcessRunning(oldpid) + # print >> sys.stderr, "running {0}".format(running) + except Exception: + pass + if not running: + try: + tty = open("/dev/tty", "w") + except Exception: + tty = subprocess.PIPE + try: + with subprocess.Popen(cmd, shell=True, stdin=tty, stdout=tty, stderr=tty) as proc: + proc.wait() + tty.close() + except OSError as e: + raise OSError("%s failed: errno=%d %s" % (cmd, e.errno, e.strerror)) + try: + with open(pidfile, "w") as pfile: + pfile.write(str(proc.pid) + "\n" + cmd + "\n") + except Exception: + pass + else: + # BUG alert: + # If user changes the file viewed in openscad (save with different name, re-open that name + # without closing openscad, again, the still running openscad does not + # pick up the changes. and we have no way to tell the difference if it did. + pass + + ################################################################ + # Call OpenSCAD to STL conversion + ################################################################ + if self.options.scad2stl is True or self.options.stlpost is True: + #inkex.utils.debug("Calling OpenSCAD to STL conversion...") + stl_fname = self.basename + ".stl" + scad2stlcmd = self.options.scad2stlcmd.format(**{"SCAD": scad_fname, "STL": stl_fname, "NAME": self.basename}) + try: + os.unlink(stl_fname) + except Exception: + pass + + with subprocess.Popen(scad2stlcmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: + proc.wait() + stdout, stderr = proc.communicate() + len = -1 + try: + len = os.path.getsize(stl_fname) + except Exception: + pass + if len < 1000: + inkex.errormsg("CMD: {} WARNING: {} is very small: {} bytes.".format(scad2stlcmd, stl_fname, len)) + inkex.errormsg("= " * 24) + inkex.errormsg("STDOUT:\n{}".format(stdout.decode('UTF-8'))) + inkex.errormsg("= " * 24) + inkex.errormsg("STDERR:\n{}".format(stderr.decode('UTF-8'))) + inkex.errormsg("= " * 24) + if len <= 0: # something is wrong. better stop here + self.options.stlpost = False + + ################################################################ + # Call OpenSCAD post processing + ################################################################ + if self.options.stlpost is True: + #inkex.utils.debug("Calling OpenSCAD post processing...") + stlpostcmd = self.options.stlpostcmd.format( + **{"STL": self.basename + ".stl", "NAME": self.basename} + ) + try: + with subprocess.Popen(stlpostcmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: + proc.wait() + stdout, stderr = proc.communicate() + if stdout or stderr: + inkex.errormsg("CMD: {}".format(stlpostcmd)) + inkex.errormsg("= " * 24) + if stdout: + inkex.errormsg("STDOUT: {}".format(stdout.decode('UTF-8'))) + inkex.errormsg("= " * 24) + if stderr: + inkex.errormsg("STDERR: {}".format(stderr.decode('UTF-8'))) + inkex.errormsg("= " * 24) + except OSError as e: + raise OSError("%s failed: errno=%d %s" % (stlpostcmd, e.errno.decode('UTF-8'), e.strerror.decode('UTF-8'))) + +if __name__ == '__main__': + PathsToOpenSCAD().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/pixels2objects/meta.json b/extensions/fablabchemnitz/pixels2objects/meta.json index 69c61f6..833f9b5 100644 --- a/extensions/fablabchemnitz/pixels2objects/meta.json +++ b/extensions/fablabchemnitz/pixels2objects/meta.json @@ -9,13 +9,13 @@ "license": "GNU GPL v3", "license_url": "https://inkscape.org/~pakin/%E2%98%85pixels-to-objects", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/pixels2objects", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/pixels2objects", "fork_url": "https://inkscape.org/~pakin/%E2%98%85pixels-to-objects", "documentation_url": "https://stadtfabrikanten.org/display/IFM/Pixels+To+Objects", "inkscape_gallery_url": null, "main_authors": [ "inkscape.org/pakin", - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/primitive/meta.json b/extensions/fablabchemnitz/primitive/meta.json index 91aa406..70f3f09 100644 --- a/extensions/fablabchemnitz/primitive/meta.json +++ b/extensions/fablabchemnitz/primitive/meta.json @@ -7,14 +7,14 @@ "original_name": "Primitive (Michael Fogleman)", "original_id": "fablabchemnitz.de.primitive", "license": "GNU GPL v3", - "license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/LICENSE", + "license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/primitive", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/primitive", "fork_url": "https://github.com/fogleman/primitive", "documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=78807580", "inkscape_gallery_url": "https://inkscape.org/~MarioVoigt/%E2%98%85primitive-for-inkscape", "main_authors": [ - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/printing_marks_dotted/meta.json b/extensions/fablabchemnitz/printing_marks_dotted/meta.json new file mode 100644 index 0000000..bf47ab1 --- /dev/null +++ b/extensions/fablabchemnitz/printing_marks_dotted/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "Printing Marks Dotted", + "id": "fablabchemnitz.de.printing_marks_dotted", + "path": "printing_marks_dotted", + "dependent_extensions": null, + "original_name": "Printing Marks Dotted", + "original_id": "org.inkscape.printing.marks.dotted", + "license": "GNU GPL v2", + "license_url": "https://inkscape.org/~jabiertxof/%E2%98%85printing-marks-dotted", + "comment": "ported to Inkscape v1 manually by Mario Voigt", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/printing_marks_dotted", + "fork_url": "https://inkscape.org/~jabiertxof/%E2%98%85printing-marks-dotted", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Printing+Marks+Dotted", + "inkscape_gallery_url": null, + "main_authors": [ + "inkscape.org/jabiertxof", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/printing_marks_dotted/printing_marks_dotted.inx b/extensions/fablabchemnitz/printing_marks_dotted/printing_marks_dotted.inx new file mode 100644 index 0000000..4223c77 --- /dev/null +++ b/extensions/fablabchemnitz/printing_marks_dotted/printing_marks_dotted.inx @@ -0,0 +1,46 @@ + + + Printing Marks Dotted + fablabchemnitz.de.printing_marks_dotted + + + true + false + false + true + false + true + false + + + + + + + + + + + + + + 5 + + 5 + 5 + 5 + 5 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/printing_marks_dotted/printing_marks_dotted.py b/extensions/fablabchemnitz/printing_marks_dotted/printing_marks_dotted.py new file mode 100644 index 0000000..06b62d8 --- /dev/null +++ b/extensions/fablabchemnitz/printing_marks_dotted/printing_marks_dotted.py @@ -0,0 +1,475 @@ +#!/usr/bin/env python3 +''' +This extension allows you to draw crop, registration and other +printing marks in Inkscape. + +Authors: + Nicolas Dufour - Association Inkscape-fr + Aurelio A. Heckert + +Copyright (C) 2008 Authors + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +''' + +from subprocess import Popen, PIPE, STDOUT +import math +import inkex +from lxml import etree + +class PrintingMarksDotted(inkex.EffectExtension): + + # Default parameters + stroke_width = 0.25 + + def add_arguments(self, pars): + pars.add_argument("--where_to_crop", default=True, help="Apply crop marks to...") + pars.add_argument("--crop_marks", type=inkex.Boolean, default=True, help="Draw crop Marks?") + pars.add_argument("--dotted_crop_marks", type=inkex.Boolean, default=True, help="Draw dotted crop Marks?") + pars.add_argument("--bleed_marks", type=inkex.Boolean, default=False, help="Draw Bleed Marks?") + pars.add_argument("--registration_marks", type=inkex.Boolean, default=False, help="Draw Registration Marks?") + pars.add_argument("--star_target", type=inkex.Boolean, default=False, help="Draw Star Target?") + pars.add_argument("--colour_bars", type=inkex.Boolean, default=False, help="Draw Colour Bars?") + pars.add_argument("--page_info", type=inkex.Boolean, default=False, help="Draw Page Information?") + pars.add_argument("--unit",default="px", help="Draw measurment") + pars.add_argument("--crop_offset", type=float, default=0, help="Offset") + pars.add_argument("--bleed_top", type=float, default=0, help="Bleed Top Size") + pars.add_argument("--bleed_bottom", type=float, default=0, help="Bleed Bottom Size") + pars.add_argument("--bleed_left", type=float, default=0, help="Bleed Left Size") + pars.add_argument("--bleed_right",type=float, default=0, help="Bleed Right Size") + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + + def addMarker(self): + svg = self.document.getroot() + xpathStr = '//marker[@id="scissorsCroper"]' + maskElement = svg.xpath(xpathStr, namespaces=inkex.NSS) + if maskElement == []: + xpathStr = '//svg:defs' + defs = svg.xpath(xpathStr, namespaces=inkex.NSS) + line_attribs = {'markerWidth': "8.2212915", + 'markerHeight': "4.8983894", + 'orient': "auto", + 'id':"scissorsCroper"} + markerElement = etree.SubElement(defs[0],inkex.addNS('marker','svg'), line_attribs) + line_attribs = {'style': "fill:#000000;stroke:#ffffff;stroke-width:0.2;stroke-miterlimit:4;stroke-opacity:1", + 'id': "scissorsCroperPath", + 'd': "m -3.09375,-2.59375 c -0.2875213,-0.019086 -0.5530997,0.080418 -0.78125,0.25 -0.2281503,0.1695818 -0.4212781,0.4427198 -0.4375,0.75 -0.014236,0.2696628 0.032949,0.4281517 0.09375,0.53125 0.011692,0.019827 0.022314,0.017924 0.03125,0.03125 -0.074992,0.019409 -0.1886388,0.0360237 -0.34375,0.0625 -0.3217609,0.0549221 -0.7596575,0.13825127 -1.21875,0.375 l -3.03125,-1.125 c -0.2710413,-0.1042898 -0.5662791,-0.1829987 -0.875,-0.15625 -0.3087209,0.026749 -0.621076,0.1687088 -0.84375,0.4375 a 0.20792008,0.20792008 0 0 0 -0.03125,0.03125 0.20792008,0.20792008 0 0 0 -0.03125,0.0625 0.20792008,0.20792008 0 0 0 0,0.0625 0.20792008,0.20792008 0 0 0 0.03125,0.0625 0.20792008,0.20792008 0 0 0 0.03125,0.03125 0.20792008,0.20792008 0 0 0 0.09375,0.0625 l 2.9375,1.15625 -2.96875,1.125 a 0.20792008,0.20792008 0 0 0 -0.09375,0.0625 0.20792008,0.20792008 0 0 0 -0.03125,0.03125 0.20792008,0.20792008 0 0 0 -0.03125,0.0625 0.20792008,0.20792008 0 0 0 0,0.0625 0.20792008,0.20792008 0 0 0 0.03125,0.0625 0.20792008,0.20792008 0 0 0 0.03125,0.03125 C -10.094168,1.9539272 -9.4699318,1.9749423 -9,1.84375 L -5.71875,0.6875 c 0.481754,0.20541523 0.912658,0.3186677 1.1875,0.375 0.1483249,0.030401 0.2392409,0.045912 0.3125,0.0625 0.03663,0.00829 0.024599,-0.00324 0.03125,0 -0.0079,0.02335 -0.010635,0.041757 -0.03125,0.09375 -0.053917,0.1359822 -0.1506131,0.3500538 -0.09375,0.625 0.074929,0.3622982 0.3561361,0.6217769 0.65625,0.75 0.3001139,0.1282231 0.6300895,0.1440646 0.9375,0.03125 0.6444683,-0.175589 0.9014775,-0.9349259 0.625,-1.5 C -2.2324842,0.83910622 -2.4880622,0.66240891 -2.75,0.5625 -3.0119378,0.46259109 -3.2717529,0.42256233 -3.53125,0.4375 c -0.2805605,0.0161501 -0.5796777,0.0351178 -0.8125,-0.03125 -0.1944918,-0.0554414 -0.3308104,-0.18103045 -0.46875,-0.375 0.1925418,-0.25215792 0.4169804,-0.350782 0.71875,-0.375 0.3394341,-0.0272407 0.7247815,0.0434012 1.0625,0 0.010025,-6.5986e-4 0.021283,9.2632e-4 0.03125,0 0.5937358,-0.0551819 1.1050788,-0.57908524 1.0625,-1.1875 -0.00523,-0.6217326 -0.5853909,-1.0659264 -1.15625,-1.0625 z M -2.9375,-1.875 c 0.1401777,0.04894 0.2268596,0.139783 0.25,0.25 a 0.20792008,0.20792008 0 0 0 0.03125,0.03125 c 0.046997,0.1597651 -0.018243,0.2935457 -0.15625,0.40625 -0.1380068,0.1127043 -0.3531142,0.176154 -0.5,0.125 -0.1652738,-0.046651 -0.2408416,-0.1796945 -0.25,-0.34375 -0.00916,-0.1640555 0.046643,-0.3414062 0.21875,-0.4375 0.104863,-0.058549 0.2664752,-0.08005 0.40625,-0.03125 z m -0.21875,3.03125 c 0.2392165,0.047351 0.4697735,0.2941069 0.4375,0.53125 -0.010405,0.1211995 -0.066062,0.2235316 -0.1875,0.28125 C -3.0276883,2.0264684 -3.2009829,2.0387215 -3.3125,2 A 0.20792008,0.20792008 0 0 0 -3.34375,2 C -3.6474031,1.9320987 -3.710744,1.2999504 -3.40625,1.1875 a 0.20792008,0.20792008 0 0 0 0.03125,0 c 0.072689,-0.036572 0.1390112,-0.047034 0.21875,-0.03125 z"} + pathElement = etree.SubElement(markerElement, inkex.addNS('path','svg'), line_attribs) + + def draw_crop_line(self, x1, y1, x2, y2, name, parent): + if self.options.dotted_crop_marks == True: + self.addMarker() + style = { 'stroke': '#FFFFFF', 'stroke-width': str(self.stroke_width), + 'fill': 'none'} + else: + style = { 'stroke': '#000000', 'stroke-width': str(self.stroke_width), + 'fill': 'none'} + line_attribs = {'style': str(inkex.Style(style)), + 'id': name, + 'd': 'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)} + etree.SubElement(parent, 'path', line_attribs) + if self.options.dotted_crop_marks == True: + style = { 'stroke': '#000000', 'stroke-width': str(self.stroke_width), + 'fill': 'none' , 'marker-end':'url(#scissorsCroper)', + 'stroke-dasharray' :'0.5,0.25', 'stroke-miterlimit':"4"} + + line_attribs = {'style': str(inkex.Style(style)), + 'id': name + "_dotted", + 'd': 'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)} + etree.SubElement(parent, 'path', line_attribs) + + def draw_bleed_line(self, x1, y1, x2, y2, name, parent): + style = { 'stroke': '#000000', 'stroke-width': str(self.stroke_width), + 'fill': 'none', + 'stroke-miterlimit': '4', 'stroke-dasharray': '4, 2, 1, 2', + 'stroke-dashoffset': '0' } + line_attribs = {'style': str(inkex.Style(style)), + 'id': name, + 'd': 'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)} + etree.SubElement(parent, 'path', line_attribs) + + def draw_reg_circles(self, cx, cy, r, name, colours, parent): + for i in range(len(colours)): + style = {'stroke':colours[i], 'stroke-width':str(r / len(colours)), + 'fill':'none'} + circle_attribs = {'style':str(inkex.Style(style)), + inkex.addNS('label','inkscape'):name, + 'cx':str(cx), 'cy':str(cy), + 'r':str((r / len(colours)) * (i + 0.5))} + etree.SubElement(parent, inkex.addNS('circle','svg'), + circle_attribs) + + def draw_registration_marks(self, cx, cy, rotate, name, parent): + colours = ['#000000','#00ffff','#ff00ff','#ffff00','#000000'] + g = etree.SubElement(parent, 'g', { 'id': name }) + for i in range(len(colours)): + style = {'fill':colours[i], 'fill-opacity':'1', 'stroke':'none'} + r = (self.mark_size/2) + step = r + stroke = r / len(colours) + regoffset = stroke * i + regmark_attribs = {'style': str(inkex.Style(style)), + 'd': 'm' +\ + ' '+str(-regoffset)+','+str(r) +\ + ' '+str(-stroke) +',0' +\ + ' '+str(step) +','+str(-r) +\ + ' '+str(-step) +','+str(-r) +\ + ' '+str(stroke) +',0' +\ + ' '+str(step) +','+str(r) +\ + ' '+str(-step) +','+str(r) +\ + ' z', + 'transform': 'translate('+str(cx)+','+str(cy)+ \ + ') rotate('+str(rotate)+')'} + etree.SubElement(g, 'path', regmark_attribs) + + def draw_star_target(self, cx, cy, name, parent): + r = (self.mark_size/2) + style = {'fill':'#000 device-cmyk(1,1,1,1)', 'fill-opacity':'1', 'stroke':'none'} + d = ' M 0,0' + i = 0 + while i < ( 2 * math.pi ): + i += math.pi / 16 + d += ' L 0,0 ' +\ + ' L '+ str(math.sin(i)*r) +','+ str(math.cos(i)*r) +\ + ' L '+ str(math.sin(i+0.09)*r) +','+ str(math.cos(i+0.09)*r) + regmark_attribs = {'style':str(inkex.Style(style)), + inkex.addNS('label','inkscape'):name, + 'transform':'translate('+str(cx)+','+str(cy)+')', + 'd':d} + etree.SubElement(parent, inkex.addNS('path','svg'), + regmark_attribs) + + def draw_coluor_bars(self, cx, cy, rotate, name, parent): + g = etree.SubElement(parent, 'g', { + 'id':name, + 'transform':'translate('+str(cx)+','+str(cy)+\ + ') rotate('+str(rotate)+')' }) + l = min( self.mark_size / 3, max(self.area_w,self.area_h) / 45 ) + for bar in [{'c':'*', 'stroke':'#000', 'x':0, 'y':-(l+1)}, + {'c':'r', 'stroke':'#0FF', 'x':0, 'y':0}, + {'c':'g', 'stroke':'#F0F', 'x':(l*11)+1, 'y':-(l+1)}, + {'c':'b', 'stroke':'#FF0', 'x':(l*11)+1, 'y':0} + ]: + i = 0 + while i <= 1: + cr = '255' + cg = '255' + cb = '255' + if bar['c'] == 'r' or bar['c'] == '*' : cr = str(255*i) + if bar['c'] == 'g' or bar['c'] == '*' : cg = str(255*i) + if bar['c'] == 'b' or bar['c'] == '*' : cb = str(255*i) + r_att = {'fill':'rgb('+cr+','+cg+','+cb+')', + 'stroke':bar['stroke'], + 'stroke-width':'0.5', + 'x':str((l*i*10)+bar['x']), 'y':str(bar['y']), + 'width':str(l), 'height':str(l)} + r = etree.SubElement(g, 'rect', r_att) + i += 0.1 + + def get_selection_area(self): + scale = self.svg.unittouu('1px') # convert to document units + sel_area = {} + min_x, min_y, max_x, max_y = False, False, False, False + for id in self.options.ids: + sel_area[id] = {} + for att in [ "x", "y", "width", "height" ]: + args = [ "inkscape", "-I", id, "--query-"+att, self.options.input_file ] + sel_area[id][att] = scale* \ + float(Popen(args, stdout=PIPE, stderr=PIPE).communicate()[0]) + current_min_x = sel_area[id]["x"] + current_min_y = sel_area[id]["y"] + current_max_x = sel_area[id]["x"] + \ + sel_area[id]["width"] + current_max_y = sel_area[id]["y"] + \ + sel_area[id]["height"] + if not min_x: min_x = current_min_x + if not min_y: min_y = current_min_y + if not max_x: max_x = current_max_x + if not max_y: max_y = current_max_y + if current_min_x < min_x: min_x = current_min_x + if current_min_y < min_y: min_y = current_min_y + if current_max_x > max_x: max_x = current_max_x + if current_max_y > max_y: max_y = current_max_y + #inkex.errormsg( '>> '+ id + + # ' min_x:'+ str(min_x) + + # ' min_y:'+ str(min_y) + + # ' max_x:'+ str(max_x) + + # ' max_y:'+ str(max_y) ) + self.area_x1 = min_x + self.area_y1 = min_y + self.area_x2 = max_x + self.area_y2 = max_y + self.area_w = max_x - min_x + self.area_h = max_y - min_y + + def effect(self): + self.mark_size = self.svg.unittouu('1cm') + self.min_mark_margin = self.svg.unittouu('3mm') + + if self.options.where_to_crop == 'selection' : + self.get_selection_area() + #inkex.errormsg('Sory, the crop to selection is a TODO feature') + #exit(1) + else : + svg = self.document.getroot() + self.area_w = self.svg.unittouu(svg.get('width')) + self.area_h = self.svg.unittouu(svg.attrib['height']) + self.area_x1 = 0 + self.area_y1 = 0 + self.area_x2 = self.area_w + self.area_y2 = self.area_h + + # Get SVG document dimensions + # self.width must be replaced by self.area_x2. same to others. + svg = self.document.getroot() + #self.width = width = self.svg.unittouu(svg.get('width')) + #self.height = height = self.svg.unittouu(svg.attrib['height']) + + # Convert parameters to user unit + offset = self.svg.unittouu(str(self.options.crop_offset) + \ + self.options.unit) + bt = self.svg.unittouu(str(self.options.bleed_top) + self.options.unit) + bb = self.svg.unittouu(str(self.options.bleed_bottom) + self.options.unit) + bl = self.svg.unittouu(str(self.options.bleed_left) + self.options.unit) + br = self.svg.unittouu(str(self.options.bleed_right) + self.options.unit) + # Bleed margin + if bt < offset : bmt = 0 + else : bmt = bt - offset + if bb < offset : bmb = 0 + else : bmb = bb - offset + if bl < offset : bml = 0 + else : bml = bl - offset + if br < offset : bmr = 0 + else : bmr = br - offset + + # Define the new document limits + offset_left = self.area_x1 - offset + offset_right = self.area_x2 + offset + offset_top = self.area_y1 - offset + offset_bottom = self.area_y2 + offset + + # Get middle positions + middle_vertical = self.area_y1 + ( self.area_h / 2 ) + middle_horizontal = self.area_x1 + ( self.area_w / 2 ) + + # Test if printing-marks layer existis + layer = self.document.xpath( + '//*[@id="printing-marks" and @inkscape:groupmode="layer"]', + namespaces=inkex.NSS) + if layer: svg.remove(layer[0]) # remove if it existis + # Create a new layer + layer = etree.SubElement(svg, 'g') + layer.set('id', 'printing-marks') + layer.set(inkex.addNS('label', 'inkscape'), 'Printing Marks') + layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') + layer.set(inkex.addNS('insensitive', 'sodipodi'), 'true') + + # Crop Mark + if self.options.crop_marks == True: + # Create a group for Crop Mark + g_attribs = {inkex.addNS('label','inkscape'):'CropMarks', + 'id':'CropMarks'} + g_crops = etree.SubElement(layer, 'g', g_attribs) + + # Top left Mark + self.draw_crop_line(self.area_x1, offset_top, + self.area_x1, offset_top - self.mark_size, + 'cropTL1', g_crops) + self.draw_crop_line(offset_left, self.area_y1, + offset_left - self.mark_size, self.area_y1, + 'cropTL2', g_crops) + + # Top right Mark + self.draw_crop_line(self.area_x2, offset_top, + self.area_x2, offset_top - self.mark_size, + 'cropTR1', g_crops) + self.draw_crop_line(offset_right, self.area_y1, + offset_right + self.mark_size, self.area_y1, + 'cropTR2', g_crops) + + # Bottom left Mark + self.draw_crop_line(self.area_x1, offset_bottom, + self.area_x1, offset_bottom + self.mark_size, + 'cropBL1', g_crops) + self.draw_crop_line(offset_left, self.area_y2, + offset_left - self.mark_size, self.area_y2, + 'cropBL2', g_crops) + + # Bottom right Mark + self.draw_crop_line(self.area_x2, offset_bottom, + self.area_x2, offset_bottom + self.mark_size, + 'cropBR1', g_crops) + self.draw_crop_line(offset_right, self.area_y2, + offset_right + self.mark_size, self.area_y2, + 'cropBR2', g_crops) + + # Bleed Mark + if self.options.bleed_marks == True: + # Create a group for Bleed Mark + g_attribs = {inkex.addNS('label','inkscape'):'BleedMarks', + 'id':'BleedMarks'} + g_bleed = etree.SubElement(layer, 'g', g_attribs) + + # Top left Mark + self.draw_bleed_line(self.area_x1 - bl, offset_top - bmt, + self.area_x1 - bl, offset_top - bmt - self.mark_size, + 'bleedTL1', g_bleed) + self.draw_bleed_line(offset_left - bml, self.area_y1 - bt, + offset_left - bml - self.mark_size, self.area_y1 - bt, + 'bleedTL2', g_bleed) + + # Top right Mark + self.draw_bleed_line(self.area_x2 + br, offset_top - bmt, + self.area_x2 + br, offset_top - bmt - self.mark_size, + 'bleedTR1', g_bleed) + self.draw_bleed_line(offset_right + bmr, self.area_y1 - bt, + offset_right + bmr + self.mark_size, self.area_y1 - bt, + 'bleedTR2', g_bleed) + + # Bottom left Mark + self.draw_bleed_line(self.area_x1 - bl, offset_bottom + bmb, + self.area_x1 - bl, offset_bottom + bmb + self.mark_size, + 'bleedBL1', g_bleed) + self.draw_bleed_line(offset_left - bml, self.area_y2 + bb, + offset_left - bml - self.mark_size, self.area_y2 + bb, + 'bleedBL2', g_bleed) + + # Bottom right Mark + self.draw_bleed_line(self.area_x2 + br, offset_bottom + bmb, + self.area_x2 + br, offset_bottom + bmb + self.mark_size, + 'bleedBR1', g_bleed) + self.draw_bleed_line(offset_right + bmr, self.area_y2 + bb, + offset_right + bmr + self.mark_size, self.area_y2 + bb, + 'bleedBR2', g_bleed) + + # Registration Mark + if self.options.registration_marks == True: + # Create a group for Registration Mark + g_attribs = {inkex.addNS('label','inkscape'):'RegistrationMarks', + 'id':'RegistrationMarks'} + g_center = etree.SubElement(layer, 'g', g_attribs) + + # Left Mark + cx = max( bml + offset, self.min_mark_margin ) + self.draw_registration_marks(self.area_x1 - cx - (self.mark_size/2), + middle_vertical - self.mark_size*1.5, + '0', 'regMarkL', g_center) + + # Right Mark + cx = max( bmr + offset, self.min_mark_margin ) + self.draw_registration_marks(self.area_x2 + cx + (self.mark_size/2), + middle_vertical - self.mark_size*1.5, + '180', 'regMarkR', g_center) + + # Top Mark + cy = max( bmt + offset, self.min_mark_margin ) + self.draw_registration_marks(middle_horizontal, + self.area_y1 - cy - (self.mark_size/2), + '90', 'regMarkT', g_center) + + # Bottom Mark + cy = max( bmb + offset, self.min_mark_margin ) + self.draw_registration_marks(middle_horizontal, + self.area_y2 + cy + (self.mark_size/2), + '-90', 'regMarkB', g_center) + + # Star Target + if self.options.star_target == True: + # Create a group for Star Target + g_attribs = {inkex.addNS('label','inkscape'):'StarTarget', + 'id':'StarTarget'} + g_center = etree.SubElement(layer, 'g', g_attribs) + + if self.area_h < self.area_w : + # Left Star + cx = max( bml + offset, self.min_mark_margin ) + self.draw_star_target(self.area_x1 - cx - (self.mark_size/2), + middle_vertical, + 'starTargetL', g_center) + # Right Star + cx = max( bmr + offset, self.min_mark_margin ) + self.draw_star_target(self.area_x2 + cx + (self.mark_size/2), + middle_vertical, + 'starTargetR', g_center) + else : + # Top Star + cy = max( bmt + offset, self.min_mark_margin ) + self.draw_star_target(middle_horizontal - self.mark_size*1.5, + self.area_y1 - cy - (self.mark_size/2), + 'starTargetT', g_center) + # Bottom Star + cy = max( bmb + offset, self.min_mark_margin ) + self.draw_star_target(middle_horizontal - self.mark_size*1.5, + self.area_y2 + cy + (self.mark_size/2), + 'starTargetB', g_center) + + + # Colour Bars + if self.options.colour_bars == True: + # Create a group for Colour Bars + g_attribs = {inkex.addNS('label','inkscape'):'ColourBars', + 'id':'PrintingColourBars'} + g_center = etree.SubElement(layer, 'g', g_attribs) + + if self.area_h > self.area_w : + # Left Bars + cx = max( bml + offset, self.min_mark_margin ) + self.draw_coluor_bars(self.area_x1 - cx - (self.mark_size/2), + middle_vertical + self.mark_size, + 90, + 'PrintingColourBarsL', g_center) + # Right Bars + cx = max( bmr + offset, self.min_mark_margin ) + self.draw_coluor_bars(self.area_x2 + cx + (self.mark_size/2), + middle_vertical + self.mark_size, + 90, + 'PrintingColourBarsR', g_center) + else : + # Top Bars + cy = max( bmt + offset, self.min_mark_margin ) + self.draw_coluor_bars(middle_horizontal + self.mark_size, + self.area_y1 - cy - (self.mark_size/2), + 0, + 'PrintingColourBarsT', g_center) + # Bottom Bars + cy = max( bmb + offset, self.min_mark_margin ) + self.draw_coluor_bars(middle_horizontal + self.mark_size, + self.area_y2 + cy + (self.mark_size/2), + 0, + 'PrintingColourBarsB', g_center) + + + # Page Information + if self.options.page_info == True: + # Create a group for Page Information + g_attribs = {inkex.addNS('label','inkscape'):'PageInformation', + 'id':'PageInformation'} + g_pag_info = etree.SubElement(layer, 'g', g_attribs) + y_margin = max( bmb + offset, self.min_mark_margin ) + txt_attribs = { + 'style': 'font-size:12px;font-style:normal;font-weight:normal;fill:#000000;font-family:Bitstream Vera Sans,sans-serif;text-anchor:middle;text-align:center', + 'x': str(middle_horizontal), + 'y': str(self.area_y2+y_margin+self.mark_size+20) + } + txt = etree.SubElement(g_pag_info, 'text', txt_attribs) + txt.text = 'Page size: ' +\ + str(round(self.svg.uutounit(self.area_w,self.options.unit),2)) +\ + 'x' +\ + str(round(self.svg.uutounit(self.area_h,self.options.unit),2)) +\ + ' ' + self.options.unit + + +if __name__ == '__main__': + PrintingMarksDotted().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/render_silhouette_regmarks/meta.json b/extensions/fablabchemnitz/render_silhouette_regmarks/meta.json new file mode 100644 index 0000000..2b25ac4 --- /dev/null +++ b/extensions/fablabchemnitz/render_silhouette_regmarks/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "Silhouette Cameo Registration Marks", + "id": "fablabchemnitz.de.render_silhouette_regmarks", + "path": "render_silhouette_regmarks", + "dependent_extensions": null, + "original_name": "Classic", + "original_id": "org.inkscape.render.silhouette-regmarks", + "license": "GNU GPL v3", + "license_url": "https://github.com/miLORD1337/silhouette-regmarks/blob/main/LICENSE", + "comment": "", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/render_silhouette_regmarks", + "fork_url": "https://github.com/miLORD1337/silhouette-regmarks", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Silhouette+Cameo+Registration+Marks", + "inkscape_gallery_url": null, + "main_authors": [ + "github.com/miLORD1337", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/render_silhouette_regmarks/render_silhouette_regmarks.inx b/extensions/fablabchemnitz/render_silhouette_regmarks/render_silhouette_regmarks.inx new file mode 100644 index 0000000..d6a7fa5 --- /dev/null +++ b/extensions/fablabchemnitz/render_silhouette_regmarks/render_silhouette_regmarks.inx @@ -0,0 +1,21 @@ + + + Silhouette Cameo Registration Marks + fablabchemnitz.de.silhouette_cameo_registration_marks + 180 + 230 + 15 + 20 + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/render_silhouette_regmarks/render_silhouette_regmarks.py b/extensions/fablabchemnitz/render_silhouette_regmarks/render_silhouette_regmarks.py new file mode 100644 index 0000000..6ce6153 --- /dev/null +++ b/extensions/fablabchemnitz/render_silhouette_regmarks/render_silhouette_regmarks.py @@ -0,0 +1,101 @@ +# +# Copyright (C) 2021 miLORD1337 +# +# 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, USA. +# +""" +Base module for rendering regmarks for Silhouette CAMEO products in Inkscape. +""" + +import inkex +from lxml import etree + +SVG_URI = u'http://www.w3.org/2000/svg' + +class SilhouetteCameoRegistrationMarks(inkex.EffectExtension): + + def add_arguments(self, pars): + # Define string option "--what" with "-w" shortcut and default value "World". + # Layer name static, since self.document.getroot() not available on initialization + self.layername = 'silhouette-regmark' + + # Parse arguments + pars.add_argument("-X", "--reg-x", "--regwidth", type = float, dest = "regwidth", default = 180.0, help="X mark distance [mm]") + pars.add_argument("-Y", "--reg-y", "--reglength", type = float, dest = "reglength", default = 230.0, help="Y mark distance [mm]") + pars.add_argument("--rego-x", "--regoriginx", type = float, dest = "regoriginx", default = 15.0, help="X mark origin from left [mm]") + pars.add_argument("--rego-y", "--regoriginy", type = float, dest = "regoriginy", default = 20.0, help="X mark origin from top [mm]") + + #SVG rect element generation routine + def drawRect(self, size, pos, name): + x, y = pos + w, h = size + rect = etree.Element('{%s}rect' % SVG_URI) + rect.set('x', str(x)) + rect.set('y', str(y)) + rect.set('id', name) + rect.set('width', str(w)) + rect.set('height', str(h)) + rect.set('style', 'fill: black;') + return rect + + #SVG line element generation routine + def drawLine(self, posStart, posEnd, name): + x1, y1 = posStart + x2, y2, = posEnd + line = etree.Element('{%s}line' % SVG_URI) + line.set('x1', str(x1)) + line.set('y1', str(y1)) + line.set('x2', str(x2)) + line.set('y2', str(y2)) + line.set('id', name) + line.set('style', 'stroke: black; stroke-width: 0.5;') + return line + + def effect(self): + svg = self.document.getroot() + + # Create a new layer. + layer = etree.SubElement(svg, 'g') + layer.set(inkex.addNS('label', 'inkscape'), self.layername) + layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') + + # Create square in top left corner + layer.append(self.drawRect((5,5), (self.options.regoriginx,self.options.regoriginy), 'TopLeft')) + + # Create group for top right corner + topRight = etree.Element('{%s}g' % SVG_URI) + topRight.set('id', 'TopRight') + topRight.set('style', 'fill: black;') + + # Create horizontal and vertical lines in group + topRight.append(self.drawLine((self.options.regwidth-20,self.options.regoriginy), (self.options.regwidth,self.options.regoriginy), 'Horizontal')) + topRight.append(self.drawLine((self.options.regwidth,self.options.regoriginy), (self.options.regwidth,self.options.regoriginy + 20), 'Vertical')) + layer.append(topRight) + + # Create group for top right corner + bottomLeft = etree.Element('{%s}g' % SVG_URI) + bottomLeft.set('id', 'BottomLeft') + bottomLeft.set('style', 'fill: black;') + + # Create horizontal and vertical lines in group + bottomLeft.append(self.drawLine((self.options.regoriginx,self.options.reglength), (self.options.regoriginx+20,self.options.reglength), 'Horizontal')) + bottomLeft.append(self.drawLine((self.options.regoriginx,self.options.reglength), (self.options.regoriginx,self.options.reglength - 20), 'Vertical')) + layer.append(bottomLeft) + + #Lock layer + layer.set(inkex.addNS('insensitive', 'sodipodi'), 'true') + +if __name__ == '__main__': + SilhouetteCameoRegistrationMarks().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/scale_to_size/meta.json b/extensions/fablabchemnitz/scale_to_size/meta.json index a7568e0..8668518 100644 --- a/extensions/fablabchemnitz/scale_to_size/meta.json +++ b/extensions/fablabchemnitz/scale_to_size/meta.json @@ -7,14 +7,14 @@ "original_name": "Scale To Size", "original_id": "fablabchemnitz.de.scale_to_size", "license": "GNU GPL v3", - "license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/LICENSE", + "license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/scale_to_size", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/scale_to_size", "fork_url": null, "documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=76054572", "inkscape_gallery_url": null, "main_authors": [ - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/snap_object_points/meta.json b/extensions/fablabchemnitz/snap_object_points/meta.json index c9f27f2..21f2f80 100644 --- a/extensions/fablabchemnitz/snap_object_points/meta.json +++ b/extensions/fablabchemnitz/snap_object_points/meta.json @@ -9,13 +9,13 @@ "license": "GNU GPL v3", "license_url": "https://inkscape.org/de/~pakin/%E2%98%85snap-object-points", "comment": "", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/snap_object_points", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/snap_object_points", "fork_url": "https://inkscape.org/de/~pakin/%E2%98%85snap-object-points", "documentation_url": "", "inkscape_gallery_url": null, "main_authors": [ "inkscape.org/pakin", - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/stroke_color_as_fill/meta.json b/extensions/fablabchemnitz/stroke_color_as_fill/meta.json index 94c2d42..038f491 100644 --- a/extensions/fablabchemnitz/stroke_color_as_fill/meta.json +++ b/extensions/fablabchemnitz/stroke_color_as_fill/meta.json @@ -9,13 +9,13 @@ "license": "GNU GPL v3", "license_url": "https://inkscape.org/de/~jabiertxof/%E2%98%85stroke-color-as-fill", "comment": "ported to Inkscape v1 by Mario Voigt", - "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/stroke_color_as_fill", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/stroke_color_as_fill", "fork_url": "https://inkscape.org/de/~jabiertxof/%E2%98%85stroke-color-as-fill", "documentation_url": "https://stadtfabrikanten.org/display/IFM/Stroke+Color+As+Fill", "inkscape_gallery_url": null, "main_authors": [ "inkscape.org/jabiertxof", - "github.com/vmario89" + "github.com/eridur-de" ] } ] \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/base_transform.py b/extensions/fablabchemnitz/table_support/base_transform.py new file mode 100644 index 0000000..7365808 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/base_transform.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +""" +base_transform.py +Base matemathical operations for SVG 3x3 matrices + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" + +import re +import inkex +import os +from math import * + +class BaseTransform(inkex.Effect): + unitMatrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + + def isset(self, v, i = None): + try: + if (i is None): + v + else: + v[i] + return True + except: + return False + + def __init__(self): + inkex.Effect.__init__(self) + + def sizeToPx(self, s, dim = "y"): + root = self.document.getroot() + try: + factor = float(root.attrib[inkex.addNS('export-' + dim + 'dpi', 'inkscape')]) + except: + factor = 90 + unit = '' + pattern = '[\\-\\d\\.]+([a-zA-Z][a-zA-Z])' + if (re.search(pattern, s)): + res = re.search(pattern, s) + unit = res.group(1) + pattern = '^([\\-\\d\\.]*)' + res = re.search(pattern, s) + n = float(res.group(1)) + if unit == 'cm': + return (n / 2.54) * factor + elif unit == 'ft': + return n * 12 * factor + elif unit == 'in': + return n * factor + elif unit == 'm': + return ((n * 10) / 2.54) * factor + elif unit == 'mm': + return ((n / 10) / 2.54) * factor + elif unit == 'pc': + return ((n * 2.36228956229) / 2.54) * factor + elif unit == 'pt': + return (((n / 2.83464646465) / 10) / 2.54) * factor + elif unit == 'px' or unit == '': + return n + + return 0 + + def transform(self, el): + result = self.unitMatrix + if (el.tag == inkex.addNS('svg', 'svg')): + return result + + if (not self.isset(el.attrib, 'transform')): + return self.multiply(self.transform(el.getparent()), result) + pattern = '(matrix|translate|scale|rotate|skewX|skewY)[\\s|,]*\\(([^\\)]*)\\)' + transforms = re.findall(pattern, el.attrib['transform']) + + for transform in transforms: + values = re.split('[\\s|,]+', transform[1]) + for i in range(len(values)): + values[i] = float(values[i]) + function = transform[0] + if (function == 'matrix'): + a = [[values[0], values[2], values[4]], + [values[1], values[3], values[5]], + [0, 0, 1]] + result = self.multiply(result, a) + elif (function == 'translate'): + a = [[1, 0, values[0]], + [0, 1, values[1]], + [0, 0, 1]] + result = self.multiply(result, a) + elif (function == 'scale'): + a = [[values[0], 0, 0], + [0, values[1], 0], + [0, 0, 1]] + result = self.multiply(result, a) + elif (function == 'rotate'): + if (len(values) == 1): + a = [[math.cos(values[0]), -math.sin(values[0]), 0], + [math.sin(values[0]), math.cos(values[0]), 0], + [0, 0, 1]] + result = self.multiply(result, a) + else: + a = [[1, 0, values[2]], + [0, 1, values[2]], + [0, 0, 1]] + result = self.multiply(result, a) + a = [[math.cos(values[0]), -math.sin(values[0]), 0], + [math.sin(values[0]), math.cos(values[0]), 0], + [0, 0, 1]] + result = self.multiply(result, a) + a = [[1, 0, -values[2]], + [0, 1, -values[2]], + [0, 0, 1]] + result = self.multiply(result, a) + elif (function == 'skewX'): + a = [[1, math.tan(values[0]), 0], + [0, 1, 0], + [0, 0, 1]] + result = self.multiply(result, a) + elif (function == 'skewY'): + a = [[1, 0, 0], + [math.tan(values[0]), 1, 0], + [0, 0, 1]] + result = self.multiply(result, a) + + return self.multiply(self.transform(el.getparent()), result) + + + def getPosition(self, el): + if not self.isset(el.attrib, 'x'): + return False + + x = self.sizeToPx(el.attrib['x'], 'x') + y = self.sizeToPx(el.attrib['y'], 'y') + v = [x, y, 1] + t = self.transform(el) + v = self.multiply(t, v) + + return {'coordinates': v, 'matrix': t} + + def setPosition(self, el, position): + c = position['coordinates'] + a = position['matrix'] + if (not self.isUnitMatrix(a)): + c = self.multiply(self.inverse(a), c) + el.set('x', str(c[0])) + el.set('y', str(c[1])) + + + def determinant(self, a): + if len(a) != 3: + return False + if (len(a[0]) != 3): + return False + + det = a[0][0] * (a[1][1] * a[2][2] - a[2][1] * a[1][2]) - a[0][1] * (a[1][0] * a[2][2] - a[2][0] * a[1][2]) + a[0][2] * (a[1][0] * a[2][1] - a[2][0] * a[1][1]) + + if (det == 0): + det = 0.00001 + + return det + + def minors(self, a): + if len(a) != 3: + return False + if (len(a[0]) != 3): + return False + + return [[a[1][1] * a[2][2] - a[2][1] * a[1][2], a[1][0] * a[2][2] - a[2][0] * a[1][2], a[1][0] * a[2][1] - a[2][0] * a[1][1]], + [a[0][1] * a[2][2] - a[2][1] * a[0][2], a[0][0] * a[2][2] - a[0][2] * a[2][0], a[0][0] * a[2][1] - a[2][0] * a[0][1]], + [a[0][1] * a[1][2] - a[1][1] * a[0][2], a[0][0] * a[1][2] - a[0][1] * a[0][2], a[0][0] * a[1][1] - a[1][0] * a[0][1]] + ] + + def cofactors(self, a): + if len(a) != 3: + return False + if (len(a[0]) != 3): + return False + + return [[a[0][0], -a[0][1], a[0][2]], + [-a[1][0], a[1][1], -a[1][2]], + [a[2][0], -a[2][1], a[2][2]] + ] + + def adjoint(self, a): + if len(a) != 3: + return False + if (len(a[0]) != 3): + return False + + return [[a[0][0], a[1][0], a[2][0]], + [a[0][1], a[1][1], a[2][1]], + [a[0][2], a[1][2], a[2][2]] + ] + + def inverse(self, a): + if len(a) != 3: + return False + if (len(a[0]) != 3): + return False + + det = self.determinant(a) + m = self.minors(a) + c = self.cofactors(m) + adj = self.adjoint(c) + + return [[adj[0][0] / det, adj[0][1] / det, adj[0][2] / det], + [adj[1][0] / det, adj[1][1] / det, adj[1][2] / det], + [adj[2][0] / det, adj[2][1] / det, adj[2][2] / det] + ] + + def multiply(self, a, v): + if len(a) != 3: + return False + if (len(a[0]) != 3): + return False + + if (len(v) != 3): + return False + + if (not self.isset(v[0], 0)): + return [a[0][0] * v[0] + a[0][1] * v[1] + a[0][2] * v[2], + a[1][0] * v[0] + a[1][1] * v[1] + a[1][2] * v[2], + a[2][0] * v[0] + a[2][1] * v[1] + a[2][2] * v[2] + ] + else: + return [[a[0][0] * v[0][0] + a[0][1] * v[1][0] + a[0][2] * v[2][0], a[0][0] * v[0][1] + a[0][1] * v[1][1] + a[0][2] * v[2][1], a[0][0] * v[0][2] + a[0][1] * v[1][2] + a[0][2] * v[2][2]], + [a[1][0] * v[0][0] + a[1][1] * v[1][0] + a[1][2] * v[2][0], a[1][0] * v[0][1] + a[1][1] * v[1][1] + a[1][2] * v[2][1], a[1][0] * v[0][2] + a[1][1] * v[1][2] + a[1][2] * v[2][2]], + [a[2][0] * v[0][0] + a[2][1] * v[1][0] + a[2][2] * v[2][0], a[2][0] * v[0][1] + a[2][1] * v[1][1] + a[2][2] * v[2][1], a[2][0] * v[0][2] + a[2][1] * v[1][2] + a[2][2] * v[2][2]] + ] + + def isUnitMatrix(self, a): + if (len(a) != 3): + return False + if (len(a[0]) != 3): + return False + + for i in range(3): + for j in range(3): + if (a[i][j] != self.unitMatrix[i][j]): + return False + + return True + + def reParse(self): + if os.name == 'nt': + path = os.environ['USERPROFILE'] + else: + path = os.path.expanduser("~") + text = inkex.etree.tostring(self.document.getroot()) + f = open(path + '/tmp.svg', 'w') + f.write(text) + f.close() + self.parse(path + '/tmp.svg') + + os.remove(path + '/tmp.svg') + + def matrix2string(self, a): + return 'matrix(' + str(a[0][0]) + ',' + str(a[1][0]) + ',' + str(a[0][1]) + ',' + str(a[1][1]) + ',' + str(a[0][2]) + ',' + str(a[1][2]) + ')' \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/meta.json b/extensions/fablabchemnitz/table_support/meta.json new file mode 100644 index 0000000..2f0bb6e --- /dev/null +++ b/extensions/fablabchemnitz/table_support/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "", + "id": "fablabchemnitz.de.table_", + "path": "table_support", + "dependent_extensions": null, + "original_name": "", + "original_id": "org.greygreen.inkscape.effects.table_", + "license": "GNU GPL v2", + "license_url": "https://sourceforge.net/projects/inkscape-tables/files/inkscape-table-1.0.tar.gz/download", + "comment": "ported to Inkscape v1 by Mario Voigt", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/table_support", + "fork_url": "https://sourceforge.net/projects/inkscape-tables", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Table+Support", + "inkscape_gallery_url": null, + "main_authors": [ + "sourceforge.net/lixapopescu", + "github.com/eridur-de" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table.py b/extensions/fablabchemnitz/table_support/table.py new file mode 100644 index 0000000..f87c105 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table.py @@ -0,0 +1,850 @@ +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import math + +import inkex +from inkex import Guide +from lxml import etree +import base_transform +import re + +class TableEngine(base_transform.BaseTransform): + + defaultId = 'inkscape-table' + cell_type_row = 'row' + cell_type_column = 'column' + normalizeFactor = 5 + tablesCount = 0 + tables = None + selectedTables = {} + mergedCells = {} + tablesTexts = {} + get_tables = True + auto_split = True + delimiter = ',' + + def __init__(self, get_tables = True, auto_split = True): + inkex.NSS['inkex'] = 'http://sodipodi.sourceforge.net/DTD/inkex-0.dtd' + self.get_tables = get_tables + self.auto_split = auto_split + base_transform.BaseTransform.__init__(self) + + def getTablesCount(self): + node = self.document.xpath('//inkex:tables', namespaces = inkex.NSS) + if len(node) == 0: + xml = '' + self.document.getroot().append(etree.fromstring(xml)) + node = self.document.xpath('//inkex:tables', namespaces = inkex.NSS) + else: + self.tablesCount = int(node[0].attrib['count']) + + self.tables = node[0] + + def isTableCell(self, id): + el = self.svg.getElementById(id) + if (el == None): + return False + + if (self.isset(el.attrib, inkex.addNS('table-id', 'inkex'))): + tableId = el.attrib[inkex.addNS('table-id', 'inkex')] + if (re.search('\\-text$', tableId)): + return False + else: + return True + + return False + + def effect(self): + self.getTablesCount() + if (self.get_tables): + self.getAllTables() + if (self.auto_split): + for id, table in self.selectedTables.items(): + for i in range(len(table)): + for j in range(len(table[i])): + if (table[i][j] != None): + points = self.splitCell(table[i][j]) + if (points != False): + if (self.isset(self.mergedCells, id)): + self.mergedCells[id].append(points) + else: + self.mergedCells[id] = [points] + for tableId in self.mergedCells: + self.getTable(tableId) + self.doinkex() + if (self.get_tables): + if (self.auto_split): + for tableId in self.mergedCells: + self.getTableText(tableId) + for points in self.mergedCells[tableId]: + if (not self.isset(points, 'removed')): + self.mergeTable(tableId, points) + + def newCell(self, x, y, width, height, id, i, j, transform = None): + #path = '//*[@inkex:table-id="%s"]' % id + _id = self.svg.get_unique_id(id) + etree.SubElement(self.svg.get_current_layer(), 'rect', { + 'id': _id, + 'style': 'fill:none;stroke:#000000;stroke-width:1px;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none', + 'width': str(width) + 'px', + 'height': str(height) + 'px', + 'x': str(x) + 'px', + 'y': str(y) + 'px', + inkex.addNS('table-id', 'inkex'): id, + inkex.addNS('row', 'inkex'): str(i), + inkex.addNS('column', 'inkex'): str(j) + }) + + if (transform != None): + el = self.svg.getElementById(_id) + el.set('transform', transform) + + return _id + + ''' + _id = self.svg.get_unique_id(id) + content = '' + + self.svg.get_current_layer().append(etree.XML(content)) + el = self.svg.getElementById(_id) + el.set(inkex.addNS('table-id', 'inkex'), id) + el.set(inkex.addNS('row', 'inkex'), str(i)) + el.set(inkex.addNS('column', 'inkex'), str(j)) + ''' + + def create(self, width, height, cols, rows): + tableId = self.defaultId + str(self.tablesCount) + self.tablesCount += 1 + self.tables.set('count', str(self.tablesCount)) + content = '' + self.tables.append(etree.fromstring(content)) + + width = self.sizeToPx(width, 'x') + height = self.sizeToPx(height, 'y') + + x = 0 + y = 0 + + content = '' + + for i in range(rows): + x = 0 + for j in range(cols): + self.newCell(x, y, width, height, tableId, i, j) + x += width + y += height + + def getTree(self, id): + ids = [id] + el = self.svg.getElementById(id) + for _el in list(el): + for _id in self.getTree(_el.attrib['id']): + ids.append(_id) + return ids + + + def getSubSelectedIds(self): + ids = [] + for id in self.svg.selected.ids: + for _id in self.getTree(id): + ids.append(_id) + return ids + + def getAllTables(self): + ids = self.getSubSelectedIds() + for id in ids: + el = self.svg.getElementById(id) + if (self.isTableCell(id)): + tableId = el.attrib[inkex.addNS('table-id', 'inkex')] + if (not self.isset(self.selectedTables, tableId)): + self.getTable(tableId) + self.tablesTexts[tableId] = self.getTableText(tableId) + + def getTableDimensions(self, tableId): + nodes = self.tables.xpath('//inkex:table[@table-id="' + tableId + '"]', namespaces = inkex.NSS) + if (len(nodes) > 0): + return {'rows': int(nodes[0].attrib['rows']), 'cols': int(nodes[0].attrib['columns'])} + return False + + def setTableDimensions(self, tableId, dimensions): + table_dim = self.tables.xpath('//inkex:table[@table-id="' + tableId + '"]', namespaces = inkex.NSS) + if (len(table_dim) > 0): + table_dim[0].set('rows', str(dimensions['rows'])) + table_dim[0].set('columns', str(dimensions['cols'])) + + + def getTable(self, tableId): + nodes = self.tables.xpath('//inkex:table[@table-id="' + tableId + '"]', namespaces = inkex.NSS) + if (len(nodes) > 0): + cols = int(nodes[0].attrib['columns']) + rows = int(nodes[0].attrib['rows']) + table = [[None for i in range(cols)] for j in range(rows)] + path = '//*[@inkex:table-id="' + tableId + '"]' + cells = self.document.xpath(path, namespaces = inkex.NSS) + for cell in cells: + i = int(cell.attrib[inkex.addNS('row', 'inkex')]) + j = int(cell.attrib[inkex.addNS('column', 'inkex')]) + table[i][j] = cell.attrib['id'] + self.selectedTables[tableId] = table + + def getTableText(self, tableId): + nodes = self.tables.xpath('//inkex:table[@table-id="' + tableId + '"]', namespaces = inkex.NSS) + if (len(nodes) > 0): + cols = int(nodes[0].attrib['columns']) + rows = int(nodes[0].attrib['rows']) + texts = [[None for i in range(cols)] for j in range(rows)] + path = '//*[@inkex:table-id="' + tableId + '-text"]' + cells = self.document.xpath(path, namespaces = inkex.NSS) + for cell in cells: + i = int(cell.attrib[inkex.addNS('row', 'inkex')]) + j = int(cell.attrib[inkex.addNS('column', 'inkex')]) + texts[i][j] = cell.attrib['id'] + return texts + return None + + def doAddGuide(self, el, type): + px = self.sizeToPx(str(self.svg.unittouu(self.document.getroot().attrib['height'])), 'y') + + position = self.getPosition(el) + if (position != False): + c = position['coordinates'] + a = position['matrix'] + x = c[0] + y = c[1] + angle = math.acos(a[0][0]) * 180 / math.pi + if angle < 90: + angle = 90 - angle + elif angle < 180: + angle = 180 - angle + elif angle < 270: + angle = 270 - angle + else: + angle = 360 - angle + if (type == self.cell_type_row): + angle += 90 + self.svg.namedview.add(Guide().move_to(str(x), str(px - y), angle)) + + def _addGuides(self, tableId, type): + table = self.selectedTables[tableId] + + count = len(table) + if (type == self.cell_type_column): + count = len(table[0]) + + for i in range(count): + _i = i + _j = 0 + if (type == self.cell_type_column): + _i = 0 + _j = i + el = self.svg.getElementById(table[_i][_j]) + self.doAddGuide(el, type) + + if (i == count - 1): + if (type == self.cell_type_column): + el.attrib['x'] = str(self.sizeToPx(el.attrib['x'], 'x') + self.sizeToPx(el.attrib['width'], 'x')) + else: + el.attrib['y'] = str(self.sizeToPx(el.attrib['y'], 'y') + self.sizeToPx(el.attrib['height'], 'y')) + self.doAddGuide(el, type) + if (type == self.cell_type_column): + el.attrib['x'] = str(self.sizeToPx(el.attrib['x'], 'x') - self.sizeToPx(el.attrib['width'], 'x')) + else: + el.attrib['y'] = str(self.sizeToPx(el.attrib['y'], 'y') - self.sizeToPx(el.attrib['height'], 'y')) + + def addGuides(self, type): + for tableId in self.selectedTables: + self._addGuides(tableId, type) + + def doEditText(self, id, fontSize): + el = self.svg.getElementById(id) + if (not self.isTableCell(id)): + return + + position = self.getPosition(el) + if (position != False): + a = position['matrix'] + if (not self.isUnitMatrix(a)): + transform = 'transform="' + self.matrix2string(a) + '"' + else: + transform = '' + content = 'text here' + + self.svg.get_current_layer().append(etree.fromstring(content)) + + def editText(self, fontSize): + ids = self.getSubSelectedIds() + + for id in ids: + self.doEditText(id, fontSize) + + def getColumnIndex(self, id): + el = self.svg.getElementById(id) + if (self.isset(el.attrib, inkex.addNS('column', 'inkex'))): + return int(el.attrib[inkex.addNS('column', 'inkex')]) + + return -1 + + def getRowIndex(self, id): + el = self.svg.getElementById(id) + if (self.isset(el.attrib, inkex.addNS('row', 'inkex'))): + return int(el.attrib[inkex.addNS('row', 'inkex')]) + + return -1 + + def setTextRect(self, text, c): + for child in list(text): + if (child.tag == inkex.addNS('flowRegion', 'svg')): + for subchild in list(child): + if (subchild.tag == inkex.addNS('rect', 'svg')): + for key, value in c.items(): + if value != None: + subchild.set(key, str(value)) + break + + def getTextRect(self, text): + for child in list(text): + if (child.tag == inkex.addNS('flowRegion', 'svg')): + for subchild in list(child): + if (subchild.tag == inkex.addNS('rect', 'svg')): + return subchild + + return None + + def moveCells(self, tableId, idx, delta, type): + table = self.selectedTables[tableId] + texts = self.tablesTexts[tableId] + if (type == self.cell_type_column): + starti = 0 + startj = idx + else: + starti = idx + startj = 0 + + for i in range(starti, len(table)): + for j in range(startj, len(table[i])): + el = self.svg.getElementById(table[i][j]) + position = self.getPosition(el) + if (position != False): + c = [self.sizeToPx(el.attrib['x'], 'x'), self.sizeToPx(el.attrib['y'], 'y')] + c[0] += delta[0] + c[1] += delta[1] + el.set('x', str(c[0])) + el.set('y', str(c[1])) + if (texts != None): + if (texts[i][j] != None): + el = self.svg.getElementById(texts[i][j]) + rect = self.getTextRect(el) + if (rect != None): + c[0] = self.sizeToPx(rect.attrib['x'], 'x') + delta[0] + c[1] = self.sizeToPx(rect.attrib['y'], 'y') + delta[1] + self.setTextRect(el, {'x': c[0], 'y': c[1]}) + + def setCellSize(self, tableId, idx, size, type): + table = self.selectedTables[tableId] + texts = self.tablesTexts[tableId] + if (type == self.cell_type_column): + size = self.sizeToPx(size, 'x') + old_size = self.sizeToPx(self.svg.getElementById(table[0][idx]).attrib['width'], 'x') + else: + size = self.sizeToPx(size, 'y') + old_size = self.sizeToPx(self.svg.getElementById(table[idx][0]).attrib['height'], 'y') + + if (type == self.cell_type_column): + delta = [size - old_size, 0, 1] + else: + delta = [0, size - old_size, 1] + + if ((idx + 1 < len(table) and type == self.cell_type_row) or (idx + 1 < len(table[0]) and type == self.cell_type_column)): + self.moveCells(tableId, idx + 1, delta, type) + + count = len(table[idx]) + if (type == self.cell_type_column): + count = len(table) + + for i in range(count): + _i = idx + _j = i + if type == self.cell_type_column: + _i = i + _j = idx + el = self.svg.getElementById(table[_i][_j]) + param = 'height' + if (type == self.cell_type_column): + param = 'width' + + el.set(param, str(size)) + + if texts != None: + if texts[_i][_j] != None: + el = self.svg.getElementById(texts[_i][_j]) + self.setTextRect(el, {param: size}) + + def editSize(self, size, type): + processed = {} + for node in self.svg.selected.values(): + id = node.get('id') + if (self.isTableCell(id)): + tableId = node.attrib[inkex.addNS('table-id', 'inkex')] + if (type == self.cell_type_column): + idx = self.getColumnIndex(id) + else: + idx = self.getRowIndex(id) + if (not self.isset(processed, tableId)): + processed[tableId] = [] + + if (not self.isset(processed[tableId], idx)): + self.setCellSize(tableId, idx, size, type) + processed[tableId].append(idx) + + def getTableWidth(self, tableId): + table = self.selectedTables[tableId] + width = 0 + for i in range(len(table[0])): + el = self.svg.getElementById(table[0][i]) + width += self.sizeToPx(el.attrib['width'], 'x') + + return width + + def getTableHeight(self, tableId): + table = self.selectedTables[tableId] + height = 0 + for i in range(len(table)): + el = self.svg.getElementById(table[i][0]) + height += self.sizeToPx(el.attrib['height'], 'y') + + return height + + def setTableSize(self, tableId, size, type): + if (type == self.cell_type_column): + size = self.sizeToPx(size, 'x') + old_size = self.getTableWidth(tableId) + else: + size = self.sizeToPx(size, 'y') + old_size = self.getTableHeight(tableId) + + factor = size / old_size + table = self.selectedTables[tableId] + count = len(table) + if (type == self.cell_type_column): + count = len(table[0]) + + for i in range(count): + if (type == self.cell_type_column): + el = self.svg.getElementById(table[0][i]) + new_size = self.sizeToPx(el.attrib['width'], 'x') * factor + else: + el = self.svg.getElementById(table[i][0]) + new_size = self.sizeToPx(el.attrib['height'], 'y') * factor + self.setCellSize(tableId, i, str(new_size), type) + + def editTable(self, width, height): + for id in self.selectedTables: + self.setTableSize(id, width, self.cell_type_column) + self.setTableSize(id, height, self.cell_type_row) + + def getTablePosition(self, tableId): + table = self.selectedTables[tableId] + el = self.svg.getElementById(table[0][0]) + return self.getPosition(el) + + def fitPage(self): + width = str(self.svg.unittouu(self.document.getroot().attrib['width'])) + height = str(self.svg.unittouu(self.document.getroot().attrib['height'])) + + for id in self.selectedTables: + position = self.getTablePosition(id) + if (position != False): + c = position['coordinates'] + self.moveCells(id, 0, [-c[0], -c[1], 1], type) + self.setTableSize(id, width, self.cell_type_column) + self.setTableSize(id, height, self.cell_type_row) + + def fitPageWidth(self): + width = str(self.svg.unittouu(self.document.getroot().attrib['width'])) + + for id in self.selectedTables: + position = self.getTablePosition(id) + if (position != False): + c = position['coordinates'] + self.moveCells(id, 0, [-c[0], 0, 1], type) + self.setTableSize(id, width, self.cell_type_column) + + def fitPageHeight(self): + height = str(self.svg.unittouu(self.document.getroot().attrib['height'])) + + for id in self.selectedTables: + position = self.getTablePosition(id) + if (position != False): + c = position['coordinates'] + self.moveCells(id, 0, [0, -c[1], 1], type) + self.setTableSize(id, height, self.cell_type_row) + + def getSelectedListIds(self): + idList = [] + for id in self.getSubSelectedIds(): + idList.append(id) + return idList + + def getCellText(self, tableId, i, j): + texts = self.tablesTexts[tableId] + if (texts != None): + if (texts[i][j] != None): + return self.svg.getElementById(texts[i][j]) + return None + + def getMergePoints(self, tableId): + dim = self.getTableDimensions(tableId) + table = self.selectedTables[tableId] + idList = self.getSelectedListIds() + start = [] + for i in range(dim['rows']): + for j in range(dim['cols']): + if (idList.count(table[i][j]) > 0): + start = [i, j] + break + if len(start) > 0: + break + + if (len(start) != 2): + return False + + end = [1, 1] + + for i in range(start[0] + 1, len(table)): + if (idList.count(table[i][start[1]]) > 0): + end[0] += 1 + else: + break + + for i in range(start[1] + 1, len(table[0])): + if (idList.count(table[start[0]][i]) > 0): + end[1] += 1 + else: + break + + for i in range(start[0], start[0] + end[0]): + for j in range(start[1], start[1] + end[1]): + if (idList.count(table[i][j]) == 0): + return False + + return {'i': start[0], 'j': start[1], 'rows': end[0], 'cols': end[1]} + + def mergeTable(self, tableId, points = None): + if (points == None): + points = self.getMergePoints(tableId) + if (points == False): + inkex.errormsg('You have to select the cells to form a rectangle from a single table.') + return + + table = self.selectedTables[tableId] + cell = self.svg.getElementById(table[points['i']][points['j']]) + width = 0 + height = 0 + widths = '' + heights = '' + + for i in range(points['i'], points['i'] + points['rows']): + el = self.svg.getElementById(table[i][points['j']]) + height += self.sizeToPx(el.attrib['height'], 'y') + if (heights != ''): + heights += self.delimiter + heights += el.attrib['height'] + + for j in range(points['j'], points['j'] + points['cols']): + el = self.svg.getElementById(table[points['i']][j]) + width += self.sizeToPx(el.attrib['width'], 'x') + if (widths != ''): + widths += self.delimiter + widths += el.attrib['width'] + + for i in range(points['i'], points['i'] + points['rows']): + for j in range(points['j'], points['j'] + points['cols']): + if (i != points['i'] or j != points['j']): + el = self.svg.getElementById(table[i][j]) + el.getparent().remove(el) + text = self.getCellText(tableId, i, j) + if (text != None): + text.getparent().remove(text) + + cell.set('width', str(width) + 'px') + cell.set('height', str(height) + 'px') + cell.set(inkex.addNS('merged', 'inkex'), str(points['rows']) + self.delimiter + str(points['cols'])) + cell.set(inkex.addNS('merged-columns-widths', 'inkex'), widths) + cell.set(inkex.addNS('merged-rows-heights', 'inkex'), heights) + + text = self.getCellText(tableId, points['i'], points['j']) + + if (text != None): + rect = self.getTextRect(text) + rect.set('width', str(width) + 'px') + rect.set('height', str(height) + 'px') + + + def mergeMerge(self): + for id in self.selectedTables: + self.mergeTable(id) + + def splitCell(self, cellId): + cell = self.svg.getElementById(cellId) + if (self.isset(cell.attrib, inkex.addNS('merged', 'inkex'))): + tableId = cell.attrib[inkex.addNS('table-id', 'inkex')] + row = int(cell.attrib[inkex.addNS('row', 'inkex')]) + column = int(cell.attrib[inkex.addNS('column', 'inkex')]) + position = self.getPosition(cell) + + merge_size = cell.attrib[inkex.addNS('merged', 'inkex')].split(self.delimiter) + widths = cell.attrib[inkex.addNS('merged-columns-widths', 'inkex')].split(self.delimiter) + heights = cell.attrib[inkex.addNS('merged-rows-heights', 'inkex')].split(self.delimiter) + + y = self.sizeToPx(cell.attrib['y'], 'y') + + for i in range(row, row + int(merge_size[0])): + x = self.sizeToPx(cell.attrib['x'], 'x') + for j in range(column, column + int(merge_size[1])): + if (i != row or j != column): + transform = None + if position != False: + a = position['matrix'] + if (not self.isUnitMatrix(a)): + transform = self.matrix2string(a) + self.newCell(x, y, self.sizeToPx(widths[j - column], 'x'), self.sizeToPx(heights[i - row], 'y'), tableId, i, j, transform) + x += self.sizeToPx(widths[j - column], 'x') + y += self.sizeToPx(heights[i - row], 'y') + + result = {'i': row, 'j': column, 'rows': int(merge_size[0]), 'cols': int(merge_size[1])} + cell.set('width', widths[0]) + cell.set('height', heights[0]) + text = self.getCellText(tableId, row, column) + if (text != None): + rect = self.getTextRect(text) + rect.set('width', widths[0]) + rect.set('height', heights[0]) + del cell.attrib[inkex.addNS('merged', 'inkex')] + del cell.attrib[inkex.addNS('merged-columns-widths', 'inkex')] + del cell.attrib[inkex.addNS('merged-rows-heights', 'inkex')] + + return result + return False + + def mergeSplit(self): + for id in self.svg.selected.ids: + self.splitCell(id) + + def updateMergedPoints(self, tableId, idx, delta, type): + if (self.get_tables): + if (self.auto_split): + if (self.isset(self.mergedCells, tableId)): + for points in self.mergedCells[tableId]: + cond1 = idx > points['i'] and idx < points['i'] + points['rows'] + cond2 = idx <= points['i'] + if (type == self.cell_type_column): + cond1 = idx > points['j'] and idx < points['j'] + points['cols'] + cond2 = idx <= points['j'] + + if (cond1): + if (type == self.cell_type_column): + points['cols'] += delta + if (points['cols'] <= 1): + points['removed'] = 1 + else: + points['rows'] += delta + if (points['rows'] <= 1): + points['removed'] = 1 + elif (cond2): + if (type == self.cell_type_column): + if (delta == -1 and idx == points['j']): + points['cols'] += delta + if (points['cols'] <= 1): + points['removed'] = 1 + else: + points['j'] += delta + else: + if (delta == -1 and idx == points['i']): + points['rows'] += delta + if (points['rows'] <= 1): + points['removed'] = 1 + else: + points['i'] += delta + + def incrementCoordonate(self, tableId, idx, inc, type): + table = self.selectedTables[tableId] + texts = self.getTableText(tableId) + starti = idx + startj = 0 + dim = self.getTableDimensions(tableId) + if type == self.cell_type_column: + dim['cols'] += inc + else: + dim['rows'] += inc + self.setTableDimensions(tableId, dim) + + if (type == self.cell_type_column): + starti = 0 + startj = idx + + for i in range(starti, len(table)): + for j in range(startj, len(table[i])): + cell = self.svg.getElementById(table[i][j]) + text = self.svg.getElementById(texts[i][j]) + if (type == self.cell_type_column): + cell.set(inkex.addNS('column', 'inkex'), str(int(cell.attrib[inkex.addNS('column', 'inkex')]) + inc)) + if (text != None): + text.set(inkex.addNS('column', 'inkex'), str(int(text.attrib[inkex.addNS('column', 'inkex')]) + inc)) + else: + cell.set(inkex.addNS('row', 'inkex'), str(int(cell.attrib[inkex.addNS('row', 'inkex')]) + inc)) + if (text != None): + text.set(inkex.addNS('row', 'inkex'), str(int(text.attrib[inkex.addNS('row', 'inkex')]) + inc)) + + def addCell(self, tableId, idx, type): + table = self.selectedTables[tableId] + if (type == self.cell_type_column): + if (idx != -1): + delta = [self.sizeToPx(self.svg.getElementById(table[0][idx]).attrib['width'], 'x'), 0, 1] + else: + delta = [self.sizeToPx(self.svg.getElementById(table[0][0]).attrib['width'], 'x'), 0, 1] + else: + if (idx != -1): + delta = [0, self.sizeToPx(self.svg.getElementById(table[idx][0]).attrib['height'], 'y'), 1] + else: + delta = [0, self.sizeToPx(self.svg.getElementById(table[0][0]).attrib['height'], 'y'), 1] + + count = len(table) + if type == self.cell_type_row: + if (idx != -1): + count = len(table[idx]) + else: + count = len(table[0]) + for i in range(count): + if (type == self.cell_type_column): + if (idx != -1): + cell = self.svg.getElementById(table[i][idx]) + else: + cell = self.svg.getElementById(table[i][0]) + else: + if (idx != -1): + cell = self.svg.getElementById(table[idx][i]) + else: + cell = self.svg.getElementById(table[0][i]) + + position = self.getPosition(cell) + transform = '' + if (position != False): + a = position['matrix'] + if (not self.isUnitMatrix(a)): + transform = self.matrix2string(a) + x = self.sizeToPx(cell.attrib['x'], 'x') + y = self.sizeToPx(cell.attrib['y'], 'y') + width = self.sizeToPx(cell.attrib['width'], 'x') + height = self.sizeToPx(cell.attrib['height'], 'y') + + if (type == self.cell_type_column): + if (idx != -1): + x += width + self.newCell(x, y, width, height, tableId, i, idx + 1, transform) + else: + if (idx != -1): + y += height + self.newCell(x, y, width, height, tableId, idx + 1, i, transform) + + self.moveCells(tableId, idx + 1, delta, type) + self.updateMergedPoints(tableId, idx + 1, 1, type) + self.incrementCoordonate(tableId, idx + 1, 1, type) + self.getTable(tableId) + self.tablesTexts[tableId] = self.getTableText(tableId) + + def addColumns(self, count, where): + for id in self.svg.selected.ids: + el = self.svg.getElementById(id) + if (self.isTableCell(id)): + tableId = el.attrib[inkex.addNS('table-id', 'inkex')] + idx = self.getColumnIndex(id) + if (where == 'before'): + idx -= 1 + + for i in range(count): + self.addCell(tableId, idx, self.cell_type_column) + + def addRows(self, count, where): + for id in self.svg.selected.ids: + el = self.svg.getElementById(id) + if (self.isTableCell(id)): + tableId = el.attrib[inkex.addNS('table-id', 'inkex')] + idx = self.getRowIndex(id) + if (where == 'before'): + idx -= 1 + + for i in range(count): + self.addCell(tableId, idx, self.cell_type_row) + + break + + def removeCell(self, tableId, idx, type): + table = self.selectedTables[tableId] + texts = self.tablesTexts[tableId] + if (type == self.cell_type_column): + delta = [-self.sizeToPx(self.svg.getElementById(table[0][idx]).attrib['width'], 'x'), 0, 1] + else: + delta = [0, -self.sizeToPx(self.svg.getElementById(table[idx][0]).attrib['height'], 'y'), 1] + + count = len(table) + if type == self.cell_type_row: + count = len(table[idx]) + + for i in range(count): + if (type == self.cell_type_column): + cell = self.svg.getElementById(table[i][idx]) + text = self.svg.getElementById(texts[i][idx]) + else: + cell = self.svg.getElementById(table[idx][i]) + text = self.svg.getElementById(texts[idx][i]) + + if (cell != None): + cell.getparent().remove(cell) + if (text != None): + text.getparent().remove(text) + + self.moveCells(tableId, idx + 1, delta, type) + self.updateMergedPoints(tableId, idx, -1, type) + self.incrementCoordonate(tableId, idx + 1, -1, type) + self.getTable(tableId) + self.tablesTexts[tableId] = self.getTableText(tableId) + + def removeRowsColumns(self, type): + for id in self.svg.selected.ids: + el = self.svg.getElementById(id) + if (el != None): + if (self.isTableCell(id)): + tableId = el.attrib[inkex.addNS('table-id', 'inkex')] + if (type == self.cell_type_column): + idx = self.getColumnIndex(id) + else: + idx = self.getRowIndex(id) + + self.removeCell(tableId, idx, type) diff --git a/extensions/fablabchemnitz/table_support/table_add_columns.inx b/extensions/fablabchemnitz/table_support/table_add_columns.inx new file mode 100644 index 0000000..e442c07 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_add_columns.inx @@ -0,0 +1,23 @@ + + + Columns + fablabchemnitz.de.table_add_columns + 1 + + + + + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_add_columns.py b/extensions/fablabchemnitz/table_support/table_add_columns.py new file mode 100755 index 0000000..06a458f --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_add_columns.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self, True, True) + self.arg_parser.add_argument('--number', type = int, default = 1, help = 'The number of columns') + self.arg_parser.add_argument('--where', default = "after", help = 'Where to add') + + def doinkex(self): + self.addColumns(self.options.number, self.options.where) + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_add_guides.inx b/extensions/fablabchemnitz/table_support/table_add_guides.inx new file mode 100644 index 0000000..0cf1c39 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_add_guides.inx @@ -0,0 +1,18 @@ + + + Guide Lines + fablabchemnitz.de.table_add_guides + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_add_guides.py b/extensions/fablabchemnitz/table_support/table_add_guides.py new file mode 100755 index 0000000..8cdda97 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_add_guides.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" + +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self) + + def doinkex(self): + self.addGuides(self.cell_type_row) + self.addGuides(self.cell_type_column) + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_add_rows.inx b/extensions/fablabchemnitz/table_support/table_add_rows.inx new file mode 100644 index 0000000..2684455 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_add_rows.inx @@ -0,0 +1,23 @@ + + + Rows + fablabchemnitz.de.table_add_rows + 1 + + + + + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_add_rows.py b/extensions/fablabchemnitz/table_support/table_add_rows.py new file mode 100755 index 0000000..b212045 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_add_rows.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self, True, True) + self.arg_parser.add_argument('--number', type = int, default = 1, help = 'The number of rows') + self.arg_parser.add_argument('--where', default = "after", help = 'Where to add') + + def doinkex(self): + self.addRows(self.options.number, self.options.where) + +if __name__ == '__main__': #pragma: no cover + Table().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_create.inx b/extensions/fablabchemnitz/table_support/table_create.inx new file mode 100644 index 0000000..0420a5c --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_create.inx @@ -0,0 +1,28 @@ + + + Create + fablabchemnitz.de.table_create + + + 2 + 2 + 100mm + 20mm + + + + + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_create.py b/extensions/fablabchemnitz/table_support/table_create.py new file mode 100755 index 0000000..40d4f8c --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_create.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" + +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + + def add_arguments(self, pars): + pars.add_argument("--rows", type=int, default=2, help='The number of table rows') + pars.add_argument("--cols", type=int, default=2, help='The number of table columns') + pars.add_argument("--width", default='100mm', help='The width of the table') + pars.add_argument("--height", default='20mm', help='The height of the table') + pars.add_argument("--tab") + + def doinkex(self): + self.create(self.options.width, self.options.height, self.options.cols, self.options.rows) + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_edit_columns.inx b/extensions/fablabchemnitz/table_support/table_edit_columns.inx new file mode 100644 index 0000000..6624b60 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_edit_columns.inx @@ -0,0 +1,20 @@ + + + Selected columns width + fablabchemnitz.de.table_edit_columns + 10mm + + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_edit_columns.py b/extensions/fablabchemnitz/table_support/table_edit_columns.py new file mode 100755 index 0000000..a36b821 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_edit_columns.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" + +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + + def add_arguments(self, pars): + pars.add_argument("--width", default='10mm', help='The new width') + + def doinkex(self): + self.editSize(self.options.width, self.cell_type_column) + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_edit_rows.inx b/extensions/fablabchemnitz/table_support/table_edit_rows.inx new file mode 100644 index 0000000..58c179f --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_edit_rows.inx @@ -0,0 +1,20 @@ + + + Selected rows height + fablabchemnitz.de.table_edit_rows + 10mm + + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_edit_rows.py b/extensions/fablabchemnitz/table_support/table_edit_rows.py new file mode 100755 index 0000000..e71f847 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_edit_rows.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" + +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + + def add_arguments(self, pars): + pars.add_argument("--height", default='10mm', help='The new height') + + def doinkex(self): + self.editSize(self.options.height, self.cell_type_row) + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_edit_table.inx b/extensions/fablabchemnitz/table_support/table_edit_table.inx new file mode 100644 index 0000000..4c9ac43 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_edit_table.inx @@ -0,0 +1,21 @@ + + + Selected table width and height + fablabchemnitz.de.table_edit_table + 150mm + 60mm + + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_edit_table.py b/extensions/fablabchemnitz/table_support/table_edit_table.py new file mode 100755 index 0000000..01ccedf --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_edit_table.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" + +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + + def add_arguments(self, pars): + pars.add_argument("--width", default='150mm', help='The new width') + pars.add_argument("--height", default='60mm', help='The new height') + + def doinkex(self): + self.editTable(self.options.width, self.options.height) + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_edit_text.inx b/extensions/fablabchemnitz/table_support/table_edit_text.inx new file mode 100644 index 0000000..2b8413e --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_edit_text.inx @@ -0,0 +1,19 @@ + + + Selected cells text + fablabchemnitz.de.table_edit_text + 12px + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_edit_text.py b/extensions/fablabchemnitz/table_support/table_edit_text.py new file mode 100755 index 0000000..b21b10f --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_edit_text.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" + +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + + def add_arguments(self, pars): + pars.add_argument("--size", default='12px', help='The font size') + + def doinkex(self): + self.editText(self.options.size) + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_fit_height.inx b/extensions/fablabchemnitz/table_support/table_fit_height.inx new file mode 100644 index 0000000..b830aa1 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_fit_height.inx @@ -0,0 +1,18 @@ + + + Selected table to page height + fablabchemnitz.de.table_fit_height + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_fit_height.py b/extensions/fablabchemnitz/table_support/table_fit_height.py new file mode 100644 index 0000000..7b6d6cb --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_fit_height.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self) + + def doinkex(self): + self.fitPageHeight() + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_fit_page.inx b/extensions/fablabchemnitz/table_support/table_fit_page.inx new file mode 100644 index 0000000..f371f7b --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_fit_page.inx @@ -0,0 +1,18 @@ + + + Selected table to page + fablabchemnitz.de.table_fit_page + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_fit_page.py b/extensions/fablabchemnitz/table_support/table_fit_page.py new file mode 100644 index 0000000..2689e46 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_fit_page.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" + +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + + def doinkex(self): + self.fitPage() + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_fit_width.inx b/extensions/fablabchemnitz/table_support/table_fit_width.inx new file mode 100644 index 0000000..779f481 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_fit_width.inx @@ -0,0 +1,18 @@ + + + Selected table to page width + fablabchemnitz.de.table_fit_width + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_fit_width.py b/extensions/fablabchemnitz/table_support/table_fit_width.py new file mode 100644 index 0000000..1ac7626 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_fit_width.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self) + + def doinkex(self): + self.fitPageWidth() + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_merge_merge.inx b/extensions/fablabchemnitz/table_support/table_merge_merge.inx new file mode 100644 index 0000000..ebd9339 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_merge_merge.inx @@ -0,0 +1,18 @@ + + + Merge selected cells + fablabchemnitz.de.table_merge_merge + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_merge_merge.py b/extensions/fablabchemnitz/table_support/table_merge_merge.py new file mode 100644 index 0000000..89d0a02 --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_merge_merge.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self) + + def doinkex(self): + self.mergeMerge() + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_merge_split.inx b/extensions/fablabchemnitz/table_support/table_merge_split.inx new file mode 100644 index 0000000..1c7134c --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_merge_split.inx @@ -0,0 +1,18 @@ + + + Split selected cells + fablabchemnitz.de.table_merge_split + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_merge_split.py b/extensions/fablabchemnitz/table_support/table_merge_split.py new file mode 100755 index 0000000..c62c8cb --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_merge_split.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self, True, False) + + def doinkex(self): + self.mergeSplit() + +if __name__ == '__main__': #pragma: no cover + Table().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_remove_columns.inx b/extensions/fablabchemnitz/table_support/table_remove_columns.inx new file mode 100644 index 0000000..eb840bf --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_remove_columns.inx @@ -0,0 +1,18 @@ + + + Selected columns + fablabchemnitz.de.table_remove_columns + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_remove_columns.py b/extensions/fablabchemnitz/table_support/table_remove_columns.py new file mode 100755 index 0000000..053f03e --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_remove_columns.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self, True, True) + + def doinkex(self): + self.removeRowsColumns(self.cell_type_column) + +if __name__ == '__main__': #pragma: no cover + Table().run() diff --git a/extensions/fablabchemnitz/table_support/table_remove_rows.inx b/extensions/fablabchemnitz/table_support/table_remove_rows.inx new file mode 100644 index 0000000..01f5dbc --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_remove_rows.inx @@ -0,0 +1,18 @@ + + + Selected rows + fablabchemnitz.de.table_remove_rows + + all + + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/table_support/table_remove_rows.py b/extensions/fablabchemnitz/table_support/table_remove_rows.py new file mode 100755 index 0000000..11a3b3f --- /dev/null +++ b/extensions/fablabchemnitz/table_support/table_remove_rows.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +table.py +Table support for Inkscape + +Copyright (C) 2011 Cosmin Popescu, cosminadrianpopescu@gmail.com + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +""" +import sys +import os +import table + +sys.path.append(os.path.dirname(sys.argv[0])) + +class Table(table.TableEngine): + def __init__(self): + table.TableEngine.__init__(self, True, True) + + def doinkex(self): + self.removeRowsColumns(self.cell_type_row) + +if __name__ == '__main__': #pragma: no cover + Table().run()