added more options to paperfold

This commit is contained in:
leyghisbb 2021-05-08 15:07:57 +02:00
parent 8616f0135f
commit 0f698fe29a
2 changed files with 133 additions and 81 deletions
extensions/fablabchemnitz

View File

@ -4,29 +4,45 @@
<id>fablabchemnitz.de.paperfold</id> <id>fablabchemnitz.de.paperfold</id>
<param name="tab" type="notebook"> <param name="tab" type="notebook">
<page name="tab_settings" gui-text="Paperfold for Inkscape"> <page name="tab_settings" gui-text="Paperfold for Inkscape">
<param name="inputfile" type="path" gui-text="Input File" filetypes="obj,off,ply,stl" mode="file" gui-description="The model to unfold. You can use obj files provided in extensions dir of Inkscape \Poly3DObjects\*.obj to play around">/your/beautiful/3dmodel/file</param> <label appearance="header">Input</label>
<param name="maxNumFaces" type="int" min="1" max="99999" gui-text="Maximum allowed faces" gui-description="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.">200</param> <param name="inputfile" type="path" gui-text="Input File" filetypes="obj,off,ply,stl" mode="file" gui-description="The model to unfold. You can use obj files provided in extensions dir of Inkscape \Poly3DObjects\*.obj to play around">/your/beautiful/3dmodel/file</param>
<param name="printNumbers" type="bool" gui-text="Print numbers on the cut edges">true</param> <param name="maxNumFaces" type="int" min="1" max="99999" gui-text="Maximum allowed faces" gui-description="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.">200</param>
<param name="printAngles" type="bool" gui-text="Print folding angles on the cut edges">true</param>
<param name="fontSize" type="int" min="1" max="100" gui-text="Label font size (%)">15</param>
<param name="scalefactor" type="float" precision="3" min="0.0001" max="10000.0" gui-text="Manual scale factor" gui-description="default is 1.0">1.0</param> <param name="scalefactor" type="float" precision="3" min="0.0001" max="10000.0" gui-text="Manual scale factor" gui-description="default is 1.0">1.0</param>
<param name="resizetoimport" type="bool" gui-text="Resize the canvas to the imported drawing's bounding box">true</param> <separator/>
<param name="extraborder" type="float" precision="3" gui-text="Add extra border around fitted canvas">0.0</param> <hbox>
<param name="extraborder_units" type="optiongroup" appearance="combo" gui-text="Border offset units"> <vbox>
<option value="mm">mm</option> <label appearance="header">Output</label>
<option value="cm">cm</option> <param name="printGluePairNumbers" type="bool" gui-text="Print glue pair numbers on cut edges">false</param>
<option value="in">in</option> <param name="printAngles" type="bool" gui-text="Print folding angles on edges">false</param>
<option value="pt">pt</option> <param name="printLengths" type="bool" gui-text="Print lengths on edges">false</param>
<option value="px">px</option> <param name="importCoplanarEdges" type="bool" gui-text="Import coplanar edges">false</param>
</param> <param name="printStats" type="bool" gui-text="Show some unfold statistics">false</param>
<param name="color_valley_cut" type="color" appearance="colorbutton" gui-text="Color for valley cuts">255</param> <param name="resizetoimport" type="bool" gui-text="Resize the canvas to the imported drawing's bounding box">true</param>
<param name="color_mountain_cut" type="color" appearance="colorbutton" gui-text="Color for mountain cuts">1968208895</param> <param name="extraborder" type="float" precision="3" gui-text="Add extra border around fitted canvas">0.0</param>
<param name="color_coplanar_lines" type="color" appearance="colorbutton" gui-text="Color for coplanar lines">1943148287</param> <param name="extraborderUnits" type="optiongroup" appearance="combo" gui-text="Border offset units">
<param name="color_valley_perforate" type="color" appearance="colorbutton" gui-text="Color for valley perforations">3422552319</param> <option value="mm">mm</option>
<param name="color_mountain_perforate" type="color" appearance="colorbutton" gui-text="Color for mountain perforations">879076607</param> <option value="cm">cm</option>
<param name="dashes" type="bool" gui-text="Dashes for cut/coplanar lines">true</param> <option value="in">in</option>
<param name="printStats" type="bool" gui-text="Show some unfold statistics">true</param> <option value="pt">pt</option>
<label appearance="header">Post Processing</label> <option value="px">px</option>
</param>
</vbox>
<separator/>
<vbox>
<label appearance="header">Style</label>
<param name="fontSize" type="int" min="1" max="100" gui-text="Label font size (%)">15</param>
<param name="flipLabels" type="bool" gui-text="Flip labels">false</param>
<param name="dashes" type="bool" gui-text="Dashes for cut/coplanar lines">true</param>
<param name="separateGluePairsByColor" type="bool" gui-text="Separate glue pairs by color">false</param>
<param name="colorValleyCut" type="color" appearance="colorbutton" gui-text="Valley cut edges">255</param>
<param name="colorMountainCut" type="color" appearance="colorbutton" gui-text="Mountain cut edges">1968208895</param>
<param name="colorCoplanarEdges" type="color" appearance="colorbutton" gui-text="Coplanar edges">1943148287</param>
<param name="colorValleyPerforates" type="color" appearance="colorbutton" gui-text="Valley perforation edges">3422552319</param>
<param name="colorMountainPerforates" type="color" appearance="colorbutton" gui-text="Mountain perforation edges">879076607</param>
</vbox>
</hbox>
<separator/>
<label appearance="header">Post Processing</label>
<label appearance="url">https://clementzheng.github.io/joinery</label> <label appearance="url">https://clementzheng.github.io/joinery</label>
</page> </page>
<page name="tab_about" gui-text="About"> <page name="tab_about" gui-text="About">

View File

@ -4,6 +4,7 @@ import math
import inkex import inkex
import tempfile import tempfile
import os import os
import random
import numpy as np import numpy as np
import openmesh as om import openmesh as om
import networkx as nx 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 Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org Mail: mario.voigt@stadtfabrikanten.org
Date: 13.09.2020 Date: 13.09.2020
Last patch: 06.05.2021 Last patch: 08.05.2021
License: GNU GPL v3 License: GNU GPL v3
To run this you need to install OpenMesh with python pip. 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 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 # 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] return [xmin, ymin, boxSize]
def writeSVG(self, unfolding, size, printNumbers): def writeSVG(self, unfolding, size):
mesh = unfolding[0] mesh = unfolding[0]
isFoldingEdge = unfolding[1] isFoldingEdge = unfolding[1]
glueNumber = unfolding[3] glueNumber = unfolding[3]
@ -447,6 +444,15 @@ def writeSVG(self, unfolding, size, printNumbers):
# Generate a main group # Generate a main group
paperfoldPageGroup = self.document.getroot().add(inkex.Group(id=self.svg.get_unique_id("paperfold-page-"))) 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 # Go over all edges of the grid
for edge in mesh.edges(): for edge in mesh.edges():
# The two endpoints # The two endpoints
@ -463,16 +469,18 @@ def writeSVG(self, unfolding, size, printNumbers):
dihedralAngle = foldingDirection[edge.idx()] dihedralAngle = foldingDirection[edge.idx()]
if dihedralAngle > 0: 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-")) line.set("id", self.svg.get_unique_id("mountain-cut-"))
mountainCuts += 1 mountainCuts += 1
elif dihedralAngle < 0: 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-")) line.set("id", self.svg.get_unique_id("valley-cut-"))
valleyCuts += 1 valleyCuts += 1
elif dihedralAngle == 0: 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-")) line.set("id", self.svg.get_unique_id("coplanar-line-"))
if self.options.importCoplanarEdges is False:
line.delete()
coplanarLines += 1 coplanarLines += 1
lineStyle.update({"stroke-width":str(strokewidth)}) lineStyle.update({"stroke-width":str(strokewidth)})
@ -485,55 +493,73 @@ def writeSVG(self, unfolding, size, printNumbers):
if self.options.dashes is True: if self.options.dashes is True:
lineStyle.update({"stroke-dasharray":(str(dashLength) + ", " + str(spaceLength))}) lineStyle.update({"stroke-dasharray":(str(dashLength) + ", " + str(spaceLength))})
if dihedralAngle > 0: 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-")) line.set("id", self.svg.get_unique_id("mountain-perforate-"))
mountainPerforations += 1 mountainPerforations += 1
if dihedralAngle < 0: 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-")) line.set("id", self.svg.get_unique_id("valley-perforate-"))
valleyPerforations += 1 valleyPerforations += 1
else: else:
lineStyle.update({"stroke-dasharray":"none"}) 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-dashoffset":"0"})
lineStyle.update({"stroke-opacity":"1"}) lineStyle.update({"stroke-opacity":"1"})
line.style = lineStyle 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 text = paperfoldPageGroup.add(TextElement(id=self.svg.get_unique_id("number-")))
if not isFoldingEdge[edge.idx()]: text.set("x", str(position[0]))
# Find halfedge in the face text.set("y", str(position[1]))
halfEdge = mesh.halfedge_handle(edge, 0) text.set("font-size", str(fontsize))
if mesh.face_handle(halfEdge).idx() == -1: text.set("style", "stroke-width:" + str(textStrokewidth) + ";text-anchor:middle;text-align:center")
halfEdge = mesh.opposite_halfedge_handle(halfEdge) text.set("transform", "rotate(" + str(rotation) + "," + str(position[0]) + "," + str(position[1]) + ")")
vector = mesh.calc_edge_vector(halfEdge)
# normalize tspan = text.add(Tspan())
vector = vector / np.linalg.norm(vector) tspan.set("x", str(position[0]))
midPoint = 0.5 * ( tspan.set("y", str(position[1]))
mesh.point(mesh.from_vertex_handle(halfEdge)) + mesh.point(mesh.to_vertex_handle(halfEdge))) tspan.set("style", "stroke-width:" + str(textStrokewidth) + ";text-anchor:middle;text-align:center")
rotatedVector = np.array([-vector[1], vector[0], 0]) tspanText = []
angle = np.arctan2(vector[1], vector[0]) if self.options.printGluePairNumbers is True and not isFoldingEdge[edge.idx()]:
position = midPoint + textDistance * rotatedVector tspanText.append(str(glueNumber[edge.idx()]))
rotation = 180 / np.pi * angle 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): 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 = paperfoldPageGroup.add(TextElement(id=self.svg.get_unique_id("number-"))) text.delete()
text.set("x", str(position[0])) tspan.delete()
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.printStats is True: if self.options.printStats is True:
inkex.utils.debug("Folding edges stats:") inkex.utils.debug("Folding edges stats:")
inkex.utils.debug(" * Number of mountain cuts: " + str(mountainCuts)) 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 mountain perforations: " + str(mountainPerforations))
inkex.utils.debug(" * Number of valley perforations: " + str(valleyPerforations)) inkex.utils.debug(" * Number of valley perforations: " + str(valleyPerforations))
inkex.utils.debug("-----------------------------------------------------------") 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 return paperfoldPageGroup
@ -550,22 +576,32 @@ class Unfold(inkex.EffectExtension):
def add_arguments(self, pars): def add_arguments(self, pars):
pars.add_argument("--tab") pars.add_argument("--tab")
#Input
pars.add_argument("--inputfile") 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("--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") 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("--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", type=float, default=0.0)
pars.add_argument("--extraborder_units") pars.add_argument("--extraborderUnits")
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") #Style
pars.add_argument("--color_coplanar_lines", type=Color, default='1943148287', help="Color for coplanar lines") pars.add_argument("--fontSize", type=int, default=15, help="Label font size (%)")
pars.add_argument("--color_valley_perforate", type=Color, default='3422552319', help="Color for valley perforations") pars.add_argument("--flipLabels", type=inkex.Boolean, default=False, help="Flip labels")
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 edges")
pars.add_argument("--dashes", type=inkex.Boolean, default=True, help="Dashes for cut/coplanar lines") pars.add_argument("--separateGluePairsByColor", type=inkex.Boolean, default=False, help="Separate glue pairs by color")
pars.add_argument("--printStats", type=inkex.Boolean, default=True, help="Show some unfold statistics") 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): def effect(self):
if not os.path.exists(self.options.inputfile): 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 # 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 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)): 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 #translate the groups next to each other to remove overlappings
if i != 0: if i != 0:
previous_bbox = paperfoldMainGroup[i-1].bounding_box() previous_bbox = paperfoldMainGroup[i-1].bounding_box()
@ -606,7 +642,7 @@ class Unfold(inkex.EffectExtension):
namedView = self.document.getroot().find(inkex.addNS('namedview', 'sodipodi')) namedView = self.document.getroot().find(inkex.addNS('namedview', 'sodipodi'))
doc_units = namedView.get(inkex.addNS('document-units', 'inkscape')) doc_units = namedView.get(inkex.addNS('document-units', 'inkscape'))
root = self.svg.getElement('//svg:svg'); 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('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('width', str(bbox.width + 2 * offset) + doc_units)
root.set('height', str(bbox.height + 2 * offset) + doc_units) root.set('height', str(bbox.height + 2 * offset) + doc_units)