diff --git a/extensions/fablabchemnitz/paperfold.inx b/extensions/fablabchemnitz/paperfold.inx
index d18b7a7a..492ea097 100644
--- a/extensions/fablabchemnitz/paperfold.inx
+++ b/extensions/fablabchemnitz/paperfold.inx
@@ -4,29 +4,45 @@
fablabchemnitz.de.paperfold
- /your/beautiful/3dmodel/file
- 200
- true
- true
- 15
+
+ /your/beautiful/3dmodel/file
+ 200
1.0
- true
- 0.0
-
-
-
-
-
-
-
- 255
- 1968208895
- 1943148287
- 3422552319
- 879076607
- true
- true
-
+
+
+
+
+ false
+ false
+ false
+ false
+ false
+ true
+ 0.0
+
+
+
+
+
+
+
+
+
+
+
+ 15
+ false
+ true
+ false
+ 255
+ 1968208895
+ 1943148287
+ 3422552319
+ 879076607
+
+
+
+
diff --git a/extensions/fablabchemnitz/paperfold.py b/extensions/fablabchemnitz/paperfold.py
index c795e157..7cb8b1d8 100644
--- a/extensions/fablabchemnitz/paperfold.py
+++ b/extensions/fablabchemnitz/paperfold.py
@@ -4,6 +4,7 @@ import math
import inkex
import tempfile
import os
+import random
import numpy as np
import openmesh as om
import networkx as nx
@@ -18,7 +19,7 @@ Paperfold is another flattener for triangle mesh files, heavily based on paperfo
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 13.09.2020
-Last patch: 06.05.2021
+Last patch: 08.05.2021
License: GNU GPL v3
To run this you need to install OpenMesh with python pip.
@@ -35,10 +36,6 @@ Module licenses
possible import file types -> https://www.graphics.rwth-aachen.de/media/openmesh_static/Documentations/OpenMesh-8.0-Documentation/a04096.html
-ToDos:
-- Add glue tabs
-- Print statistics about groups
-
"""
# Compute the third point of a triangle when two points and all edge lengths are given
@@ -417,7 +414,7 @@ def findBoundingBox(mesh):
return [xmin, ymin, boxSize]
-def writeSVG(self, unfolding, size, printNumbers):
+def writeSVG(self, unfolding, size):
mesh = unfolding[0]
isFoldingEdge = unfolding[1]
glueNumber = unfolding[3]
@@ -447,6 +444,15 @@ def writeSVG(self, unfolding, size, printNumbers):
# Generate a main group
paperfoldPageGroup = self.document.getroot().add(inkex.Group(id=self.svg.get_unique_id("paperfold-page-")))
+ #generate random colors for glue pairs
+ randomColorSet = []
+ if self.options.separateGluePairsByColor:
+ while len(randomColorSet) < len(mesh.edges()):
+ r = lambda: random.randint(0,255)
+ newColor = '#%02X%02X%02X' % (r(),r(),r())
+ if newColor not in randomColorSet:
+ randomColorSet.append(newColor)
+
# Go over all edges of the grid
for edge in mesh.edges():
# The two endpoints
@@ -463,16 +469,18 @@ def writeSVG(self, unfolding, size, printNumbers):
dihedralAngle = foldingDirection[edge.idx()]
if dihedralAngle > 0:
- lineStyle.update({"stroke": self.options.color_mountain_cut})
+ lineStyle.update({"stroke": self.options.colorMountainCut})
line.set("id", self.svg.get_unique_id("mountain-cut-"))
mountainCuts += 1
elif dihedralAngle < 0:
- lineStyle.update({"stroke": self.options.color_valley_cut})
+ lineStyle.update({"stroke": self.options.colorValleyCut})
line.set("id", self.svg.get_unique_id("valley-cut-"))
valleyCuts += 1
elif dihedralAngle == 0:
- lineStyle.update({"stroke": self.options.color_coplanar_lines})
+ lineStyle.update({"stroke": self.options.colorCoplanarEdges})
line.set("id", self.svg.get_unique_id("coplanar-line-"))
+ if self.options.importCoplanarEdges is False:
+ line.delete()
coplanarLines += 1
lineStyle.update({"stroke-width":str(strokewidth)})
@@ -485,55 +493,73 @@ def writeSVG(self, unfolding, size, printNumbers):
if self.options.dashes is True:
lineStyle.update({"stroke-dasharray":(str(dashLength) + ", " + str(spaceLength))})
if dihedralAngle > 0:
- lineStyle.update({"stroke": self.options.color_mountain_perforate})
+ lineStyle.update({"stroke": self.options.colorMountainPerforates})
line.set("id", self.svg.get_unique_id("mountain-perforate-"))
mountainPerforations += 1
if dihedralAngle < 0:
- lineStyle.update({"stroke": self.options.color_valley_perforate})
+ lineStyle.update({"stroke": self.options.colorValleyPerforates})
line.set("id", self.svg.get_unique_id("valley-perforate-"))
valleyPerforations += 1
else:
lineStyle.update({"stroke-dasharray":"none"})
-
+ # The number of the edge to be glued
+ if not isFoldingEdge[edge.idx()]:
+ if self.options.separateGluePairsByColor is True:
+ lineStyle.update({"stroke": randomColorSet[glueNumber[edge.idx()]]})
+ gluePairs += 1
+
lineStyle.update({"stroke-dashoffset":"0"})
lineStyle.update({"stroke-opacity":"1"})
line.style = lineStyle
+
+ # Textual things
+ halfEdge = mesh.halfedge_handle(edge, 0) # Find halfedge in the face
+ if mesh.face_handle(halfEdge).idx() == -1:
+ halfEdge = mesh.opposite_halfedge_handle(halfEdge)
+ vector = mesh.calc_edge_vector(halfEdge)
+ # normalize
+ vector = vector / np.linalg.norm(vector)
+ midPoint = 0.5 * (
+ mesh.point(mesh.from_vertex_handle(halfEdge)) + mesh.point(mesh.to_vertex_handle(halfEdge)))
+ rotatedVector = np.array([-vector[1], vector[0], 0])
+ angle = np.arctan2(vector[1], vector[0])
+ position = midPoint + textDistance * rotatedVector
+ if self.options.flipLabels is True:
+ position = midPoint - textDistance * rotatedVector
+ rotation = 180 / np.pi * angle
+ if self.options.flipLabels is True:
+ rotation += 180
- # The number of the edge to be glued
- if not isFoldingEdge[edge.idx()]:
- # Find halfedge in the face
- halfEdge = mesh.halfedge_handle(edge, 0)
- if mesh.face_handle(halfEdge).idx() == -1:
- halfEdge = mesh.opposite_halfedge_handle(halfEdge)
- vector = mesh.calc_edge_vector(halfEdge)
- # normalize
- vector = vector / np.linalg.norm(vector)
- midPoint = 0.5 * (
- mesh.point(mesh.from_vertex_handle(halfEdge)) + mesh.point(mesh.to_vertex_handle(halfEdge)))
- rotatedVector = np.array([-vector[1], vector[0], 0])
- angle = np.arctan2(vector[1], vector[0])
- position = midPoint + textDistance * rotatedVector
- rotation = 180 / np.pi * angle
+ text = paperfoldPageGroup.add(TextElement(id=self.svg.get_unique_id("number-")))
+ text.set("x", str(position[0]))
+ text.set("y", str(position[1]))
+ text.set("font-size", str(fontsize))
+ text.set("style", "stroke-width:" + str(textStrokewidth) + ";text-anchor:middle;text-align:center")
+ text.set("transform", "rotate(" + str(rotation) + "," + str(position[0]) + "," + str(position[1]) + ")")
+
+ tspan = text.add(Tspan())
+ tspan.set("x", str(position[0]))
+ tspan.set("y", str(position[1]))
+ tspan.set("style", "stroke-width:" + str(textStrokewidth) + ";text-anchor:middle;text-align:center")
+ tspanText = []
+ if self.options.printGluePairNumbers is True and not isFoldingEdge[edge.idx()]:
+ tspanText.append(str(glueNumber[edge.idx()]))
+ if self.options.printAngles is True:
+ tspanText.append("{:0.2f}°".format(dihedralAngle))
+ if self.options.printLengths is True:
+ printUnit = True
+ if printUnit is False:
+ unitToPrint = self.svg.unit
+ else:
+ unitToPrint = ""
+ tspanText.append("{:0.2f} {}".format(self.options.scalefactor * math.hypot(vertex1[0] - vertex0[0], vertex1[1] - vertex0[1]), unitToPrint))
+ tspan.text = " | ".join(tspanText)
- if (printNumbers):
- text = paperfoldPageGroup.add(TextElement(id=self.svg.get_unique_id("number-")))
- text.set("x", str(position[0]))
- text.set("y", str(position[1]))
- text.set("font-size", str(fontsize))
- text.set("style", "stroke-width:" + str(textStrokewidth) + ";text-anchor:middle;text-align:center")
- text.set("transform", "rotate(" + str(rotation) + "," + str(position[0]) + "," + str(position[1]) + ")")
-
- tspan = text.add(Tspan())
- tspan.set("x", str(position[0]))
- tspan.set("y", str(position[1]))
- tspan.set("style", "stroke-width:" + str(textStrokewidth) + ";text-anchor:middle;text-align:center")
- tspan.text = str(glueNumber[edge.idx()])
- if self.options.printAngles is True:
- tspan.text += " ({:0.2f}°)".format(dihedralAngle)
-
- gluePairs += 1
-
+ if (self.options.printGluePairNumbers is False and self.options.printAngles is False and self.options.printLengths is False) or self.options.importCoplanarEdges is False and dihedralAngle == 0:
+ text.delete()
+ tspan.delete()
+
if self.options.printStats is True:
inkex.utils.debug("Folding edges stats:")
inkex.utils.debug(" * Number of mountain cuts: " + str(mountainCuts))
@@ -542,7 +568,7 @@ def writeSVG(self, unfolding, size, printNumbers):
inkex.utils.debug(" * Number of mountain perforations: " + str(mountainPerforations))
inkex.utils.debug(" * Number of valley perforations: " + str(valleyPerforations))
inkex.utils.debug("-----------------------------------------------------------")
- inkex.utils.debug("Number of glue pairs: " + str(gluePairs))
+ inkex.utils.debug("Number of glue pairs: {:0.0f}".format(gluePairs / 2))
return paperfoldPageGroup
@@ -550,22 +576,32 @@ class Unfold(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("--tab")
+
+ #Input
pars.add_argument("--inputfile")
pars.add_argument("--maxNumFaces", type=int, default=200, help="If the STL file has too much detail it contains a large number of faces. This will make unfolding extremely slow. So we can limit it.")
- pars.add_argument("--printNumbers", type=inkex.Boolean, default=False, help="Print numbers on the cut edges")
- pars.add_argument("--printAngles", type=inkex.Boolean, default=False, help="Print folding angles on the cut edges")
- pars.add_argument("--fontSize", type=int, default=15, help="Label font size (%)")
pars.add_argument("--scalefactor", type=float, default=1.0, help="Manual scale factor")
+
+ #Output
+ pars.add_argument("--printGluePairNumbers", type=inkex.Boolean, default=False, help="Print glue pair numbers on cut edges")
+ pars.add_argument("--printAngles", type=inkex.Boolean, default=False, help="Print folding angles on edges")
+ pars.add_argument("--printLengths", type=inkex.Boolean, default=False, help="Print elengths on edges")
+ pars.add_argument("--importCoplanarEdges", type=inkex.Boolean, default=False, help="Import coplanar edges")
+ pars.add_argument("--printStats", type=inkex.Boolean, default=True, help="Show some unfold statistics")
pars.add_argument("--resizetoimport", type=inkex.Boolean, default=True, help="Resize the canvas to the imported drawing's bounding box")
pars.add_argument("--extraborder", type=float, default=0.0)
- pars.add_argument("--extraborder_units")
- pars.add_argument("--color_valley_cut", type=Color, default='255', help="Color for valley cuts")
- pars.add_argument("--color_mountain_cut", type=Color, default='1968208895', help="Color for mountain cuts")
- pars.add_argument("--color_coplanar_lines", type=Color, default='1943148287', help="Color for coplanar lines")
- pars.add_argument("--color_valley_perforate", type=Color, default='3422552319', help="Color for valley perforations")
- pars.add_argument("--color_mountain_perforate", type=Color, default='879076607', help="Color for mountain perforations")
- pars.add_argument("--dashes", type=inkex.Boolean, default=True, help="Dashes for cut/coplanar lines")
- pars.add_argument("--printStats", type=inkex.Boolean, default=True, help="Show some unfold statistics")
+ pars.add_argument("--extraborderUnits")
+
+ #Style
+ pars.add_argument("--fontSize", type=int, default=15, help="Label font size (%)")
+ pars.add_argument("--flipLabels", type=inkex.Boolean, default=False, help="Flip labels")
+ pars.add_argument("--dashes", type=inkex.Boolean, default=True, help="Dashes for cut/coplanar edges")
+ pars.add_argument("--separateGluePairsByColor", type=inkex.Boolean, default=False, help="Separate glue pairs by color")
+ pars.add_argument("--colorValleyCut", type=Color, default='255', help="Valley cut edges")
+ pars.add_argument("--colorMountainCut", type=Color, default='1968208895', help="Mountain cut edges")
+ pars.add_argument("--colorCoplanarEdges", type=Color, default='1943148287', help="Coplanar edges")
+ pars.add_argument("--colorValleyPerforates", type=Color, default='3422552319', help="Valley perforation edges")
+ pars.add_argument("--colorMountainPerforates", type=Color, default='879076607', help="Mountain perforation edges")
def effect(self):
if not os.path.exists(self.options.inputfile):
@@ -586,7 +622,7 @@ class Unfold(inkex.EffectExtension):
# Create a new container group to attach all paperfolds
paperfoldMainGroup = self.document.getroot().add(inkex.Group(id=self.svg.get_unique_id("paperfold-"))) #make a new group at root level
for i in range(len(unfoldedComponents)):
- paperfoldPageGroup = writeSVG(self, unfoldedComponents[i], maxSize, self.options.printNumbers)
+ paperfoldPageGroup = writeSVG(self, unfoldedComponents[i], maxSize)
#translate the groups next to each other to remove overlappings
if i != 0:
previous_bbox = paperfoldMainGroup[i-1].bounding_box()
@@ -606,7 +642,7 @@ class Unfold(inkex.EffectExtension):
namedView = self.document.getroot().find(inkex.addNS('namedview', 'sodipodi'))
doc_units = namedView.get(inkex.addNS('document-units', 'inkscape'))
root = self.svg.getElement('//svg:svg');
- offset = self.svg.unittouu(str(self.options.extraborder) + self.options.extraborder_units)
+ offset = self.svg.unittouu(str(self.options.extraborder) + self.options.extraborderUnits)
root.set('viewBox', '%f %f %f %f' % (bbox.left - offset, bbox.top - offset, bbox.width + 2 * offset, bbox.height + 2 * offset))
root.set('width', str(bbox.width + 2 * offset) + doc_units)
root.set('height', str(bbox.height + 2 * offset) + doc_units)