added functions to render travel moves

This commit is contained in:
Mario Voigt 2021-11-08 13:48:02 +01:00
parent 2787147b23
commit 84ba33f2de
7 changed files with 280 additions and 56 deletions

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Draw Directions</name>
<id>fablabchemnitz.de.draw_directions</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Shape/Pattern from existing Path(s)"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">draw_directions.py</command>
</script>
</inkscape-extension>

View File

@ -1,33 +0,0 @@
#!/usr/bin/env python3
import inkex
from inkex.paths import Path
from inkex import Circle
class DrawDirections(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(self.options.dotsize/2) + "px"))))
startCircle.style = style
def add_arguments(self, pars):
pars.add_argument("--dotsize", type=int, default=10, help="Dot size (px) for self-intersecting points")
def effect(self):
dot_group = self.svg.add(inkex.Group())
for node in self.svg.selection.filter(inkex.PathElement).values():
points = list(node.path.end_points)
start = points[0]
end = points[len(points) - 1]
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
else: #open contour with start and end point
self.drawCircle(dot_group, '#FF0000', start)
self.drawCircle(dot_group, '#0000FF', end)
if __name__ == '__main__':
DrawDirections().run()

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<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">
<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/>
<param name="draw_travel_moves" type="bool" gui-text="Draw travel moves">false</param>
<param name="ignore_colors" type="bool" gui-text="Ignore stroke colors" gui-description="If enabled we connect all lines by order, ignoring the stroke color (normally we have one travel line group per color)">false</param>
<param name="dashed_style" type="bool" gui-text="Dashed style">true</param>
<param name="arrow_style" type="bool" gui-text="Arrow style">true</param>
<param name="arrow_size" type="float" min="0.1" max="10.0" precision="2" gui-text="Arrow size (scale)">1.0</param>
<separator/>
<param name="debug" type="bool" gui-text="Print debug info">false</param>
</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>2021 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
<spacer/>
<label appearance="header">Online Documentation</label>
<label appearance="url">https://y.stadtfabrikanten.org/drawdirectionstravelmoves</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X</label>
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</label>
<spacer/>
<label appearance="header">MightyScape Extension Collection</label>
<label>This piece of software is part of the MightyScape for Inkscape Extension Collection and is licensed under GNU GPL v3</label>
<label appearance="url">https://y.stadtfabrikanten.org/mightyscape-overview</label>
</page>
<page name="tab_donate" gui-text="Donate">
<label appearance="header">Coffee + Pizza</label>
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
<spacer/>
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
<spacer/>
<label>Thanks for using our extension and helping us!</label>
<image>../000_about_fablabchemnitz.svg</image>
</page>
</param>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Shape/Pattern from existing Path(s)"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">draw_directions_tavel_moves.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,214 @@
#!/usr/bin/env python3
from lxml import etree
import itertools
import inkex
from inkex import Circle, Vector2d
from inkex.paths import Path
from inkex.bezier import csplength
class DrawDirectionsTravelMoves(inkex.EffectExtension):
def drawCircle(self, transform, 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
startCircle.transform = transform
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)
for group in groups:
#inkex.utils.debug(str(layer.get('inkscape:label')) + " == " + layerName)
if group.get('id') == groupId:
return group
return None
def add_arguments(self, pars):
pars.add_argument("--nb_main")
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)
pars.add_argument("--ignore_colors", type=inkex.Boolean, default=False, help="If enabled we connect all lines by order, ignoring the stroke color (normally we have one travel line group per color)")
pars.add_argument("--dashed_style", type=inkex.Boolean, default=True)
pars.add_argument("--arrow_style", type=inkex.Boolean, default=True)
pars.add_argument("--arrow_size", type=float, default=True)
pars.add_argument("--debug", type=inkex.Boolean, default=False)
def effect(self):
global so
so = self.options
linePrefix = "travelLine-"
groupPrefix = "travelLines-"
ignoreWord = "ignore"
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):
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):
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):
selectedPaths.append(element)
else:
for element in self.svg.selected.values():
parseChildren(element)
dot_group = self.svg.add(inkex.Group())
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;")
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 = {}
for element in selectedPaths:
strokeColor = element.style.get('stroke')
if strokeColor is None or strokeColor == "none":
strokeColor = "none"
if so.ignore_colors is True:
strokeColor = ignoreWord
strokeColors.append(strokeColor)
groupName = groupPrefix + strokeColor
travelGroup = self.find_group(groupName)
if travelGroup is None:
self.document.getroot().append(inkex.Group(id=groupName))
else: #exists
self.document.getroot().append(travelGroup)
for child in travelGroup:
child.delete()
for strokeColor in strokeColors:
if strokeColor in strokeColorsAndCounts:
strokeColorsAndCounts[strokeColor] = strokeColorsAndCounts[strokeColor] + 1
else:
strokeColorsAndCounts[strokeColor] = 1
startEndPath = [] #the container for all paths we will loop on
for element in selectedPaths:
#points = list(element.path.end_points)
p = element.path.transform(element.composed_transform())
points = list(p.end_points)
start = points[0]
end = points[len(points) - 1]
startEndPath.append([element, start, end])
#if element.getparent() != self.document.getroot():
# transform = element.getparent().composed_transform()
#else:
# transform = None
transform = None
if so.draw_dots is True:
if start[0] == end[0] and start[1] == end[1]:
self.drawCircle(transform, dot_group, '#00FF00', start)
self.drawCircle(transform, dot_group, '#FFFF00', points[1]) #draw one point which gives direction of the path
else: #open contour with start and end point
self.drawCircle(transform, dot_group, '#FF0000', start)
self.drawCircle(transform, dot_group, '#0000FF', end)
if so.draw_travel_moves is True:
for sc in strokeColorsAndCounts: #loop through all unique stroke colors
colorCount = strokeColorsAndCounts[sc] #the total color count per stroke color
ran = len(startEndPath) + 1 #one more because the last travel moves goes back to 0,0 (so for 3 elements (1,2,3) the range is 0 to 3 -> makes 4)
firstOccurence = True
createdMoves = 0
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')
if i == ran or createdMoves == colorCount:
elementStroke = startEndPath[i-1][0].style.get('stroke')
if elementStroke is None or elementStroke == "none":
elementStroke = "none"
if so.debug is True: inkex.utils.debug("current stroke color {}, element stroke color{}".format(sc, elementStroke))
if sc == elementStroke or sc == ignoreWord:
if firstOccurence is True:
travelStart = Vector2d(0,0)
firstOccurence = False
else:
if i <= ran - 1:
travelStart = startEndPath[i-1][2] #end point from this path
if so.debug is True: inkex.utils.debug("travelStart={}".format(travelStart))
if i < ran - 1:
travelEnd = startEndPath[i][1]
if createdMoves == colorCount:
travelEnd = Vector2d(0,0)
if so.debug is True: inkex.utils.debug("travelEnd={}".format(travelEnd))
if so.debug is True:
if i < ran - 1:
inkex.utils.debug("segment={},{}".format(startEndPath[i][2], startEndPath[i][1]))
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'))
#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)
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()
travelGroups = self.document.xpath("//svg:g[starts-with(@id, 'travelLines-')]", namespaces=inkex.NSS)
for travelGroup in travelGroups:
if len(travelGroup) == 0:
travelGroup.delete()
if __name__ == '__main__':
DrawDirectionsTravelMoves().run()

View File

@ -1,17 +1,17 @@
[
{
"name": "Draw Directions",
"id": "fablabchemnitz.de.draw_directions",
"name": "Draw Directions / Travel Moves",
"id": "fablabchemnitz.de.draw_directions_travel_moces",
"path": "draw_directions",
"dependent_extensions": null,
"original_name": "Draw Directions",
"original_id": "fablabchemnitz.de.draw_directions",
"original_name": "Draw Directions / Travel Moves",
"original_id": "fablabchemnitz.de.draw_directions_travel_moces",
"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/draw_directions",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Draw+Directions",
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=120524682",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/vmario89"

View File

@ -13,6 +13,7 @@ class LaserCheck(inkex.EffectExtension):
'''
ToDos:
- 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
@ -36,7 +37,9 @@ class LaserCheck(inkex.EffectExtension):
- statistics
- export as PDF
- run as script to generate quick results for users
- check for old styles which should be upgraded
- check for old styles which should be upgraded (cleanup styles tool)
- check for elements which have no style attribute (should be created) -> (cleanup styles tool)
- migrate styles from groups/layers to path styles (cleanup styles tool)
- self-intersecting paths
- number of parts (isles) to weed in total
- number of parts which are smaller than vector grid