added spirograph extension, fix in vpypetools, adjusted polyhedra, some changes in styles to layers
This commit is contained in:
parent
5d87b2cd13
commit
6d27ef1a30
@ -1,18 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Animate Order - Kopfteil-Laser.svg</title>
|
|
||||||
<meta name="description" content="SVG Drawing Animation">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<button onclick="vi.reset().play();">replay</button>
|
|
||||||
<br/>
|
|
||||||
<object id="animate_order" type="image/svg+xml" data="drawing.svg"></object>
|
|
||||||
<script src="./vivus-0.4.6/dist/vivus.js"></script>
|
|
||||||
<script>
|
|
||||||
var vi = new Vivus('animate_order', {type: 'oneByOne', duration:7200.0, reverseStack:false});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 504 KiB |
1307
extensions/fablabchemnitz/inkscapeMadeEasy/inkscapeMadeEasy_Base.py
Normal file
1307
extensions/fablabchemnitz/inkscapeMadeEasy/inkscapeMadeEasy_Base.py
Normal file
File diff suppressed because it is too large
Load Diff
2231
extensions/fablabchemnitz/inkscapeMadeEasy/inkscapeMadeEasy_Draw.py
Normal file
2231
extensions/fablabchemnitz/inkscapeMadeEasy/inkscapeMadeEasy_Draw.py
Normal file
File diff suppressed because it is too large
Load Diff
1632
extensions/fablabchemnitz/inkscapeMadeEasy/inkscapeMadeEasy_Plot.py
Normal file
1632
extensions/fablabchemnitz/inkscapeMadeEasy/inkscapeMadeEasy_Plot.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -150,9 +150,9 @@ class Polyhedra(inkex.EffectExtension):
|
|||||||
gsub_attribs = {inkex.addNS('label','inkscape'):'Polygon ' + str( poly ) + 'border' }
|
gsub_attribs = {inkex.addNS('label','inkscape'):'Polygon ' + str( poly ) + 'border' }
|
||||||
gsub = etree.SubElement(g, 'g', gsub_attribs)
|
gsub = etree.SubElement(g, 'g', gsub_attribs)
|
||||||
|
|
||||||
# Create SVG Path for gear
|
# Create SVG Path
|
||||||
cutStyle = { 'stroke': '#0000FF', 'fill': 'none' }
|
cutStyle = { 'stroke': '#0000FF', 'stroke-width': '1px', 'fill': 'none' }
|
||||||
perfStyle = { 'stroke': '#FF0000', 'fill': 'none' }
|
perfStyle = { 'stroke': '#FF0000', 'stroke-width': '1px', 'fill': 'none' }
|
||||||
textStyle = {
|
textStyle = {
|
||||||
'font-size': str( size/4 ),
|
'font-size': str( size/4 ),
|
||||||
'font-family': 'arial',
|
'font-family': 'arial',
|
||||||
|
34
extensions/fablabchemnitz/spirograph.inx
Normal file
34
extensions/fablabchemnitz/spirograph.inx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>SpiroGraph</name>
|
||||||
|
<id>fablabchemnitz.de.spirograph</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="Cartesian plot" appearance="minimal" gui-text="Data">
|
||||||
|
<param name="curveType" type="optiongroup" appearance="combo" gui-text="Curve type:">
|
||||||
|
<option value="Epitrochoid">Epitrochoid</option>
|
||||||
|
<option value="Hypotrochoid">Hypotrochoid</option>
|
||||||
|
</param>
|
||||||
|
<label appearance="header">Curve parameters</label>
|
||||||
|
<param name="radius_R" type="int" min="0" max="1000" gui-text="Fixed circle radius (R):">10.0</param>
|
||||||
|
<param name="radius_r" type="int" min="-1000" max="1000" gui-text="Rolling circle radius (r):">5.0</param>
|
||||||
|
<param name="pencil_distance" type="int" min="-1000" max="1000" gui-text="Pencil distance¹ (d):">2</param>
|
||||||
|
<label>¹ use d=r for Epi/Hypocycloid. </label>
|
||||||
|
<label appearance="header">Plot parameters</label>
|
||||||
|
<param name="detailLevel" type="int" min="1" max="10" gui-text="Detail level:">1.0</param>
|
||||||
|
<param name="drawBaseCircles" type="bool" gui-text="Draw base circles">false</param>
|
||||||
|
<param name="animate" type="bool" gui-text="Animate">false</param>
|
||||||
|
<param type="path" name="directory" gui-text="Animation directory:" mode="folder"/>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Shape/Pattern from Generator"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">spirograph.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
251
extensions/fablabchemnitz/spirograph.py
Normal file
251
extensions/fablabchemnitz/spirograph.py
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
import scipy.signal as scipySignal
|
||||||
|
|
||||||
|
import inkscapeMadeEasy.inkscapeMadeEasy_Base as inkBase
|
||||||
|
import inkscapeMadeEasy.inkscapeMadeEasy_Draw as inkDraw
|
||||||
|
import inkscapeMadeEasy.inkscapeMadeEasy_Plot as inkPlot
|
||||||
|
|
||||||
|
|
||||||
|
# least common multiplier
|
||||||
|
def myLcm(x, y):
|
||||||
|
return x * y / math.gcd(int(x), int(y))
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------
|
||||||
|
# noinspection PyAttributeOutsideInit
|
||||||
|
class Spirograph(inkBase.inkscapeMadeEasy):
|
||||||
|
def __init__(self):
|
||||||
|
inkBase.inkscapeMadeEasy.__init__(self)
|
||||||
|
|
||||||
|
self.arg_parser.add_argument("--tab", type=str, dest="tab", default="object")
|
||||||
|
self.arg_parser.add_argument("--curveType", type=str, dest="curveType", default='resistor')
|
||||||
|
self.arg_parser.add_argument("--radius_R", type=float, dest="radius_R", default=10.0)
|
||||||
|
self.arg_parser.add_argument("--radius_r", type=float, dest="radius_r", default=5.0)
|
||||||
|
self.arg_parser.add_argument("--detailLevel", type=float, dest="detailLevel", default=1.0)
|
||||||
|
self.arg_parser.add_argument("--adaptiveTheta", type=self.bool, dest="adaptiveTheta", default=False)
|
||||||
|
self.arg_parser.add_argument("--pencil_distance", type=float, dest="pencil_distance", default=1.0)
|
||||||
|
self.arg_parser.add_argument("--drawBaseCircles", type=self.bool, dest="drawBaseCircles", default=False)
|
||||||
|
self.arg_parser.add_argument("--animate", type=self.bool, dest="animate", default=False)
|
||||||
|
self.arg_parser.add_argument("--directory", type=str, dest="directory", default='./')
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
so = self.options
|
||||||
|
|
||||||
|
# sets the position to the viewport center, round to next 10.
|
||||||
|
position = [self.svg.namedview.center[0], self.svg.namedview.center[1]]
|
||||||
|
position[0] = int(math.ceil(position[0] / 10.0)) * 10
|
||||||
|
position[1] = int(math.ceil(position[1] / 10.0)) * 10
|
||||||
|
|
||||||
|
root_layer = self.document.getroot()
|
||||||
|
group = self.createGroup(root_layer, 'Spiro')
|
||||||
|
|
||||||
|
so.tab = so.tab.replace('"', '') # removes de exceeding double quotes from the string
|
||||||
|
|
||||||
|
# curve parameters
|
||||||
|
R = so.radius_R
|
||||||
|
r = so.radius_r
|
||||||
|
d = so.pencil_distance
|
||||||
|
finalTheta = 2 * np.pi * myLcm(abs(r), R) / R
|
||||||
|
|
||||||
|
if 'hypo' in so.curveType.lower():
|
||||||
|
typeCurve = 'hypo'
|
||||||
|
if 'epi' in so.curveType.lower():
|
||||||
|
typeCurve = 'epi'
|
||||||
|
|
||||||
|
# markers and linestyles
|
||||||
|
Lgray = inkDraw.color.gray(0.8)
|
||||||
|
Dgray = inkDraw.color.gray(0.3)
|
||||||
|
# wheel
|
||||||
|
markerCenterDisk = inkDraw.marker.createDotMarker(self, nameID='diskCenter', scale=0.3, RenameMode=1, strokeColor=Dgray,
|
||||||
|
fillColor=inkDraw.color.defined('white'))
|
||||||
|
markerPen = inkDraw.marker.createDotMarker(self, nameID='diskPen', scale=0.3, RenameMode=1, strokeColor=Dgray,
|
||||||
|
fillColor=inkDraw.color.defined('white'))
|
||||||
|
[startArrowMarker, endArrowMarker] = inkDraw.marker.createArrow1Marker(self, nameID='arrowRot', RenameMode=1, scale=0.3, strokeColor=Dgray,
|
||||||
|
fillColor=Dgray)
|
||||||
|
|
||||||
|
if typeCurve == 'hypo':
|
||||||
|
self.lineStyleArrow = inkDraw.lineStyle.set(lineWidth=r / 40, lineColor=Dgray, markerStart=startArrowMarker, markerEnd=None)
|
||||||
|
else:
|
||||||
|
self.lineStyleArrow = inkDraw.lineStyle.set(lineWidth=r / 40, lineColor=Dgray, markerStart=None, markerEnd=endArrowMarker)
|
||||||
|
|
||||||
|
self.lineStyleARM = inkDraw.lineStyle.set(lineWidth=r / 40, lineColor=Dgray, markerStart=markerCenterDisk, markerEnd=markerPen)
|
||||||
|
self.lineStyleDisk = inkDraw.lineStyle.set(lineWidth=r / 40, lineColor=None, fillColor=Lgray)
|
||||||
|
|
||||||
|
# curve
|
||||||
|
self.lineStyleCurve = inkDraw.lineStyle.set(lineWidth=0.8, lineColor=inkDraw.color.defined('red'), markerStart=None, markerEnd=None,
|
||||||
|
markerMid=None)
|
||||||
|
self.lineStyleCurve2 = inkDraw.lineStyle.set(lineWidth=0.8, lineColor=inkDraw.color.defined('Dgreen'), markerStart=None, markerEnd=None,
|
||||||
|
markerMid=None)
|
||||||
|
self.lineStyleCurve3 = inkDraw.lineStyle.set(lineWidth=0.8, lineColor=inkDraw.color.defined('blue'), markerStart=None, markerEnd=None,
|
||||||
|
markerMid=None)
|
||||||
|
|
||||||
|
self.lineStylePre = inkDraw.lineStyle.set(lineWidth=1, lineColor=inkDraw.color.defined('red'))
|
||||||
|
self.constructionLine = inkDraw.lineStyle.set(lineWidth=0.5, lineColor=Dgray)
|
||||||
|
|
||||||
|
# draft Points
|
||||||
|
if so.adaptiveTheta:
|
||||||
|
nPrePoints = 10 * so.detailLevel # number of pre points per turn
|
||||||
|
thetasDraft = np.linspace(0, finalTheta, int(nPrePoints * finalTheta / (2 * np.pi)))
|
||||||
|
|
||||||
|
[pointsDraft, _, curvatureDraft] = self.calcCurve__trochoid(typeCurve, R, r, d, thetasDraft)
|
||||||
|
|
||||||
|
# find sampling points based on local curvature
|
||||||
|
nSamples = np.ones(curvatureDraft.shape)*min(2,so.detailLevel)
|
||||||
|
detailFactor=5
|
||||||
|
# treshold normalized curvatures
|
||||||
|
nSamples[curvatureDraft>0.8] *=detailFactor
|
||||||
|
detailFactor = 2.5
|
||||||
|
# check if vector changed direction abuptly
|
||||||
|
for i,p in enumerate(pointsDraft):
|
||||||
|
if i==0:
|
||||||
|
v1=pointsDraft[i+1]-pointsDraft[i]
|
||||||
|
v2=pointsDraft[i]-pointsDraft[-1]
|
||||||
|
elif i < len(pointsDraft)-1:
|
||||||
|
v1=pointsDraft[i+1]-pointsDraft[i]
|
||||||
|
v2=pointsDraft[i]-pointsDraft[i-1]
|
||||||
|
else:
|
||||||
|
v1=pointsDraft[0]-pointsDraft[i]
|
||||||
|
v2=pointsDraft[i]-pointsDraft[i-1]
|
||||||
|
|
||||||
|
v1=v1/np.linalg.norm(v1)
|
||||||
|
v2=v2/np.linalg.norm(v2)
|
||||||
|
if np.dot(v1,v2)<0.5:
|
||||||
|
nSamples[i] *=detailFactor
|
||||||
|
|
||||||
|
thetasFinal = np.array([])
|
||||||
|
for i in range(len(nSamples) - 1):
|
||||||
|
thetasFinal = np.append(thetasFinal, np.linspace(thetasDraft[i], thetasDraft[i + 1], int(nSamples[i]), endpoint=False))
|
||||||
|
|
||||||
|
thetasFinal = np.append(thetasFinal, finalTheta)
|
||||||
|
# filter the sampled angles to have a smooth transition.
|
||||||
|
Ntaps = 5
|
||||||
|
gaussWindow = scipySignal.gaussian(Ntaps, std=5)
|
||||||
|
gaussWindow = gaussWindow / np.sum(gaussWindow)
|
||||||
|
|
||||||
|
# inkPlot.plot.cartesian(self, root_layer, np.arange(thetasFinal.shape[0]), thetasFinal * 180 / np.pi, position, xTicks=False, yTicks=True, xTickStep=thetasFinal.shape[0]/10, yTickStep=120.0, xScale=10, yScale=10,xGrid=True, yGrid=True, forceXlim=None, forceYlim=None)
|
||||||
|
thetasFinal = scipySignal.filtfilt(gaussWindow, np.array([1]), thetasFinal)
|
||||||
|
|
||||||
|
# inkPlot.plot.cartesian(self, root_layer, np.arange(thetasFinal.shape[0]), thetasFinal * 180 / np.pi, position, xTicks=False, yTicks=True,xTickStep=thetasFinal.shape[0]/10, yTickStep=120.0, xScale=10, yScale=10, xGrid=True, yGrid=True, forceXlim=None, forceYlim=None,drawAxis=False)
|
||||||
|
else:
|
||||||
|
nPrePoints = 20 * so.detailLevel # number of pre points per turn
|
||||||
|
thetasFinal = np.linspace(0, finalTheta, int(nPrePoints * finalTheta / (2 * np.pi)))
|
||||||
|
|
||||||
|
# final shape
|
||||||
|
[PointsFinal, CentersFinal, curvatureFinal] = self.calcCurve__trochoid(typeCurve, R, r, d, thetasFinal)
|
||||||
|
[PointsFinal2, CentersFinal2, curvatureFinal2] = self.calcCurve__trochoid(typeCurve, R, r, -d, thetasFinal)
|
||||||
|
[PointsFinal3, CentersFinal3, curvatureFinal3] = self.calcCurve__trochoid(typeCurve, R, r, r, thetasFinal)
|
||||||
|
|
||||||
|
if so.animate:
|
||||||
|
animGroup = self.createGroup(group, 'Anim')
|
||||||
|
|
||||||
|
circle_R = inkDraw.circle.centerRadius(parent=animGroup, centerPoint=[0, 0], radius=R, offset=position, lineStyle=self.constructionLine)
|
||||||
|
|
||||||
|
# draw planetary wheel
|
||||||
|
wheelGroup = self.createGroup(animGroup, 'Anim')
|
||||||
|
circle_r = inkDraw.circle.centerRadius(wheelGroup, centerPoint=CentersFinal[0], radius=r, offset=position, lineStyle=self.lineStyleDisk)
|
||||||
|
arms1 = inkDraw.line.absCoords(wheelGroup, coordsList=[CentersFinal[0], PointsFinal[0]], offset=position, lineStyle=self.lineStyleARM)
|
||||||
|
arms2 = inkDraw.line.absCoords(wheelGroup, coordsList=[CentersFinal2[0], PointsFinal2[0]], offset=position, lineStyle=self.lineStyleARM)
|
||||||
|
arms3 = inkDraw.line.absCoords(wheelGroup, coordsList=[CentersFinal3[0], PointsFinal3[0]], offset=position, lineStyle=self.lineStyleARM)
|
||||||
|
|
||||||
|
arc1 = inkDraw.arc.centerAngStartAngEnd(wheelGroup, centerPoint=CentersFinal[0], radius=r * 0.6, angStart=40, angEnd=80, offset=position,
|
||||||
|
lineStyle=self.lineStyleArrow)
|
||||||
|
arc2 = inkDraw.arc.centerAngStartAngEnd(wheelGroup, centerPoint=CentersFinal[0], radius=r * 0.6, angStart=160, angEnd=200,
|
||||||
|
offset=position, lineStyle=self.lineStyleArrow)
|
||||||
|
arc3 = inkDraw.arc.centerAngStartAngEnd(wheelGroup, centerPoint=CentersFinal[0], radius=r * 0.6, angStart=280, angEnd=320,
|
||||||
|
offset=position, lineStyle=self.lineStyleArrow)
|
||||||
|
|
||||||
|
self.exportSVG(animGroup, os.path.join(so.directory,'outSVG_%1.5d.svg' % 0))
|
||||||
|
|
||||||
|
for i in range(1, len(thetasFinal)):
|
||||||
|
|
||||||
|
self.moveElement(wheelGroup, [CentersFinal[i][0] - CentersFinal[i - 1][0], CentersFinal[i][1] - CentersFinal[i - 1][1]])
|
||||||
|
if typeCurve == 'hypo':
|
||||||
|
self.rotateElement(wheelGroup, [position[0] + CentersFinal[i][0], position[1] + CentersFinal[i][1]],
|
||||||
|
(thetasFinal[i] - thetasFinal[i - 1]) * (R - r) / r * 180 / np.pi)
|
||||||
|
else:
|
||||||
|
self.rotateElement(wheelGroup, [position[0] + CentersFinal[i][0], position[1] + CentersFinal[i][1]],
|
||||||
|
- (thetasFinal[i] - thetasFinal[i - 1]) * (R + r) / r * 180 / np.pi)
|
||||||
|
|
||||||
|
curve1 = inkDraw.line.absCoords(parent=animGroup, coordsList=PointsFinal[:i + 1], offset=position, lineStyle=self.lineStyleCurve,
|
||||||
|
closePath=False)
|
||||||
|
curve2 = inkDraw.line.absCoords(parent=animGroup, coordsList=PointsFinal2[:i + 1], offset=position, lineStyle=self.lineStyleCurve2,
|
||||||
|
closePath=False)
|
||||||
|
curve3 = inkDraw.line.absCoords(parent=animGroup, coordsList=PointsFinal3[:i + 1], offset=position, lineStyle=self.lineStyleCurve3,
|
||||||
|
closePath=False)
|
||||||
|
|
||||||
|
self.exportSVG(animGroup, os.path.join(so.directory , 'outSVG_%1.5d.svg' % i))
|
||||||
|
|
||||||
|
self.removeElement(curve1)
|
||||||
|
self.removeElement(curve2)
|
||||||
|
self.removeElement(curve3)
|
||||||
|
self.removeElement(animGroup)
|
||||||
|
else:
|
||||||
|
if so.drawBaseCircles:
|
||||||
|
inkDraw.circle.centerRadius(parent=group, centerPoint=position, radius=R, offset=[0, 0], lineStyle=self.constructionLine)
|
||||||
|
|
||||||
|
if typeCurve == 'hypo':
|
||||||
|
inkDraw.circle.centerRadius(parent=group, centerPoint=position, radius=r, offset=[R - r, 0], lineStyle=self.constructionLine)
|
||||||
|
if typeCurve == 'epi':
|
||||||
|
inkDraw.circle.centerRadius(parent=group, centerPoint=position, radius=r, offset=[R + r, 0], lineStyle=self.constructionLine)
|
||||||
|
|
||||||
|
inkDraw.line.absCoords(group, PointsFinal, position, 'spiro', self.lineStyleCurve, closePath=True)
|
||||||
|
|
||||||
|
# plot curvatures
|
||||||
|
if False:
|
||||||
|
inkPlot.plot.polar(self, group, curvatureFinal, thetasFinal * 180 / np.pi, [position[0] + 3 * R, position[1]], rTicks=False,
|
||||||
|
tTicks=False, rTickStep=0.2, tTickStep=45.0, rScale=20, rGrid=True, tGrid=True, lineStylePlot=self.lineStyleCurve,
|
||||||
|
forceRlim=[0.0, 1.0])
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# typeCurve: 'hypo', 'epi'
|
||||||
|
def calcCurve__trochoid(self, typeCurve, R, r, d, thetas):
|
||||||
|
j = complex(0, 1)
|
||||||
|
if typeCurve.lower() == 'hypo':
|
||||||
|
# https://www.mathcurve.com/courbes2d.gb/hypotrochoid/hypotrochoid.shtml
|
||||||
|
P_complex = (R - r) * np.exp(j * thetas) + d * np.exp(-j * thetas * (R - r) / r)
|
||||||
|
dP_complex = (R - r) * j * np.exp(j * thetas) + d * (-j) * (R - r) / r * np.exp(-j * thetas * (R - r) / r)
|
||||||
|
ddP_complex = (R - r) * (-1) * np.exp(j * thetas) + d * (-1) * ((R - r) / r) ** 2 * np.exp(-j * thetas * (R - r) / r)
|
||||||
|
centerGear = (R - r) * np.exp(j * thetas)
|
||||||
|
if typeCurve.lower() == 'epi':
|
||||||
|
# https://www.mathcurve.com/courbes2d.gb/epitrochoid/epitrochoid.shtml
|
||||||
|
P_complex = (R + r) * np.exp(j * thetas) - d * np.exp(j * thetas * (R + r) / r)
|
||||||
|
dP_complex = (R + r) * j * np.exp(j * thetas) - d * j * (R + r) / r * np.exp(j * thetas * (R + r) / r)
|
||||||
|
ddP_complex = (R + r) * (-1) * np.exp(j * thetas) - d * (-1) * ((R + r) / r) ** 2 * np.exp(j * thetas * (R + r) / r)
|
||||||
|
centerGear = (R + r) * np.exp(j * thetas)
|
||||||
|
|
||||||
|
with np.errstate(divide='ignore', invalid='ignore'):
|
||||||
|
curvature = np.divide(abs(dP_complex.real * ddP_complex.imag - dP_complex.imag * ddP_complex.real),
|
||||||
|
(dP_complex.real ** 2 + dP_complex.imag ** 2) ** (2 / 3))
|
||||||
|
|
||||||
|
# remove Nan=0/0
|
||||||
|
np.nan_to_num(curvature, copy=False)
|
||||||
|
|
||||||
|
# remove values too large
|
||||||
|
curvature[curvature > 10 * np.mean(curvature)] = 0.0
|
||||||
|
|
||||||
|
# self.Dump(curvature, '/home/fernando/lixo.txt', 'w')
|
||||||
|
# normalize curvature
|
||||||
|
curvature = self._normalizeCurvatures(curvature, 0.0, 1.0)
|
||||||
|
|
||||||
|
Points = np.column_stack((P_complex.real, P_complex.imag))
|
||||||
|
Centers = np.column_stack((centerGear.real, centerGear.imag))
|
||||||
|
|
||||||
|
return [Points, Centers, curvature]
|
||||||
|
|
||||||
|
def _normalizeCurvatures(self, curvatures, normMin=0.0, normMax=1.0):
|
||||||
|
y1 = normMin
|
||||||
|
y2 = normMax
|
||||||
|
x1 = np.min(curvatures)
|
||||||
|
x2 = np.max(curvatures)
|
||||||
|
alpha = (y2 - y1) / (x2 - x1)
|
||||||
|
return alpha * (curvatures - x1) + y1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sp = Spirograph()
|
||||||
|
sp.run()
|
@ -23,7 +23,7 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
def findLayer(self, layerName):
|
def findLayer(self, layerName):
|
||||||
svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS)
|
svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS)
|
||||||
for layer in svg_layers:
|
for layer in svg_layers:
|
||||||
#inkex.utils.debug(str(layer.get('inkscape:label')) + " == " + layerName)
|
#self.msg(str(layer.get('inkscape:label')) + " == " + layerName)
|
||||||
if layer.get('inkscape:label') == layerName:
|
if layer.get('inkscape:label') == layerName:
|
||||||
return layer
|
return layer
|
||||||
return None
|
return None
|
||||||
@ -31,7 +31,7 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
def createLayer(self, layerNodeList, layerName):
|
def createLayer(self, layerNodeList, layerName):
|
||||||
svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]
|
svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]
|
||||||
for layer in layerNodeList:
|
for layer in layerNodeList:
|
||||||
#inkex.utils.debug(str(layer[0].get('inkscape:label')) + " == " + layerName)
|
#self.msg(str(layer[0].get('inkscape:label')) + " == " + layerName)
|
||||||
if layer[0].get('inkscape:label') == layerName:
|
if layer[0].get('inkscape:label') == layerName:
|
||||||
return layer[0] #already exists. Do not create duplicate
|
return layer[0] #already exists. Do not create duplicate
|
||||||
layer = etree.SubElement(svg, 'g')
|
layer = etree.SubElement(svg, 'g')
|
||||||
@ -68,8 +68,8 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
import applytransform
|
import applytransform
|
||||||
applyTransformAvailable = True
|
applyTransformAvailable = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# inkex.utils.debug(e)
|
# self.msg(e)
|
||||||
inkex.utils.debug("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
|
self.msg("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
|
||||||
|
|
||||||
layer_name = None
|
layer_name = None
|
||||||
layerNodeList = [] #list with layer, neutral_value, element and self.options.separateby type
|
layerNodeList = [] #list with layer, neutral_value, element and self.options.separateby type
|
||||||
@ -175,12 +175,11 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
layer_name = "fill-opacity-none"
|
layer_name = "fill-opacity-none"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
inkex.utils.debug("No proper option selected.")
|
self.msg("No proper option selected.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
if neutral_value is not None: #apply decimals filter
|
if neutral_value is not None: #apply decimals filter
|
||||||
neutral_value = float(round(neutral_value, self.options.decimals))
|
neutral_value = float(round(neutral_value, self.options.decimals))
|
||||||
|
|
||||||
if layer_name is not None:
|
if layer_name is not None:
|
||||||
layer_name = layer_name.split(";")[0] #cut off existing semicolons to avoid duplicated layers with/without semicolon
|
layer_name = layer_name.split(";")[0] #cut off existing semicolons to avoid duplicated layers with/without semicolon
|
||||||
currentLayer = self.findLayer(layer_name)
|
currentLayer = self.findLayer(layer_name)
|
||||||
@ -188,13 +187,16 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
layerNodeList.append([self.createLayer(layerNodeList, layer_name), neutral_value, element, self.options.separateby])
|
layerNodeList.append([self.createLayer(layerNodeList, layer_name), neutral_value, element, self.options.separateby])
|
||||||
else:
|
else:
|
||||||
layerNodeList.append([currentLayer, neutral_value, element, self.options.separateby]) #layer is existent. append items to this later
|
layerNodeList.append([currentLayer, neutral_value, element, self.options.separateby]) #layer is existent. append items to this later
|
||||||
|
elif layer_name is None and self.options.put_unfiltered:
|
||||||
|
layer_name = 'without-' + self.options.separateby + '-in-style-attribute'
|
||||||
else: #if no style attribute in element and not a group
|
else: #if no style attribute in element and not a group
|
||||||
if isinstance(element, inkex.Group) is False:
|
if isinstance(element, inkex.Group) is False:
|
||||||
if self.options.show_info:
|
if self.options.show_info:
|
||||||
inkex.utils.debug(element.get('id') + ' has no style attribute')
|
self.msg(element.get('id') + ' has no style attribute')
|
||||||
if self.options.put_unfiltered:
|
if self.options.put_unfiltered:
|
||||||
layer_name = 'without-style-attribute'
|
layer_name = 'without-style-attribute'
|
||||||
currentLayer = self.findLayer(layer_name)
|
currentLayer = self.findLayer(layer_name)
|
||||||
|
|
||||||
if currentLayer is None: #layer does not exist, so create a new one
|
if currentLayer is None: #layer does not exist, so create a new one
|
||||||
layerNodeList.append([self.createLayer(layerNodeList, layer_name), None, element, None])
|
layerNodeList.append([self.createLayer(layerNodeList, layer_name), None, element, None])
|
||||||
else:
|
else:
|
||||||
@ -235,11 +237,11 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
maxval = max(minmax_range)
|
maxval = max(minmax_range)
|
||||||
sliceinterval = (maxval - minval) / self.options.subdividethreshold
|
sliceinterval = (maxval - minval) / self.options.subdividethreshold
|
||||||
|
|
||||||
#inkex.utils.debug("neutral values (sorted) = " + str(minmax_range))
|
#self.msg("neutral values (sorted) = " + str(minmax_range))
|
||||||
#inkex.utils.debug("min neutral_value = " + str(minval))
|
#self.msg("min neutral_value = " + str(minval))
|
||||||
#inkex.utils.debug("max neutral_value = " + str(maxval))
|
#self.msg("max neutral_value = " + str(maxval))
|
||||||
#inkex.utils.debug("slice value (divide step size) = " + str(sliceinterval))
|
#self.msg("slice value (divide step size) = " + str(sliceinterval))
|
||||||
#inkex.utils.debug("subdivides (parent layers) = " + str(self.options.subdividethreshold))
|
#self.msg("subdivides (parent layers) = " + str(self.options.subdividethreshold))
|
||||||
|
|
||||||
for layerNode in layerNodeList:
|
for layerNode in layerNodeList:
|
||||||
for x in range(0, self.options.subdividethreshold): #loop through the sorted neutral_values and determine to which layer they should belong
|
for x in range(0, self.options.subdividethreshold): #loop through the sorted neutral_values and determine to which layer they should belong
|
||||||
@ -276,8 +278,8 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
|
|
||||||
#finally append the sublayers to the slices
|
#finally append the sublayers to the slices
|
||||||
#for layer in topLevelLayerNodeList:
|
#for layer in topLevelLayerNodeList:
|
||||||
#inkex.utils.debug(layer[0].get('inkscape:label'))
|
#self.msg(layer[0].get('inkscape:label'))
|
||||||
#inkex.utils.debug(layer[1])
|
#self.msg(layer[1])
|
||||||
for newLayerNode in topLevelLayerNodeList:
|
for newLayerNode in topLevelLayerNodeList:
|
||||||
newLayerNode[0].append(newLayerNode[1]) #append newlayer to layer
|
newLayerNode[0].append(newLayerNode[1]) #append newlayer to layer
|
||||||
|
|
||||||
@ -286,7 +288,7 @@ class StylesToLayers(inkex.EffectExtension):
|
|||||||
import cleangroups
|
import cleangroups
|
||||||
cleangroups.CleanGroups.effect(self)
|
cleangroups.CleanGroups.effect(self)
|
||||||
except:
|
except:
|
||||||
inkex.utils.debug("Calling 'Remove Empty Groups' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
|
self.msg("Calling 'Remove Empty Groups' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
StylesToLayers().run()
|
StylesToLayers().run()
|
@ -30,7 +30,7 @@ Extension for InkScape 1.X
|
|||||||
Author: Mario Voigt / FabLab Chemnitz
|
Author: Mario Voigt / FabLab Chemnitz
|
||||||
Mail: mario.voigt@stadtfabrikanten.org
|
Mail: mario.voigt@stadtfabrikanten.org
|
||||||
Date: 02.04.2021
|
Date: 02.04.2021
|
||||||
Last patch: 08.04.2021
|
Last patch: 24.04.2021
|
||||||
License: GNU GPL v3
|
License: GNU GPL v3
|
||||||
|
|
||||||
This piece of spaghetti-code, called "vpypetools", is a wrapper to pass (pipe) line elements from InkScape selection (or complete canvas) to vpype.
|
This piece of spaghetti-code, called "vpypetools", is a wrapper to pass (pipe) line elements from InkScape selection (or complete canvas) to vpype.
|
||||||
@ -180,7 +180,7 @@ class vpypetools (inkex.EffectExtension):
|
|||||||
for csp in subPath:
|
for csp in subPath:
|
||||||
if len(csp[1]) > 0: #we need exactly two points per straight line segment
|
if len(csp[1]) > 0: #we need exactly two points per straight line segment
|
||||||
points.append(Point(round(csp[1][0], self.options.decimals), round(csp[1][1], self.options.decimals)))
|
points.append(Point(round(csp[1][0], self.options.decimals), round(csp[1][1], self.options.decimals)))
|
||||||
if subPath[-1][0] == 'Z' or subPath[0][1] == subPath[-1][1]: #check if path is closed by Z or first pont == last point
|
if len(subPath) > 2 and (subPath[-1][0] == 'Z' or subPath[0][1] == subPath[-1][1]): #check if path has more than 2 points and is closed by Z or first pont == last point
|
||||||
points.append(Point(round(subPath[0][1][0], self.options.decimals), round(subPath[0][1][1], self.options.decimals))) #if closed, we add the first point again
|
points.append(Point(round(subPath[0][1][0], self.options.decimals), round(subPath[0][1][1], self.options.decimals))) #if closed, we add the first point again
|
||||||
lc.append(LineString(points))
|
lc.append(LineString(points))
|
||||||
|
|
||||||
@ -365,7 +365,7 @@ class vpypetools (inkex.EffectExtension):
|
|||||||
|
|
||||||
# convert vpype polylines/lines/polygons to regular paths again. We need to use "--with-gui" to respond to "WARNING: ignoring verb FileSave - GUI required for this verb."
|
# convert vpype polylines/lines/polygons to regular paths again. We need to use "--with-gui" to respond to "WARNING: ignoring verb FileSave - GUI required for this verb."
|
||||||
if self.options.strokes_to_paths is True:
|
if self.options.strokes_to_paths is True:
|
||||||
cli_output = inkscape(output_file, "--with-gui", actions="EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit")
|
cli_output = inkscape(output_file, "--with-gui", actions="EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit") #we do not use StrokeToPath because it will convert svg:line to a svg:path, but as closed path with four points
|
||||||
if len(cli_output) > 0:
|
if len(cli_output) > 0:
|
||||||
self.debug(_("Inkscape returned the following output when trying to run the vpype object to path back-conversion:"))
|
self.debug(_("Inkscape returned the following output when trying to run the vpype object to path back-conversion:"))
|
||||||
self.debug(cli_output)
|
self.debug(cli_output)
|
||||||
@ -436,7 +436,7 @@ class vpypetools (inkex.EffectExtension):
|
|||||||
if self.options.input_handling == "layers":
|
if self.options.input_handling == "layers":
|
||||||
if self_viewBox is not None:
|
if self_viewBox is not None:
|
||||||
element.set('transform', 'scale(' + str(scaleX) + ',' + str(scaleY) + ')') #imported groups need to be transformed. Or they have wrong size. Reason: different viewBox sizes/units in namedview definitions
|
element.set('transform', 'scale(' + str(scaleX) + ',' + str(scaleY) + ')') #imported groups need to be transformed. Or they have wrong size. Reason: different viewBox sizes/units in namedview definitions
|
||||||
if self.options.apply_transformations is True and applyTransformAvailable is True: #we apply the transforms directly after adding them
|
if self.options.apply_transformations is True and applyTransformAvailable is True and self.options.strokes_to_paths is True: #we apply the transforms directly after adding them, but we need to have strokes_to_paths enabled!
|
||||||
applytransform.ApplyTransform().recursiveFuseTransform(element)
|
applytransform.ApplyTransform().recursiveFuseTransform(element)
|
||||||
|
|
||||||
# Delete the temporary file again because we do not need it anymore
|
# Delete the temporary file again because we do not need it anymore
|
||||||
|
Reference in New Issue
Block a user