cleaned and fixed chain_paths
This commit is contained in:
parent
ea71e26473
commit
008230e9f7
@ -28,31 +28,13 @@ __version__ = '0.7' # Keep in sync with chain_paths.inx ca line 22
|
|||||||
__author__ = 'Juergen Weigert <juergen@fabmail.org>'
|
__author__ = 'Juergen Weigert <juergen@fabmail.org>'
|
||||||
__credits__ = ['Juergen Weigert', 'Veronika Irvine']
|
__credits__ = ['Juergen Weigert', 'Veronika Irvine']
|
||||||
|
|
||||||
import sys, os, shutil, time, logging, tempfile, math
|
import sys
|
||||||
|
import math
|
||||||
import re
|
import re
|
||||||
|
|
||||||
#debug = True
|
|
||||||
debug = False
|
|
||||||
|
|
||||||
# search path, so that inkscape libraries are found when we are standalone.
|
|
||||||
sys_platform = sys.platform.lower()
|
|
||||||
if sys_platform.startswith('win'): # windows
|
|
||||||
sys.path.append('C:\Program Files\Inkscape\share\extensions')
|
|
||||||
elif sys_platform.startswith('darwin'): # mac
|
|
||||||
sys.path.append('/Applications/Inkscape.app/Contents/Resources/extensions')
|
|
||||||
else: # linux
|
|
||||||
# if sys_platform.startswith('linux'):
|
|
||||||
sys.path.append('/usr/share/inkscape/extensions')
|
|
||||||
|
|
||||||
# inkscape libraries
|
|
||||||
import inkex
|
import inkex
|
||||||
|
|
||||||
inkex.localization.localize()
|
inkex.localization.localize()
|
||||||
|
|
||||||
from optparse import SUPPRESS_HELP
|
from optparse import SUPPRESS_HELP
|
||||||
|
debug = False
|
||||||
def uutounit(self, nn, uu):
|
|
||||||
return self.svg.unittouu(str(nn)+uu)
|
|
||||||
|
|
||||||
class ChainPaths(inkex.Effect):
|
class ChainPaths(inkex.Effect):
|
||||||
|
|
||||||
@ -86,7 +68,7 @@ class ChainPaths(inkex.Effect):
|
|||||||
- The document units are always irrelevant as
|
- The document units are always irrelevant as
|
||||||
everything in inkscape is expected to be in 90dpi pixel units
|
everything in inkscape is expected to be in 90dpi pixel units
|
||||||
"""
|
"""
|
||||||
dialog_units = uutounit(self, 1.0, units)
|
dialog_units = self.svg.unittouu(str(1.0)+units)
|
||||||
self.unit_factor = 1.0 / dialog_units
|
self.unit_factor = 1.0 / dialog_units
|
||||||
return self.unit_factor
|
return self.unit_factor
|
||||||
|
|
||||||
@ -237,7 +219,7 @@ class ChainPaths(inkex.Effect):
|
|||||||
|
|
||||||
if self.near_ends(end1, seg['end2']):
|
if self.near_ends(end1, seg['end2']):
|
||||||
# prepend seg to chain
|
# prepend seg to chain
|
||||||
self.set_segment_done(seg['id'], seg['n'], 'prepended to ' + id + ' ' + str(cur_idx))
|
self.set_segment_done(seg['id'], seg['n'], 'prepended to ' + str(id) + ' ' + str(cur_idx))
|
||||||
chain = self.link_segments(seg['seg'], chain)
|
chain = self.link_segments(seg['seg'], chain)
|
||||||
end1 = [chain[0][1][0], chain[0][1][1]]
|
end1 = [chain[0][1][0], chain[0][1][1]]
|
||||||
segments_idx = 0 # this chain changed. re-visit all candidate
|
segments_idx = 0 # this chain changed. re-visit all candidate
|
||||||
@ -245,7 +227,7 @@ class ChainPaths(inkex.Effect):
|
|||||||
|
|
||||||
if self.near_ends(end2, seg['end1']):
|
if self.near_ends(end2, seg['end1']):
|
||||||
# append seg to chain
|
# append seg to chain
|
||||||
self.set_segment_done(seg['id'], seg['n'], 'appended to ' + id + ' ' + str(cur_idx))
|
self.set_segment_done(seg['id'], seg['n'], 'appended to ' + str(id) + ' ' + str(cur_idx))
|
||||||
chain = self.link_segments(chain, seg['seg'])
|
chain = self.link_segments(chain, seg['seg'])
|
||||||
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
||||||
segments_idx = 0 # this chain changed. re-visit all candidate
|
segments_idx = 0 # this chain changed. re-visit all candidate
|
||||||
|
45
extensions/fablabchemnitz_pixel2svg.inx
Normal file
45
extensions/fablabchemnitz_pixel2svg.inx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Pixel2SVG</name>
|
||||||
|
<id>fablabchemnitz.de.pixel2svg</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="pixel2svg_tab" _gui-text="Options">
|
||||||
|
<param name="squaresize" type="int" min="1" max="100" _gui-text="Width and height of vector squares in pixels">5</param>
|
||||||
|
<param name="offset_image" type="boolean" _gui-text="Offset traced image">true</param>
|
||||||
|
<param name="delete_image" type="boolean" _gui-text="Delete bitmap image">false</param>
|
||||||
|
</page>
|
||||||
|
<page name="advanced_tab" _gui-text="Advanced">
|
||||||
|
<param name="transparency" type="boolean" _gui-text="Convert transparency to 'fill-opacity'">true</param>
|
||||||
|
<param name="overlap" type="boolean" _gui-text="Overlap vector squares by 1px">false</param>
|
||||||
|
<param name="verbose" type="boolean" _gui-text="Report image info (size, format, mode)">false</param>
|
||||||
|
<param name="maxsize" type="int" min="1" max="10000" _gui-text="Max. image size (width or height)">256</param>
|
||||||
|
</page>
|
||||||
|
<page name="advanced_color_tab" _gui-text="Colors">
|
||||||
|
<param name="color_mode" type="enum" _gui-text="">
|
||||||
|
<item value="all">Trace all colors.</item>
|
||||||
|
<item value="other">Don't trace this color:</item>
|
||||||
|
<item value="this">Only trace this color:</item>
|
||||||
|
</param>
|
||||||
|
<param name="color" type="string" max_length="6" _gui-text="Color (hex):">FFFFFF</param>
|
||||||
|
</page>
|
||||||
|
<page name="about_tab" _gui-text="About">
|
||||||
|
<_param name="instructions" type="description" xml:space="preserve">This extension is based on:
|
||||||
|
pixel2svg - converts pixel art to SVG - pixel by pixel.
|
||||||
|
Copyright 2011 Florian Berger
|
||||||
|
http://florian-berger.de/en/software/pixel2svg
|
||||||
|
</_param>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect needs-document="true" needs-live-preview="true">
|
||||||
|
<object-type>image</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu _name="FabLab Chemnitz">
|
||||||
|
<submenu _name="Tracing/Edge Detection" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command reldir="extensions" interpreter="python">fablabchemnitz_pixel2svg.py</command>
|
||||||
|
</script>
|
||||||
|
<options silent="false" />
|
||||||
|
</inkscape-extension>
|
314
extensions/fablabchemnitz_pixel2svg.py
Normal file
314
extensions/fablabchemnitz_pixel2svg.py
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Pixel2SVG - Convert the pixels of bitmap images to SVG rects
|
||||||
|
|
||||||
|
Idea and original implementation as standalone script:
|
||||||
|
Copyright (C) 2011 Florian Berger <fberger@florian-berger.de>
|
||||||
|
Homepage: <http://florian-berger.de/en/software/pixel2svg>
|
||||||
|
|
||||||
|
Rewritten as Inkscape extension:
|
||||||
|
Copyright (C) 2012 ~suv <suv-sf@users.sourceforge.net>
|
||||||
|
|
||||||
|
'getFilePath()' is based on code from 'extractimages.py':
|
||||||
|
Copyright (C) 2005 Aaron Spike, aaron@ekips.org
|
||||||
|
|
||||||
|
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 base64
|
||||||
|
from io import StringIO, BytesIO
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
import inkex
|
||||||
|
from PIL import Image
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
inkex.localization.localize
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
|
||||||
|
# int r = ( hexcolor >> 16 ) & 0xFF;
|
||||||
|
# int g = ( hexcolor >> 8 ) & 0xFF;
|
||||||
|
# int b = hexcolor & 0xFF;
|
||||||
|
# int hexcolor = (r << 16) + (g << 8) + b;
|
||||||
|
|
||||||
|
def hex_to_int_color(v):
|
||||||
|
if (v[0] == '#'):
|
||||||
|
v = v[1:]
|
||||||
|
assert(len(v) == 6)
|
||||||
|
return int(v[:2], 16), int(v[2:4], 16), int(v[4:6], 16)
|
||||||
|
|
||||||
|
|
||||||
|
class Pixel2SVG(inkex.Effect):
|
||||||
|
def __init__(self):
|
||||||
|
inkex.Effect.__init__(self)
|
||||||
|
# pixel2svg options
|
||||||
|
self.arg_parser.add_argument("-s", "--squaresize", type=int, default="5", help="Width and height of vector squares in pixels")
|
||||||
|
self.arg_parser.add_argument("--transparency", type=inkex.Boolean, default=True, help="Convert transparency to 'fill-opacity'")
|
||||||
|
self.arg_parser.add_argument("--overlap", type=inkex.Boolean, default=False, help="Overlap vector squares by 1px")
|
||||||
|
self.arg_parser.add_argument("--offset_image", type=inkex.Boolean, default=True, help="Offset traced image")
|
||||||
|
self.arg_parser.add_argument("--delete_image", type=inkex.Boolean, default=False, help="Delete bitmap image")
|
||||||
|
self.arg_parser.add_argument("--maxsize", type=int, default="256", help="Max. image size (width or height)")
|
||||||
|
self.arg_parser.add_argument("--verbose", type=inkex.Boolean, default=False)
|
||||||
|
self.arg_parser.add_argument("--color_mode", default="all", help="Which colors to trace.")
|
||||||
|
self.arg_parser.add_argument("--color", default="FFFFFF", help="Special color")
|
||||||
|
self.arg_parser.add_argument("--tab")
|
||||||
|
|
||||||
|
def getImagePath(self, node, xlink):
|
||||||
|
"""
|
||||||
|
Find image file, return path
|
||||||
|
"""
|
||||||
|
absref = node.get(inkex.addNS('absref', 'sodipodi'))
|
||||||
|
url = urlparse(xlink)
|
||||||
|
href = urllib.request.url2pathname
|
||||||
|
|
||||||
|
path = ''
|
||||||
|
#path selection strategy:
|
||||||
|
# 1. href if absolute
|
||||||
|
# 2. realpath-ified href
|
||||||
|
# 3. absref, only if the above does not point to a file
|
||||||
|
if href is not None:
|
||||||
|
path = os.path.realpath(href)
|
||||||
|
if (not os.path.isfile(path)):
|
||||||
|
if absref is not None:
|
||||||
|
path = absref
|
||||||
|
|
||||||
|
try:
|
||||||
|
path = unicode(path, "utf-8")
|
||||||
|
except TypeError:
|
||||||
|
path = path
|
||||||
|
|
||||||
|
if (not os.path.isfile(path)):
|
||||||
|
inkex.errormsg(_(
|
||||||
|
"No xlink:href or sodipodi:absref attributes found, " +
|
||||||
|
"or they do not point to an existing file! Unable to find image file."))
|
||||||
|
if path:
|
||||||
|
inkex.errormsg(_("Sorry we could not locate %s") % str(path))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (os.path.isfile(path)):
|
||||||
|
return path
|
||||||
|
|
||||||
|
def getImageData(self, xlink):
|
||||||
|
"""
|
||||||
|
Read, decode and return data of embedded image
|
||||||
|
"""
|
||||||
|
comma = xlink.find(',')
|
||||||
|
data = ''
|
||||||
|
|
||||||
|
if comma > 0:
|
||||||
|
data = base64.decodebytes(xlink[comma:].encode('UTF-8'))
|
||||||
|
else:
|
||||||
|
inkex.errormsg(_("Failed to read embedded image data."))
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def getImage(self, node):
|
||||||
|
image_element=self.svg.find('.//{http://www.w3.org/2000/svg}image')
|
||||||
|
image_string=image_element.get('{http://www.w3.org/1999/xlink}href')
|
||||||
|
#find comma position
|
||||||
|
i=0
|
||||||
|
while i<40:
|
||||||
|
if image_string[i]==',':
|
||||||
|
break
|
||||||
|
i=i+1
|
||||||
|
return Image.open(BytesIO(base64.b64decode(image_string[i+1:len(image_string)])))
|
||||||
|
|
||||||
|
def drawFilledRect(self, parent, svgpx):
|
||||||
|
"""
|
||||||
|
Draw rect based on ((x, y), (width,height), ((r,g,b),a)), add to parent
|
||||||
|
"""
|
||||||
|
style = {}
|
||||||
|
pos = svgpx[0]
|
||||||
|
dim = svgpx[1]
|
||||||
|
rgb = svgpx[2][0]
|
||||||
|
alpha = svgpx[2][1]
|
||||||
|
|
||||||
|
style['stroke'] = 'none'
|
||||||
|
|
||||||
|
if len(rgb) == 3:
|
||||||
|
# fill: rgb tuple
|
||||||
|
style['fill'] = '#%02x%02x%02x' % (rgb[0], rgb[1], rgb[2])
|
||||||
|
elif len(rgb) == 1:
|
||||||
|
# fill: color name, or 'none'
|
||||||
|
style['fill'] = rgb[0]
|
||||||
|
else:
|
||||||
|
# fill: 'Unset' (no fill defined)
|
||||||
|
pass
|
||||||
|
|
||||||
|
if alpha < 255:
|
||||||
|
# only write 'fill-opacity' for non-default value
|
||||||
|
style['fill-opacity'] = '%s' % round(alpha/255.0, 8)
|
||||||
|
|
||||||
|
rect_attribs = {'x': str(pos[0]),
|
||||||
|
'y': str(pos[1]),
|
||||||
|
'width': str(dim[0]),
|
||||||
|
'height': str(dim[1]),
|
||||||
|
'style': str(inkex.Style(style)), }
|
||||||
|
|
||||||
|
rect = etree.SubElement(parent, inkex.addNS('rect', 'svg'), rect_attribs)
|
||||||
|
|
||||||
|
return rect
|
||||||
|
|
||||||
|
def vectorizeImage(self, node):
|
||||||
|
"""
|
||||||
|
Parse RGBA values of linked bitmap image, create a group and
|
||||||
|
draw the rectangles (SVG pixels) inside the new group
|
||||||
|
"""
|
||||||
|
image = self.getImage(node)
|
||||||
|
|
||||||
|
if image:
|
||||||
|
# init, set limit (default: 256)
|
||||||
|
pixel2svg_max = self.options.maxsize
|
||||||
|
|
||||||
|
if self.options.verbose:
|
||||||
|
inkex.debug("ID: %s" % node.get('id'))
|
||||||
|
inkex.debug("Image size:\t%dx%d" % image.size)
|
||||||
|
inkex.debug("Image format:\t%s" % image.format)
|
||||||
|
inkex.debug("Image mode:\t%s" % image.mode)
|
||||||
|
inkex.debug("Image info:\t%s" % image.info)
|
||||||
|
|
||||||
|
if (image.mode == 'P' and 'transparency' in image.info):
|
||||||
|
inkex.debug(
|
||||||
|
"Note: paletted image with an alpha channel is handled badly with " +
|
||||||
|
"current PIL:\n" +
|
||||||
|
"<http://stackoverflow.com/questions/12462548/pil-image-mode-p-rgba>")
|
||||||
|
elif not image.mode in ('RGBA', 'LA'):
|
||||||
|
inkex.debug("No alpha channel or transparency found")
|
||||||
|
|
||||||
|
image = image.convert("RGBA")
|
||||||
|
(width, height) = image.size
|
||||||
|
|
||||||
|
if width <= pixel2svg_max and height <= pixel2svg_max:
|
||||||
|
|
||||||
|
# color trace modes
|
||||||
|
trace_color = []
|
||||||
|
if self.options.color:
|
||||||
|
trace_color = hex_to_int_color(self.options.color)
|
||||||
|
|
||||||
|
# get RGBA data
|
||||||
|
rgba_values = list(image.getdata())
|
||||||
|
|
||||||
|
# create group
|
||||||
|
nodeParent = node.getparent()
|
||||||
|
nodeIndex = nodeParent.index(node)
|
||||||
|
pixel2svg_group = etree.Element(inkex.addNS('g', 'svg'))
|
||||||
|
pixel2svg_group.set('id', "%s_pixel2svg" % node.get('id'))
|
||||||
|
nodeParent.insert(nodeIndex+1, pixel2svg_group)
|
||||||
|
|
||||||
|
# move group beside original image
|
||||||
|
if self.options.offset_image:
|
||||||
|
pixel2svg_offset = width
|
||||||
|
else:
|
||||||
|
pixel2svg_offset = 0.0
|
||||||
|
pixel2svg_translate = ('translate(%s, %s)' %
|
||||||
|
(float(node.get('x') or 0.0) + pixel2svg_offset,
|
||||||
|
node.get('y') or 0.0))
|
||||||
|
pixel2svg_group.set('transform', pixel2svg_translate)
|
||||||
|
|
||||||
|
# draw bbox rectangle at the bottom of group
|
||||||
|
pixel2svg_bbox_fill = ('none', )
|
||||||
|
pixel2svg_bbox_alpha = 255
|
||||||
|
pixel2svg_bbox = ((0, 0),
|
||||||
|
(width * self.options.squaresize,
|
||||||
|
height * self.options.squaresize),
|
||||||
|
(pixel2svg_bbox_fill, pixel2svg_bbox_alpha))
|
||||||
|
self.drawFilledRect(pixel2svg_group, pixel2svg_bbox)
|
||||||
|
|
||||||
|
# reverse list (performance), pop last one instead of first
|
||||||
|
rgba_values.reverse()
|
||||||
|
# loop through pixels (per row)
|
||||||
|
rowcount = 0
|
||||||
|
while rowcount < height:
|
||||||
|
colcount = 0
|
||||||
|
while colcount < width:
|
||||||
|
rgba_tuple = rgba_values.pop()
|
||||||
|
# Omit transparent pixels
|
||||||
|
if rgba_tuple[3] > 0:
|
||||||
|
# color options
|
||||||
|
do_trace = True
|
||||||
|
if (self.options.color_mode != "all"):
|
||||||
|
if (trace_color == rgba_tuple[:3]):
|
||||||
|
# colors match
|
||||||
|
if (self.options.color_mode == "other"):
|
||||||
|
do_trace = False
|
||||||
|
else:
|
||||||
|
# colors don't match
|
||||||
|
if (self.options.color_mode == "this"):
|
||||||
|
do_trace = False
|
||||||
|
if do_trace:
|
||||||
|
# position
|
||||||
|
svgpx_x = colcount * self.options.squaresize
|
||||||
|
svgpx_y = rowcount * self.options.squaresize
|
||||||
|
# dimension + overlap
|
||||||
|
svgpx_size = self.options.squaresize + self.options.overlap
|
||||||
|
# get color, ignore alpha
|
||||||
|
svgpx_rgb = rgba_tuple[:3]
|
||||||
|
svgpx_a = 255
|
||||||
|
# transparency
|
||||||
|
if self.options.transparency:
|
||||||
|
svgpx_a = rgba_tuple[3]
|
||||||
|
svgpx = ((svgpx_x, svgpx_y),
|
||||||
|
(svgpx_size, svgpx_size),
|
||||||
|
(svgpx_rgb, svgpx_a)
|
||||||
|
)
|
||||||
|
# draw square in group
|
||||||
|
self.drawFilledRect(pixel2svg_group, svgpx)
|
||||||
|
colcount = colcount + 1
|
||||||
|
rowcount = rowcount + 1
|
||||||
|
|
||||||
|
# all done
|
||||||
|
if DEBUG:
|
||||||
|
inkex.debug("All rects drawn.")
|
||||||
|
|
||||||
|
if self.options.delete_image:
|
||||||
|
nodeParent.remove(node)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# bail out with larger images
|
||||||
|
inkex.errormsg(_(
|
||||||
|
"Bailing out: this extension is not intended for large images.\n" +
|
||||||
|
"The current limit is %spx for either dimension of the bitmap image."
|
||||||
|
% pixel2svg_max))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# clean-up?
|
||||||
|
if DEBUG:
|
||||||
|
inkex.debug("Done.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
inkex.errormsg(_("Bailing out: No supported image file or data found"))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
"""
|
||||||
|
Pixel2SVG - Convert the pixels of bitmap images to SVG rects
|
||||||
|
"""
|
||||||
|
found_image = False
|
||||||
|
if (self.options.ids):
|
||||||
|
for node in self.svg.selected.values():
|
||||||
|
if node.tag == inkex.addNS('image', 'svg'):
|
||||||
|
found_image = True
|
||||||
|
self.vectorizeImage(node)
|
||||||
|
|
||||||
|
if not found_image:
|
||||||
|
inkex.errormsg(_("Please select one or more bitmap image(s) for Pixel2SVG"))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Pixel2SVG().run()
|
1240
extensions/fablabchemnitz_reorder_sequence.py
Normal file
1240
extensions/fablabchemnitz_reorder_sequence.py
Normal file
File diff suppressed because it is too large
Load Diff
495
extensions/fablabchemnitz_tool_covers.py
Normal file
495
extensions/fablabchemnitz_tool_covers.py
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# (c) 2020 Yoichi Tanibayashi
|
||||||
|
#
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
import math
|
||||||
|
inkex.localization.localize
|
||||||
|
|
||||||
|
|
||||||
|
class Point(object):
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def distance(self, c):
|
||||||
|
return math.sqrt((c.x - self.x) ** 2 + (c.y - self.y) ** 2)
|
||||||
|
|
||||||
|
def rotate(self, rad):
|
||||||
|
new_x = math.cos(rad) * self.x - math.sin(rad) * self.y
|
||||||
|
new_y = math.sin(rad) * self.x + math.cos(rad) * self.y
|
||||||
|
self.x = new_x
|
||||||
|
self.y = new_y
|
||||||
|
return self
|
||||||
|
|
||||||
|
def mirror(self):
|
||||||
|
self.x = -self.x
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class Vpoint(Point):
|
||||||
|
'''
|
||||||
|
(x, y)座標と方向(rad)を持つ点
|
||||||
|
|
||||||
|
rad: 方向(真上: 0, 右: math.pi / 2, ..)
|
||||||
|
'''
|
||||||
|
def __init__(self, x, y, rad=0):
|
||||||
|
super(Vpoint, self).__init__(x, y)
|
||||||
|
self.rad = rad
|
||||||
|
|
||||||
|
def rotate(self, rad):
|
||||||
|
super(Vpoint, self).rotate(rad)
|
||||||
|
self.rad += rad
|
||||||
|
return self
|
||||||
|
|
||||||
|
def mirror(self):
|
||||||
|
super(Vpoint, self).mirror()
|
||||||
|
self.rad = -self.rad
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class SvgObj(object):
|
||||||
|
DEF_COLOR = '#00FF00'
|
||||||
|
DEF_STROKE_WIDTH = 0.2
|
||||||
|
DEF_STROKE_DASHARRAY = 'none'
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self.type = None
|
||||||
|
self.attr = {}
|
||||||
|
|
||||||
|
def draw(self, color=DEF_COLOR,
|
||||||
|
stroke_width=DEF_STROKE_WIDTH,
|
||||||
|
stroke_dasharray=DEF_STROKE_DASHARRAY):
|
||||||
|
|
||||||
|
self.attr['style'] = str(inkex.Style({
|
||||||
|
'stroke': str(color),
|
||||||
|
'stroke-width': str(stroke_width),
|
||||||
|
'stroke-dasharray': str(stroke_dasharray),
|
||||||
|
'fill': 'none'}))
|
||||||
|
return etree.SubElement(self.parent,
|
||||||
|
inkex.addNS(self.type, 'svg'),
|
||||||
|
self.attr)
|
||||||
|
|
||||||
|
|
||||||
|
class SvgCircle(SvgObj):
|
||||||
|
DEF_COLOR = '#FF0000'
|
||||||
|
DEF_STROKE_WIDTH = 0.2
|
||||||
|
DEF_STROKE_DASHARRAY = 'none'
|
||||||
|
|
||||||
|
def __init__(self, parent, r):
|
||||||
|
super(SvgCircle, self).__init__(parent)
|
||||||
|
self.r = r
|
||||||
|
self.type = 'circle'
|
||||||
|
|
||||||
|
def draw(self, point,
|
||||||
|
color=DEF_COLOR,
|
||||||
|
stroke_width=DEF_STROKE_WIDTH,
|
||||||
|
stroke_dasharray=DEF_STROKE_DASHARRAY):
|
||||||
|
self.attr['cx'] = str(point.x)
|
||||||
|
self.attr['cy'] = str(point.y)
|
||||||
|
self.attr['r'] = str(self.r)
|
||||||
|
|
||||||
|
return super(SvgCircle, self).draw(color,
|
||||||
|
stroke_width, stroke_dasharray)
|
||||||
|
|
||||||
|
|
||||||
|
class SvgPath(SvgObj):
|
||||||
|
DEF_COLOR = '#0000FF'
|
||||||
|
DEF_STROKE_WIDTH = 0.2
|
||||||
|
DEF_STROKE_DASHARRAY = 'none'
|
||||||
|
|
||||||
|
def __init__(self, parent, points):
|
||||||
|
super(SvgPath, self).__init__(parent)
|
||||||
|
self.points = points
|
||||||
|
self.type = 'path'
|
||||||
|
|
||||||
|
def create_svg_d(self, origin_vpoint, points):
|
||||||
|
'''
|
||||||
|
to be override
|
||||||
|
|
||||||
|
This is sample code.
|
||||||
|
'''
|
||||||
|
svg_d = ''
|
||||||
|
for i, p in enumerate(points):
|
||||||
|
(x1, y1) = (p.x + origin_vpoint.x, p.y + origin_vpoint.y)
|
||||||
|
if i == 0:
|
||||||
|
svg_d = 'M %f,%f' % (x1, y1)
|
||||||
|
else:
|
||||||
|
svg_d += ' L %f,%f' % (x1, y1)
|
||||||
|
return svg_d
|
||||||
|
|
||||||
|
def rotate(self, rad):
|
||||||
|
for p in self.points:
|
||||||
|
p.rotate(rad)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def mirror(self):
|
||||||
|
for p in self.points:
|
||||||
|
p.mirror()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def draw(self, origin,
|
||||||
|
color=DEF_COLOR, stroke_width=DEF_STROKE_WIDTH,
|
||||||
|
stroke_dasharray=DEF_STROKE_DASHARRAY):
|
||||||
|
|
||||||
|
self.rotate(origin.rad)
|
||||||
|
|
||||||
|
svg_d = self.create_svg_d(origin, self.points)
|
||||||
|
# inkex.errormsg('svg_d=%s' % svg_d)
|
||||||
|
# inkex.errormsg('svg_d=%s' % str(Path( svg_d )))
|
||||||
|
|
||||||
|
self.attr['d'] = svg_d
|
||||||
|
return super(SvgPath, self).draw(color, stroke_width, stroke_dasharray)
|
||||||
|
|
||||||
|
|
||||||
|
class SvgLine(SvgPath):
|
||||||
|
# exactly same as SvgPath
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SvgPolygon(SvgPath):
|
||||||
|
def create_svg_d(self, origin, points):
|
||||||
|
svg_d = super(SvgPolygon, self).create_svg_d(origin, points)
|
||||||
|
svg_d += ' Z'
|
||||||
|
return svg_d
|
||||||
|
|
||||||
|
|
||||||
|
class SvgPart1Outline(SvgPolygon):
|
||||||
|
def __init__(self, parent, points, bw_bf):
|
||||||
|
super(SvgPart1Outline, self).__init__(parent, points)
|
||||||
|
self.bw_bf = bw_bf
|
||||||
|
|
||||||
|
def create_svg_d(self, origin, points, bw_bf=1):
|
||||||
|
for i, p in enumerate(points):
|
||||||
|
(x1, y1) = (p.x + origin.x, p.y + origin.y)
|
||||||
|
if i == 0:
|
||||||
|
d = 'M %f,%f' % (x1, y1)
|
||||||
|
elif i == 7:
|
||||||
|
d += ' L %f,%f' % (x1, y1)
|
||||||
|
x2 = x1
|
||||||
|
y2 = y1 + self.bw_bf
|
||||||
|
elif i == 8:
|
||||||
|
d += ' C %f,%f %f,%f %f,%f' % (x2, y2, x1, y2, x1, y1)
|
||||||
|
else:
|
||||||
|
d += ' L %f,%f' % (x1, y1)
|
||||||
|
|
||||||
|
d += ' Z'
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
class SvgNeedleHole(SvgPolygon):
|
||||||
|
def __init__(self, parent, w, h, tf):
|
||||||
|
'''
|
||||||
|
w: width
|
||||||
|
h: height
|
||||||
|
tf: tilt factor
|
||||||
|
'''
|
||||||
|
self.w = w
|
||||||
|
self.h = h
|
||||||
|
self.tf = tf
|
||||||
|
|
||||||
|
self.gen_points(self.w, self.h, self.tf)
|
||||||
|
super(SvgNeedleHole, self).__init__(parent, self.points)
|
||||||
|
|
||||||
|
def gen_points(self, w, h, tf):
|
||||||
|
self.points = []
|
||||||
|
self.points.append(Point(-w / 2, h * tf))
|
||||||
|
self.points.append(Point( w / 2, h * (1 - tf)))
|
||||||
|
self.points.append(Point( w / 2, -h * tf))
|
||||||
|
self.points.append(Point(-w / 2, -h * (1 - tf)))
|
||||||
|
|
||||||
|
|
||||||
|
class Part1(object):
|
||||||
|
def __init__(self, parent,
|
||||||
|
w1, w2, h1, h2, bw, bl, bf, dia1, d1, d2,
|
||||||
|
needle_w, needle_h, needle_tf, needle_corner_rotation):
|
||||||
|
self.parent = parent
|
||||||
|
self.w1 = w1
|
||||||
|
self.w2 = w2
|
||||||
|
self.h1 = h1
|
||||||
|
self.h2 = h2
|
||||||
|
self.bw = bw
|
||||||
|
self.bl = bl
|
||||||
|
self.bf = bf
|
||||||
|
self.dia1 = dia1
|
||||||
|
self.d1 = d1
|
||||||
|
self.d2 = d2
|
||||||
|
self.needle_w = needle_w
|
||||||
|
self.needle_h = needle_h
|
||||||
|
self.needle_tf = needle_tf
|
||||||
|
self.needle_corner_rotation = needle_corner_rotation
|
||||||
|
|
||||||
|
# グループ作成
|
||||||
|
attr = {inkex.addNS('label', 'inkscape'): 'Part1'}
|
||||||
|
self.g = etree.SubElement(self.parent, 'g', attr)
|
||||||
|
|
||||||
|
# 図形作成
|
||||||
|
self.points_outline = self.create_points_outline()
|
||||||
|
self.svg_outline = SvgPart1Outline(self.g, self.points_outline,
|
||||||
|
(self.bw * self.bf))
|
||||||
|
self.svg_hole = SvgCircle(self.g, self.dia1 / 2)
|
||||||
|
|
||||||
|
self.vpoints_needle = self.create_needle_vpoints()
|
||||||
|
self.svgs_needle_hole = []
|
||||||
|
for v in self.vpoints_needle:
|
||||||
|
svg_nh = SvgNeedleHole(self.g,
|
||||||
|
self.needle_w,
|
||||||
|
self.needle_h,
|
||||||
|
self.needle_tf)
|
||||||
|
self.svgs_needle_hole.append((svg_nh, v))
|
||||||
|
|
||||||
|
def create_points_outline(self):
|
||||||
|
'''
|
||||||
|
外枠の座標を生成
|
||||||
|
'''
|
||||||
|
points = []
|
||||||
|
(x0, y0) = (-(self.w2 / 2), 0)
|
||||||
|
|
||||||
|
(x, y) = (x0, y0 + self.h1 + self.h2)
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
y = y0 + self.h1
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
x = -(self.w1 / 2)
|
||||||
|
y = y0
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
x = self.w1 / 2
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
x = self.w2 / 2
|
||||||
|
y += self.h1
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
y += self.h2
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
x = self.bw / 2
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
y += self.bl - self.bw / 2
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
x = -(self.bw / 2)
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
y = y0 + self.h1 + self.h2
|
||||||
|
points.append(Point(x, y))
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
def create_needle_vpoints(self):
|
||||||
|
'''
|
||||||
|
針穴の点と方向を生成
|
||||||
|
'''
|
||||||
|
rad1 = math.atan((self.w2 - self.w1) / (2 * self.h1))
|
||||||
|
rad1a = (math.pi - rad1) / 2
|
||||||
|
a1 = self.d1 / math.tan(rad1a)
|
||||||
|
|
||||||
|
rad2 = (math.pi / 2) - rad1
|
||||||
|
rad2a = (math.pi - rad2) / 2
|
||||||
|
a2 = self.d1 / math.tan(rad2a)
|
||||||
|
|
||||||
|
#
|
||||||
|
# 頂点
|
||||||
|
#
|
||||||
|
vpoints1 = []
|
||||||
|
for i, p in enumerate(self.points_outline):
|
||||||
|
(nx, ny) = (p.x, p.y)
|
||||||
|
if i == 0:
|
||||||
|
nx += self.d1
|
||||||
|
ny -= self.d1 * 1.5
|
||||||
|
vpoints1.append(Vpoint(nx, ny, 0))
|
||||||
|
if i == 1:
|
||||||
|
nx += self.d1
|
||||||
|
ny += a1
|
||||||
|
vpoints1.append(Vpoint(nx, ny, rad1))
|
||||||
|
if i == 2:
|
||||||
|
nx += a2
|
||||||
|
ny += self.d1
|
||||||
|
vpoints1.append(Vpoint(nx, ny, math.pi / 2))
|
||||||
|
if i == 3:
|
||||||
|
nx -= a2
|
||||||
|
ny += self.d1
|
||||||
|
vpoints1.append(Vpoint(nx, ny, (math.pi / 2) + rad2))
|
||||||
|
if i == 4:
|
||||||
|
nx -= self.d1
|
||||||
|
ny += a1
|
||||||
|
vpoints1.append(Vpoint(nx, ny, math.pi))
|
||||||
|
if i == 5:
|
||||||
|
nx -= self.d1
|
||||||
|
ny -= self.d1 * 1.5
|
||||||
|
vpoints1.append(Vpoint(nx, ny, math.pi))
|
||||||
|
if i > 5:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 頂点を補完する点を生成
|
||||||
|
vpoints2 = []
|
||||||
|
for i in range(len(vpoints1)-1):
|
||||||
|
d = vpoints1[i].distance(vpoints1[i+1])
|
||||||
|
n = int(abs(round(d / self.d2)))
|
||||||
|
for p in self.split_vpoints(vpoints1[i], vpoints1[i+1], n):
|
||||||
|
vpoints2.append(p)
|
||||||
|
|
||||||
|
vpoints2.insert(0, vpoints1[0])
|
||||||
|
return vpoints2
|
||||||
|
|
||||||
|
def split_vpoints(self, v1, v2, n):
|
||||||
|
'''
|
||||||
|
v1, v2間をn個に分割して、リストを生成
|
||||||
|
'''
|
||||||
|
if n == 0:
|
||||||
|
return [v1]
|
||||||
|
(dx, dy) = ((v2.x - v1.x) / n, (v2.y - v1.y) / n)
|
||||||
|
|
||||||
|
v = []
|
||||||
|
for i in range(n):
|
||||||
|
v.append(Vpoint(v1.x + dx * (i + 1),
|
||||||
|
v1.y + dy * (i + 1),
|
||||||
|
v1.rad))
|
||||||
|
if self.needle_corner_rotation:
|
||||||
|
v[-1].rad = (v1.rad + v2.rad) / 2
|
||||||
|
return v
|
||||||
|
|
||||||
|
def draw(self, origin):
|
||||||
|
origin_base = Vpoint(origin.x + self.w2 / 2,
|
||||||
|
origin.y,
|
||||||
|
origin.rad)
|
||||||
|
self.svg_outline.draw(origin_base, color='#0000FF')
|
||||||
|
|
||||||
|
x = origin.x + self.w2 / 2
|
||||||
|
y = origin.y + self.h1 + self.h2 + self.bl - self.bw / 2
|
||||||
|
origin_hole = Point(x, y)
|
||||||
|
self.svg_hole.draw(origin_hole, color='#FF0000')
|
||||||
|
|
||||||
|
for (svg_nh, p) in self.svgs_needle_hole:
|
||||||
|
origin_nh = Vpoint(origin.x + p.x + self.w2 / 2,
|
||||||
|
origin.y + p.y,
|
||||||
|
p.rad)
|
||||||
|
svg_nh.draw(origin_nh, color='#FF0000')
|
||||||
|
|
||||||
|
|
||||||
|
class Part2(object):
|
||||||
|
def __init__(self, parent, part1, dia2):
|
||||||
|
self.parent = parent
|
||||||
|
self.part1 = part1
|
||||||
|
self.dia2 = dia2
|
||||||
|
|
||||||
|
# グループ作成
|
||||||
|
attr = {inkex.addNS('label', 'inkscape'): 'Part2'}
|
||||||
|
self.g = etree.SubElement(self.parent, 'g', attr)
|
||||||
|
|
||||||
|
# 外枠
|
||||||
|
# ``Part1``の``points_outline``をミラーして、
|
||||||
|
# 最初の6つのポイントを利用
|
||||||
|
self.points_outline = []
|
||||||
|
for i in range(6):
|
||||||
|
self.points_outline.append(self.part1.points_outline[i].mirror())
|
||||||
|
|
||||||
|
self.svg_outline = SvgPolygon(self.g, self.points_outline)
|
||||||
|
|
||||||
|
# 留め具
|
||||||
|
self.svg_hole = SvgCircle(self.g, self.dia2 / 2)
|
||||||
|
|
||||||
|
# 針穴
|
||||||
|
# ``Part1``の``vpoints_needle``をミラーして利用
|
||||||
|
self.svgs_needle_hole = []
|
||||||
|
for v in self.part1.vpoints_needle:
|
||||||
|
v.mirror()
|
||||||
|
# ``SvgNeedleHole``もミラーする
|
||||||
|
svg_nh = SvgNeedleHole(self.g,
|
||||||
|
self.part1.needle_w,
|
||||||
|
self.part1.needle_h,
|
||||||
|
self.part1.needle_tf)
|
||||||
|
svg_nh.mirror()
|
||||||
|
self.svgs_needle_hole.append((svg_nh, v))
|
||||||
|
|
||||||
|
def draw(self, origin):
|
||||||
|
origin_base = Vpoint(origin.x + self.part1.w2 / 2,
|
||||||
|
origin.y, origin.rad)
|
||||||
|
self.svg_outline.draw(origin_base, color='#0000FF')
|
||||||
|
|
||||||
|
x = origin.x + self.part1.w2 / 2
|
||||||
|
y = origin.y + self.part1.h1 + self.part1.h2
|
||||||
|
y -= (self.svg_hole.r + self.part1.d1)
|
||||||
|
origin_hole = Vpoint(x, y, origin.rad)
|
||||||
|
self.svg_hole.draw(origin_hole, color='#FF0000')
|
||||||
|
|
||||||
|
for (svg_nh, p) in self.svgs_needle_hole:
|
||||||
|
origin_nh = Vpoint(origin.x + p.x + self.part1.w2 / 2,
|
||||||
|
origin.y + p.y,
|
||||||
|
p.rad)
|
||||||
|
svg_nh.draw(origin_nh, color='#FF0000')
|
||||||
|
|
||||||
|
|
||||||
|
class PliersCover(inkex.Effect):
|
||||||
|
DEF_OFFSET_X = 20
|
||||||
|
DEF_OFFSET_Y = 20
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
inkex.Effect.__init__(self)
|
||||||
|
self.arg_parser.add_argument("--tabs")
|
||||||
|
self.arg_parser.add_argument("--w1", type=float)
|
||||||
|
self.arg_parser.add_argument("--w2", type=float)
|
||||||
|
self.arg_parser.add_argument("--h1", type=float)
|
||||||
|
self.arg_parser.add_argument("--h2", type=float)
|
||||||
|
self.arg_parser.add_argument("--bw", type=float)
|
||||||
|
self.arg_parser.add_argument("--bl", type=float)
|
||||||
|
self.arg_parser.add_argument("--bf", type=float)
|
||||||
|
self.arg_parser.add_argument("--dia1", type=float)
|
||||||
|
self.arg_parser.add_argument("--dia2", type=float)
|
||||||
|
self.arg_parser.add_argument("--d1", type=float)
|
||||||
|
self.arg_parser.add_argument("--d2", type=float)
|
||||||
|
self.arg_parser.add_argument("--needle_w", type=float)
|
||||||
|
self.arg_parser.add_argument("--needle_h", type=float)
|
||||||
|
self.arg_parser.add_argument("--needle_tf", type=float)
|
||||||
|
self.arg_parser.add_argument("--needle_corner_rotation", type=inkex.Boolean, default=True)
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
# inkex.errormsg('view_center=%s' % str(self.view_center))
|
||||||
|
# inkex.errormsg('selected=%s' % str(self.selected))
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
opt = self.options
|
||||||
|
|
||||||
|
#
|
||||||
|
# error check
|
||||||
|
#
|
||||||
|
if opt.w1 >= opt.w2:
|
||||||
|
msg = "Error: w1(%d) > w2(%d) !" % (opt.w1, opt.w2)
|
||||||
|
inkex.errormsg(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
if opt.dia1 >= opt.bw:
|
||||||
|
msg = "Error: dia1(%d) >= bw(%d) !" % (opt.dia1, opt.bw)
|
||||||
|
inkex.errormsg(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
#
|
||||||
|
# draw
|
||||||
|
#
|
||||||
|
origin_vpoint = Vpoint(self.DEF_OFFSET_X, self.DEF_OFFSET_Y)
|
||||||
|
|
||||||
|
# グループ作成
|
||||||
|
attr = {inkex.addNS('label', 'inkscape'): 'PliersCover'}
|
||||||
|
self.g = etree.SubElement(self.svg.get_current_layer(), 'g', attr)
|
||||||
|
|
||||||
|
part1 = Part1(self.g,
|
||||||
|
opt.w1, opt.w2, opt.h1, opt.h2,
|
||||||
|
opt.bw, opt.bl, opt.bf, opt.dia1,
|
||||||
|
opt.d1, opt.d2,
|
||||||
|
opt.needle_w, opt.needle_h, opt.needle_tf,
|
||||||
|
opt.needle_corner_rotation)
|
||||||
|
part1.draw(origin_vpoint)
|
||||||
|
|
||||||
|
origin_vpoint.x += opt.w2 + 10
|
||||||
|
|
||||||
|
part2 = Part2(self.g, part1, opt.dia2)
|
||||||
|
part2.draw(origin_vpoint)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
PliersCover().run()
|
Reference in New Issue
Block a user