Mario Voigt 2021-11-24 00:38:41 +01:00
commit d6409fda72
14 changed files with 343 additions and 123 deletions

View File

@ -36,7 +36,7 @@ DETACHED_PROCESS = 0x00000008
class AnimateOrder(inkex.EffectExtension):
def spawnIndependentProcess(self, args): #function to spawn non-blocking inkscape instance. the inkscape command is available because it is added to ENVIRONMENT when Inkscape main instance is started
def spawnIndependentProcess(self, args):
warnings.simplefilter('ignore', ResourceWarning) #suppress "enable tracemalloc to get the object allocation traceback"
if os.name == 'nt':
subprocess.Popen(args, close_fds=True, creationflags=DETACHED_PROCESS)

View File

@ -69,7 +69,7 @@ class LinksCreator(inkex.EffectExtension):
if element.tag == inkex.addNS('path','svg'):
parent = element.getparent()
idx = parent.index(element)
idSuffix = 0
idSuffix = 0
raw = element.path.to_arrays()
subPaths, prev = [], 0
for i in range(len(raw)): # Breaks compound paths into simple paths

View File

@ -3,7 +3,13 @@
<name>Draw Directions / Travel Moves</name>
<id>fablabchemnitz.de.draw_directions_tavel_moves</id>
<param name="nb_main" type="notebook">
<page name="tab_settings" gui-text="Settings">
<page name="tab_draw" gui-text="Draw">
<param name="order" type="optiongroup" appearance="combo" gui-text="Order">
<option value="separate_groups">To separate groups</option>
<option value="separate_layers">To separate layers</option>
<option value="element_index">At element's index</option>
</param>
<separator/>
<param name="draw_dots" type="bool" gui-text="Draw dots" gui-description="Start and end point of opened (red + blue) and closed paths (green + yellow)">true</param>
<param name="dotsize" type="int" min="1" max="9999" gui-text="Dot size (px)">10</param>
<separator/>
@ -15,9 +21,12 @@
<separator/>
<param name="debug" type="bool" gui-text="Print debug info">false</param>
</page>
<page name="tab_remove" gui-text="Remove">
<label>If this page is selected, you can remove all old travel lines/groups Just press 'apply'.</label>
</page>
<page name="tab_about" gui-text="About">
<label appearance="header">Draw Directions / Travel Moves</label>
<label>Draw travel lines and/or dots to mark start and end points of paths</label>
<label>Draw travel lines and/or dots to mark start and end points of paths and to show the line order.</label>
<label>2021 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
<spacer/>
<label appearance="header">Online Documentation</label>

View File

@ -6,21 +6,22 @@ import inkex
from inkex import Circle, Vector2d
from inkex.paths import Path
from inkex.bezier import csplength
from debugpy.common.timestamp import current
'''
ToDos
- draw numbers for each travel lines next to the line
- option selection: add travel lines by color groups or add them at the index of the line -> this keeps the order in total (will need transform of start point and second transform of end point, if they are in different groups)
- option to just remove all generated travelLines/dots (especially hard to delete if they are inserted at element order)
'''
class DrawDirectionsTravelMoves(inkex.EffectExtension):
def drawCircle(self, group, color, point):
style = inkex.Style({'stroke': 'none', 'fill': color})
startCircle = group.add(Circle(cx=str(point[0]), cy=str(point[1]), r=str(self.svg.unittouu(str(so.dotsize/2) + "px"))))
startCircle.style = style
def find_group(self, groupId):
''' check if a group with a given id exists or not. Returns None if not found, else returns the group element '''
groups = self.document.xpath('//svg:g', namespaces=inkex.NSS)
@ -29,10 +30,36 @@ class DrawDirectionsTravelMoves(inkex.EffectExtension):
if group.get('id') == groupId:
return group
return None
def createTravelMarker(self, markerId):
#add new marker to defs (or overwrite if existent)
defs = self.svg.defs
for defi in defs:
if defi.tag == "{http://www.w3.org/2000/svg}marker" and defi.get('id') == markerId: #delete
defi.delete()
marker = inkex.Marker(id=markerId)
marker.set("inkscape:isstock", "true")
marker.set("inkscape:stockid", markerId)
marker.set("orient", "auto")
marker.set("refY", "0.0")
marker.set("refX", "0.0")
marker.set("style", "overflow:visible;")
markerPath = inkex.PathElement(id=self.svg.get_unique_id('markerId-'))
markerPath.style = {"fill-rule": "evenodd", "fill": "context-stroke", "stroke-width": str(self.svg.unittouu('1px'))}
markerPath.transform = "scale(1.0,1.0) rotate(0) translate(-6.0,0)"
arrowHeight = 6.0
markerPath.attrib["transform"] = "scale({},{}) rotate(0) translate(-{},0)".format(so.arrow_size, so.arrow_size, arrowHeight)
markerPath.path = "M {},0.0 L -3.0,5.0 L -3.0,-5.0 L {},0.0 z".format(arrowHeight, arrowHeight)
marker.append(markerPath)
defs.append(marker) #do not append before letting contain it one path. Otherwise Inkscape is going to crash immediately
def add_arguments(self, pars):
pars.add_argument("--nb_main")
pars.add_argument("--order", default="separate_groups")
pars.add_argument("--draw_dots", type=inkex.Boolean, default=True, help="Start and end point of opened (red + blue) and closed paths (green + yellow)")
pars.add_argument("--dotsize", type=int, default=10, help="Dot size (px)")
pars.add_argument("--draw_travel_moves", type=inkex.Boolean, default=False)
@ -45,62 +72,76 @@ class DrawDirectionsTravelMoves(inkex.EffectExtension):
def effect(self):
global so
so = self.options
linePrefix = "travelLine-"
dotPrefix = "dots-"
lineSuffix = "-travelLine"
groupPrefix = "travelLines-"
ignoreWord = "ignore"
if so.nb_main == "tab_remove":
#remove dots
dots = self.document.xpath("//svg:g[starts-with(@id, '" + dotPrefix + "')]", namespaces=inkex.NSS)
for dot in dots:
dot.delete()
#remove travel lines
travelLines = self.document.xpath("//svg:path[contains(@id, '" + lineSuffix + "')]", namespaces=inkex.NSS)
for travelLine in travelLines:
travelLine.delete()
#remove travel groups/layers
travelGroups = self.document.xpath("//svg:g[starts-with(@id, '" + groupPrefix + "')]", namespaces=inkex.NSS)
for travelGroup in travelGroups:
if len(travelGroup) == 0:
travelGroup.delete()
return
#else ...
selectedPaths = [] #total list of elements to parse
def parseChildren(element):
if isinstance(element, inkex.PathElement) and element not in selectedPaths and linePrefix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
if isinstance(element, inkex.PathElement) and element not in selectedPaths and lineSuffix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
selectedPaths.append(element)
children = element.getchildren()
if children is not None:
for child in children:
if isinstance(child, inkex.PathElement) and child not in selectedPaths and linePrefix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
if isinstance(child, inkex.PathElement) and child not in selectedPaths and lineSuffix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
selectedPaths.append(child)
parseChildren(child) #go deeper and deeper
#check if we have selectedPaths elements or if we should parse the whole document instead
if len(self.svg.selected) == 0:
for element in self.document.getroot().iter(tag=etree.Element):
if isinstance(element, inkex.PathElement) and element != self.document.getroot() and linePrefix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
if isinstance(element, inkex.PathElement) and element != self.document.getroot() and lineSuffix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
selectedPaths.append(element)
else:
for element in self.svg.selected.values():
parseChildren(element)
dot_group = self.svg.add(inkex.Group())
dotGroup = self.svg.add(inkex.Group(id=self.svg.get_unique_id(dotPrefix)))
if so.order == "separate_layers":
dotGroup.set("inkscape:groupmode", "layer")
dotGroup.set("inkscape:label", dotPrefix + "layer")
if so.order == "separate_groups":
dotGroup.pop("inkscape:groupmode")
dotGroup.pop("inkscape:label")
if dotGroup.style.get('display') is not None:
dotGroup.style.pop("display") #if the group previously has been a layer (which was hidden), a display:none will be added. we don't want that
if so.arrow_style is True:
markerId = "travel_move_arrow"
#add new marker to defs (or overwrite if existent)
defs = self.svg.defs
for defi in defs:
if defi.tag == "{http://www.w3.org/2000/svg}marker" and defi.get('id') == markerId: #delete
defi.delete()
marker = inkex.Marker(id=markerId)
marker.set("inkscape:isstock", "true")
marker.set("inkscape:stockid", markerId)
marker.set("orient", "auto")
marker.set("refY", "0.0")
marker.set("refX", "0.0")
marker.set("style", "overflow:visible;")
self.createTravelMarker(markerId)
markerPath = inkex.PathElement(id=self.svg.get_unique_id('markerId-'))
markerPath.style = {"fill-rule": "evenodd", "fill": "context-stroke", "stroke-width": str(self.svg.unittouu('1px'))}
markerPath.transform = "scale(1.0,1.0) rotate(0) translate(-6.0,0)"
arrowHeight = 6.0
markerPath.attrib["transform"] = "scale({},{}) rotate(0) translate(-{},0)".format(so.arrow_size, so.arrow_size, arrowHeight)
markerPath.path = "M {},0.0 L -3.0,5.0 L -3.0,-5.0 L {},0.0 z".format(arrowHeight, arrowHeight)
marker.append(markerPath)
defs.append(marker) #do not append before letting contain it one path. Otherwise Inkscape is going to crash immediately
#collect all different stroke colors to distinguish by groups
strokeColors = []
strokeColorsAndCounts = {}
startEndPath = [] #the container for all paths we will loop on
'''
the container for all paths we will loop on. Index:
0 = element
1 = start point
2 = end point
'''
startEndPath = []
for element in selectedPaths:
strokeColor = element.style.get('stroke')
@ -112,11 +153,21 @@ class DrawDirectionsTravelMoves(inkex.EffectExtension):
groupName = groupPrefix + strokeColor
travelGroup = self.find_group(groupName)
if travelGroup is None:
self.document.getroot().append(inkex.Group(id=groupName))
travelGroup = inkex.Group(id=groupName)
self.document.getroot().append(travelGroup)
else: #exists
self.document.getroot().append(travelGroup)
for child in travelGroup:
child.delete()
if so.order == "separate_layers":
travelGroup.set("inkscape:groupmode", "layer")
travelGroup.set("inkscape:label", groupName + "-layer")
if so.order == "separate_groups":
travelGroup.pop("inkscape:groupmode")
travelGroup.pop("inkscape:label")
if travelGroup.style.get('display') is not None:
travelGroup.style.pop("display")
p = element.path.transform(element.composed_transform())
points = list(p.end_points)
@ -127,11 +178,11 @@ class DrawDirectionsTravelMoves(inkex.EffectExtension):
if so.draw_dots is True:
if start[0] == end[0] and start[1] == end[1]:
self.drawCircle(dot_group, '#00FF00', start)
self.drawCircle(dot_group, '#FFFF00', points[1]) #draw one point which gives direction of the path
self.drawCircle(dotGroup, '#00FF00', start)
self.drawCircle(dotGroup, '#FFFF00', points[1]) #draw one point which gives direction of the path
else: #open contour with start and end point
self.drawCircle(dot_group, '#FF0000', start)
self.drawCircle( dot_group, '#0000FF', end)
self.drawCircle(dotGroup, '#FF0000', start)
self.drawCircle( dotGroup, '#0000FF', end)
for strokeColor in strokeColors:
if strokeColor in strokeColorsAndCounts:
@ -149,8 +200,22 @@ class DrawDirectionsTravelMoves(inkex.EffectExtension):
for i in range(0, ran): #loop through the item selection. nested loop. so we loop over alle elements again for each color
if i < ran - 1:
elementStroke = startEndPath[i][0].style.get('stroke')
currentElement = startEndPath[i][0]
idx = currentElement.getparent().index(currentElement)
travelLineId = currentElement.get('id') + lineSuffix + ("-begin" if firstOccurence is True else "")
if i == ran or createdMoves == colorCount:
elementStroke = startEndPath[i-1][0].style.get('stroke')
currentElement = startEndPath[i-1][0]
idx = currentElement.getparent().index(currentElement) + 1
travelLineId = currentElement.get('id') + lineSuffix + "-end"
if i < ran - 2:
nextElement = startEndPath[i+1][0]
elif i < ran - 1:
nextElement = startEndPath[i][0]
else:
nextElement = None
if elementStroke is None or elementStroke == "none":
elementStroke = "none"
@ -177,34 +242,61 @@ class DrawDirectionsTravelMoves(inkex.EffectExtension):
if i == ran - 1:
inkex.utils.debug("segment={},{}".format(startEndPath[i-1][1], travelEnd))
travelLine = inkex.PathElement(id=self.svg.get_unique_id(linePrefix) + element.get('id'))
travelLine = inkex.PathElement(id=travelLineId)
#if some objects are at svg:svg level this may cause errors
#if element.getparent() != self.document.getroot():
# travelLine.transform = element.getparent().composed_transform()
travelLine.set('d', "M{:0.6f},{:0.6f} L{:0.6f},{:0.6f}".format(travelStart[0],travelStart[1],travelEnd[0],travelEnd[1]))
travelLine.style = {'stroke': ("#000000" if so.ignore_colors is True else sc), 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px')), 'marker-end': 'url(#marker1426)'}
if so.dashed_style is True:
travelLine.style['stroke-dasharray'] = '1,1'
travelLine.style['stroke-dashoffset'] = '0'
if so.arrow_style is True:
travelLine.style["marker-end"] = "url(#{})".format(markerId)
#check the length. if zero we do not add
slengths, stotal = csplength(travelLine.path.transform(element.composed_transform()).to_superpath()) #get segment lengths and total length of path in document's internal unit
if stotal > 0:
#finally add the line
travelGroup = self.find_group(groupPrefix + sc)
travelGroup.add(travelLine)
#this is a really dirty block of code
if so.order == "element_index":
#adding the lines at element index requires to apply transformations for start point and end point (in case they are in different groups)
pseudo1 = inkex.PathElement()
pseudo1.set('d', "M{:0.6f},{:0.6f}".format(travelStart[0],travelStart[1]))
pseudo2 = inkex.PathElement()
pseudo2.set('d', "M{:0.6f},{:0.6f}".format(travelEnd[0],travelEnd[1]))
if nextElement is not None:
if currentElement.getparent() == nextElement.getparent():
pseudo1.path = pseudo1.path.transform(-currentElement.composed_transform()).to_superpath()
pseudo2.path = pseudo2.path.transform(-nextElement.composed_transform()).to_superpath()
else:
pseudo1.path = pseudo1.path.transform(-currentElement.composed_transform()).to_superpath()
pseudo2.path = pseudo2.path.transform(-currentElement.composed_transform()).to_superpath()
else:
pseudo1.path = pseudo1.path.transform(-currentElement.composed_transform()).to_superpath()
pseudo2.path = pseudo2.path.transform(-currentElement.composed_transform()).to_superpath()
travelLine.path = pseudo1.path + pseudo2.get('d').replace("M", "L")
if so.debug is True: self.msg("travelLine={}".format(travelLine.path))
#check the length. if zero we do not add
slengths, stotal = csplength(travelLine.path.transform(currentElement.composed_transform()).to_superpath()) #get segment lengths and total length of path in document's internal unit
if stotal > 0:
#finally add the line
currentElement.getparent().insert(idx, travelLine)
else:
if so.debug is True: inkex.utils.debug("Line has length of zero")
else:
if so.debug is True: inkex.utils.debug("Line has length of zero")
travelLine.set('d', "M{:0.6f},{:0.6f} L{:0.6f},{:0.6f}".format(travelStart[0],travelStart[1],travelEnd[0],travelEnd[1]))
#check the length. if zero we do not add
slengths, stotal = csplength(travelLine.path.transform(currentElement.composed_transform()).to_superpath()) #get segment lengths and total length of path in document's internal unit
if stotal > 0:
#finally add the line
self.find_group(groupPrefix + sc).add(travelLine)
else:
if so.debug is True: inkex.utils.debug("Line has length of zero")
createdMoves += 1 #each time we created a move we count up. we want to compare against the total count of that color
if so.debug is True: inkex.utils.debug("createdMoves={}".format(createdMoves))
if so.debug is True: inkex.utils.debug("-"*40)
#cleanup empty groups
if len(dot_group) == 0:
dot_group.delete()
if len(dotGroup) == 0:
dotGroup.delete()
travelGroups = self.document.xpath("//svg:g[starts-with(@id, 'travelLines-')]", namespaces=inkex.NSS)
for travelGroup in travelGroups:
if len(travelGroup) == 0:

View File

@ -7,7 +7,8 @@
<label appearance="header">General Settings</label>
<param name="debug" type="bool" gui-text="Enable debug">false</param>
<param name="apply_transformations" type="bool" gui-text="Apply transformations (requires separate extension)" gui-description="This will call the extension 'Apply Transformations'. Helps avoiding geometry shifting">false</param>
<param name="breakapart" type="bool" gui-text="Break apart output path(s) into segments" gui-description="Performs CTRL + SHIFT + K to break the new output path into it's parts. Recommended to enable because default break apart of Inkscape might produce pointy paths.">true</param>
<param name="breakapart" type="bool" gui-text="Break apart selected path(s) into segments" gui-description="Performs CTRL + SHIFT + K to break paths into parts">true</param>
<param name="breakapart_total" type="bool" gui-text="Break segments to lines" gui-description="Gives the best results for nodes/&lt;interval&gt; filtering">true</param>
<hbox>
<vbox>
<label appearance="header">Threshold</label>

View File

@ -30,7 +30,8 @@ class FilterByLengthArea(inkex.EffectExtension):
pars.add_argument('--tab')
pars.add_argument('--debug', type=inkex.Boolean, default=False)
pars.add_argument("--apply_transformations", type=inkex.Boolean, default=False, help="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting")
pars.add_argument("--breakapart", type=inkex.Boolean, default=True, help="Performs CTRL + SHIFT + K to break the new output path into it's parts. Recommended to enable because default break apart of Inkscape might produce pointy paths.")
pars.add_argument("--breakapart", type=inkex.Boolean, default=True, help="Break apart selected path(s) into segments")
pars.add_argument("--breakapart_total", type=inkex.Boolean, default=True, help="Gives the best results for nodes/<interval> filtering")
pars.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Cleanup all unused groups/layers (requires separate extension)")
pars.add_argument('--unit')
pars.add_argument('--min_filter_enable', type=inkex.Boolean, default=True, help='Enable filtering min.')
@ -63,21 +64,40 @@ class FilterByLengthArea(inkex.EffectExtension):
idSuffix = 0
raw = element.path.to_arrays()
subPaths, prev = [], 0
for i in range(len(raw)): # Breaks compound paths into simple paths
if raw[i][0] == 'M' and i != 0:
subPaths.append(raw[prev:i])
prev = i
subPaths.append(raw[prev:])
if self.options.breakapart_total is False:
for i in range(len(raw)): # Breaks compound paths into simple paths
if raw[i][0] == 'M' and i != 0:
subPaths.append(raw[prev:i])
prev = i
subPaths.append(raw[prev:])
else:
rawCopy = element.path.to_arrays() #we need another set of the same path
for i in range(len(raw)): # Breaks compound paths into simple paths
if i != 0:
if raw[i][0] == 'C':
rawCopy[i][1] = [raw[i][1][-2], raw[i][1][-1]]
elif raw[i][0] == 'L':
rawCopy[i][1] = [raw[i][1][0], raw[i][1][1]]
elif raw[i][0] == 'Z': #replace Z with another L command (which moves to the coordinates of the first M command in path) to have better overview
raw[-1][0] = 'L'
raw[-1][1] = raw[0][1]
rawCopy[i][0] = 'M' #we really need M. Does not matter if 'L' or 'C'.
#self.msg("s1={},s2={}".format(rawCopy[i-1], raw[i]))
subPaths.append([rawCopy[i-1], raw[i]])
prev = i
subPaths = subPaths[::-1]
for subpath in subPaths:
#self.msg(subpath)
replacedelement = copy.copy(element)
oldId = replacedelement.get('id')
csp = CubicSuperPath(subpath)
if len(subpath) > 1 and csp[0][0] != csp[0][1]: #avoids pointy paths like M "31.4794 57.6024 Z"
replacedelement.set('d', csp)
if len(subPaths) == 1:
replacedelement.set('id', oldId)
replacedelement.set('id', "{}".format(oldId))
else:
replacedelement.set('id', oldId + str(idSuffix))
replacedelement.set('id', "{}-{}".format(oldId, str(idSuffix)))
idSuffix += 1
parent.insert(idx, replacedelement)
breakelements.append(replacedelement)
@ -116,7 +136,7 @@ class FilterByLengthArea(inkex.EffectExtension):
elements.extend(self.breakContours(element, None))
if so.debug is True:
inkex.utils.debug("Collecting elements ...")
inkex.utils.debug("Collecting svg:path elements ...")
for element in elements:
# additional option to apply transformations. As we clear up some groups to form new layers, we might lose translations, rotations, etc.
@ -229,12 +249,15 @@ class FilterByLengthArea(inkex.EffectExtension):
if so.group is True:
group.append(element)
if so.cleanup == True:
try:
import remove_empty_groups
remove_empty_groups.RemoveEmptyGroups.effect(self)
except:
self.msg("Calling 'Remove Empty Groups' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
#if len(group) == 0:
# group.delete()
if so.cleanup is True:
try:
import remove_empty_groups
remove_empty_groups.RemoveEmptyGroups.effect(self)
except:
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__':
FilterByLengthArea().run()

View File

@ -5,6 +5,7 @@
<param name="begin_str" type="string" gui-text="begin_str:">0</param>
<param name="repeat_str" type="string" gui-text="repeat_str:">indefinite</param>
<param name="dur_str" type="string" gui-text="dur_str:">9.1</param>
<param name="delete" type="bool" gui-text="Remove frames">true</param>
<effect>
<object-type>all</object-type>
<effects-menu>

View File

@ -1,5 +1,4 @@
#!/usr/bin/env python
# coding=utf-8
#!/usr/bin/env python3
#
# Copyright (C) 2021 roberta bennett repeatingshadow@protonmail.com
#
@ -18,15 +17,18 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
"""
Create svg path animation from frames.
Create svg path animation as SMIL from frames.
Place each frame of the animation in a layer named 'frame'.
Each of these layers should have the same number of paths,
and each path should have the same number of points as the corresponding
path in other layers.
Note if there are more than one path in the frames, the Z order of the paths
must match. It helps to use the XML editor option to observe the z order.
The animation is applied to the paths in the first layer in the sequence, so the
properties of that layer are used.
properties of that layer are used. In particular, the first layer ought to be set
visible.
Animations with different numbers of frames can be put into different sequences,
named 'sequence', using sub-groups:
@ -51,9 +53,9 @@ Layers:
frame
frame
Layer names must contain 'frame' and groups names contain 'sequence',
eg, frame1 frame 2 frame 30, sequence 1, rythm sequence, rocket sequence
use layer named exactly 'frame' and groups named exactly 'sequence',
not, eg, frame1 frame2 frame3 !
"""
@ -70,36 +72,52 @@ class AnimateElement(inkex.BaseElement):
class AnimationExtension(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("--delete", type=inkex.Boolean, help="Remove frames")
pars.add_argument("--begin_str", default="0", help="begin string: eg 0;an2.end;an3.begin")
pars.add_argument("--repeat_str", default="indefinite", help="indefinite or an integer")
pars.add_argument("--dur_str", default="7.9", help="duration in seconds. Do not decorate with units")
pars.add_argument("--repeat_str", default="indefinite", help="indefinite or an integer")
pars.add_argument("--dur_str", default="7.9", help="duration in seconds. Do not decorate with units")
def crunchFrames(self,frames):
if frames is None:
raise inkex.AbortExtension("layer named frame does not exist.")
frame0paths = frames[0].findall('svg:path')
Dlists = [p.get_path() for p in frame0paths]
for frame in frames[1:]:
paths = frame.findall("svg:path")
for i,p in enumerate(paths):
Dlists[i] += ";\n"+p.get_path()
for i,dl in enumerate(Dlists):
animel = AnimateElement(
attributeName="d",
attributeType="XML",
begin=self.options.begin_str,
dur=self.options.dur_str,
repeatCount=self.options.repeat_str,
values=dl)
frame0paths[i].append(animel)
for frame in frames[1:]:
if self.options.delete:
frame.delete()
return
def effect(self):
sequences = self.svg.findall("svg:g[@inkscape:label='sequence']")
if len(sequences) == 0:
raise inkex.AbortExtension("layer named sequence does not exist.")
sequences = [ elem for elem in self.svg.findall("svg:g[@inkscape:label]")
if "sequence" in (elem.get('inkscape:label'))
]
if len(sequences)==0:
frames = [ elem for elem in self.svg.findall("svg:g[@inkscape:label]")
if "frame" in (elem.get('inkscape:label'))
]
self.crunchFrames(frames)
return
for sequence in sequences:
frames = sequence.findall("svg:g[@inkscape:label='frame']")
if len(frames) == 0:
raise inkex.AbortExtension("layer named frame does not exist.")
frame0paths = frames[0].findall('svg:path')
Dlists = [p.get_path() for p in frame0paths]
for frame in frames[1:]:
paths = frame.findall("svg:path")
for i,p in enumerate(paths):
Dlists[i] += ";\n"+p.get_path()
for i,dl in enumerate(Dlists):
animel = AnimateElement(
attributeName="d",
attributeType="XML",
begin=self.options.begin_str,
dur=self.options.dur_str,
repeatCount=self.options.repeat_str,
values=dl)
frame0paths[i].append(animel)
frames = [ elem for elem in sequence.findall("svg:g[@inkscape:label]")
if "frame" in (elem.get('inkscape:label'))
]
self.crunchFrames(frames)
if __name__ == '__main__':
AnimationExtension().run()
AnimationExtension().run()

View File

@ -59,9 +59,8 @@
<option value="1016x711">1016 x 711 mm (Fusion M2 40)</option>
<option value="1219x914">1219 x 914 mm (Fusion Pro 48)</option>
</param>
<param name="max_cutting_speed" type="float" min="1.000" max="9999.000" precision="2" gui-text="Max. cutting speed (mm/s)">120</param>
<param name="max_travel_speed" type="float" min="1.000" max="9999.000" precision="2" gui-text="Max. travel speed (mm/s)">500</param>
<param name="cut_travel_factor" type="float" min="0.001" max="10.00" precision="3" gui-text="Factor between cutting/travel moves" gui-description="Usually ~60-80% are cutting time, rest is travel time. Set this factor to express the amount of travel time in relation to cutting time.">0.60</param>
<param name="max_cutting_speed" type="float" min="1.000" max="9999.000" precision="2" gui-text="Max. cutting speed (mm/s)">120.0</param>
<param name="max_travel_speed" type="float" min="1.000" max="9999.000" precision="2" gui-text="Max. travel speed (mm/s)">500.0</param>
<param name="price_per_minute_gross" type="float" min="0.0" max="9999.0" precision="2" gui-text="Price/minute € (gross)">2.0</param>
<param name="vector_grid_xy" type="float" min="0.0" max="9999.0" precision="2" gui-text="Vector grid (mm)">12.0</param>
</page>

View File

@ -13,17 +13,16 @@ class LaserCheck(inkex.EffectExtension):
'''
ToDos:
- check for elements which have the attribute "display:none" (some groups have this)
- inx:
- set speed manually or pick machine (epilog) - travel and cut speed are prefilled then
- calculate cut estimation with linear or non-linear (epiloog) speeds > select formula or like this
- calculate cut estimation with linear or non-linear (epilog) speeds > select formula or like this
- select time estimation for specific speed percentage for for all speeds (100,90, ...)
- select material (parameters -> how to???)
- select power of CO² source
- add fields for additional costs like configuring the machine or grabbing parts out of the machine (weeding), etc.
- add mode select: cut, engrave
- look for lines especially containing id "travelLines-" and sum up travel lines
- add some extra seconds for start, stop, removing parts, attaching material, ...
- maybe remove totalTravelLength and set manually ...
- Handlungsempfehlungen einbauen
- verweisen auf diverse plugins, die man nutzen kann:
- migrate ungrouper
@ -65,9 +64,8 @@ class LaserCheck(inkex.EffectExtension):
pars.add_argument('--tab')
pars.add_argument('--machine_size', default="812x508")
pars.add_argument('--max_cutting_speed', type=float, default=500)
pars.add_argument('--max_travel_speed', type=float, default=150)
pars.add_argument('--cut_travel_factor', type=float, default=0.60)
pars.add_argument('--max_cutting_speed', type=float, default=120)
pars.add_argument('--max_travel_speed', type=float, default=500)
pars.add_argument('--price_per_minute_gross', type=float, default=2.0)
pars.add_argument('--vector_grid_xy', type=float, default=12.0) #TODO
@ -572,27 +570,36 @@ class LaserCheck(inkex.EffectExtension):
if so.checks == "check_all" or so.cutting_estimation is True:
inkex.utils.debug("\n---------- Cutting time estimation (Epilog Lasers)")
totalCuttingLength = 0
pathCount = 0
totalTravelLength = 0
cuttingPathCount = 0
travelPathCount = 0
for element in shapes:
if isinstance(element, inkex.PathElement):
slengths, stotal = csplength(element.path.transform(element.composed_transform()).to_superpath())
totalCuttingLength += stotal
pathCount += 1 #each path has one start and one end (if open path) or start=end on closed path. For cutting the we calculate with 2 points because we enter and leave each path ALWAYS
totalTravelLength = totalCuttingLength * so.cut_travel_factor
if "-travelLine" in element.get('id'): #we use that id scheme together with the extension "Draw Directions / Travel Moves"
totalTravelLength += stotal
travelPathCount += 1
elif "markerId-" in element.get('id'):
pass #we skip the path "markerId-<nr>", possibly generated by the extension "Draw Directions / Travel Moves
else:
totalCuttingLength += stotal
cuttingPathCount += 1
totalLength = totalCuttingLength + totalTravelLength
inkex.utils.debug("total paths={}".format(pathCount))
inkex.utils.debug("total cutting paths={}".format(cuttingPathCount))
inkex.utils.debug("total travel paths={}".format(travelPathCount))
inkex.utils.debug("(measured) cutting length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalCuttingLength), "mm"), self.svg.uutounit(str(totalCuttingLength), "mm")))
inkex.utils.debug("(estimated) travel length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalTravelLength), "mm"), self.svg.uutounit(str(totalTravelLength), "mm")))
inkex.utils.debug("(estimated) total length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalLength), "mm"), self.svg.uutounit(str(totalLength), "mm")))
inkex.utils.debug("(measured) travel length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalTravelLength), "mm"), self.svg.uutounit(str(totalTravelLength), "mm")))
inkex.utils.debug("(measured) total length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalLength), "mm"), self.svg.uutounit(str(totalLength), "mm")))
''' from https://www.epiloglaser.com/assets/downloads/fusion-material-settings.pdf
Speed Settings: The speed setting scale of 1% to 100% is not linear
"Speed Settings: The speed setting scale of 1% to 100% is not linear
i.e. 100% speed will not be twice as fast as 50% speed. This non-linear
scale is very useful in compensating for the different factors that affect engraving time.
scale is very useful in compensating for the different factors that affect engraving time."
'''
for speedFactor in [100,90,80,70,60,50,40,30,20,10,9,8,7,6,5,4,3,2,1]:
speedFactorR = speedFactor / 100.0
adjusted_speed = 482.4000 / so.max_cutting_speed #empiric - found out by trying for hours ...
empiric_scale = 1 + (speedFactorR**2) / 19.0 #empiric - found out by trying for hours ...
adjusted_speed = 480.0 / so.max_cutting_speed #empiric - found out by trying for hours ...
empiric_scale = 1 + (speedFactorR**2) / 15.25 #empiric - found out by trying for hours ...
v_cut = so.max_cutting_speed * speedFactorR
v_travel = so.max_travel_speed #this is always at maximum
tsec_cut = (self.svg.uutounit(str(totalCuttingLength)) / (adjusted_speed * so.max_cutting_speed * speedFactorR)) * empiric_scale

View File

@ -0,0 +1,20 @@
[
{
"name": "Open Extension Directory",
"id": "fablabchemnitz.de.open_dir",
"path": "open_dir",
"dependent_extensions": null,
"original_name": "Open Extension Directory",
"original_id": "fablabchemnitz.de.open_dir",
"license": "GNU GPL v3",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/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/open_dir",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Open+Extension+Directory",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/vmario89"
]
}
]

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Open Extension Directory</name>
<id>fablabchemnitz.de.open_dir</id>
<effect needs-live-preview="false">
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz"/>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">open_dir.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
import subprocess
import os
import sys
import warnings
import inkex
DETACHED_PROCESS = 0x00000008
class OpenExtensionDirectory(inkex.EffectExtension):
def spawnIndependentProcess(self, args):
warnings.simplefilter('ignore', ResourceWarning) #suppress "enable tracemalloc to get the object allocation traceback"
if os.name == 'nt':
subprocess.Popen(args, close_fds=True, creationflags=DETACHED_PROCESS)
else:
subprocess.Popen(args, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
warnings.simplefilter("default", ResourceWarning)
def effect(self):
extension_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', '..')
if os.name == 'nt':
explorer = "explorer"
else:
explorer = "xdg-open"
args = [explorer, extension_dir]
try:
self.spawnIndependentProcess(args)
except FileNotFoundError as e:
inkex.utils.debug(e)
exit(1)
if __name__ == '__main__':
OpenExtensionDirectory().run()

View File

@ -307,7 +307,7 @@ class RobotBoxes(inkex.EffectExtension):
# Translate group
#transform = 'translate(' + str( self.svg.namedview.center[0] ) + ',' + str( self.svg.namedview.center[1] ) + ')'
g = etree.SubElement(self.svg.get_current_layer(), 'g', {inkex.addNS('label','inkscape'):'RobotBox'})
g.transform = transform
#g.transform = transform
# Create SVG Path for box bounds
style = { 'stroke': '#000000', 'fill': 'none', 'stroke-width':str(self.svg.unittouu("1px")) }