huge update for paperfold
This commit is contained in:
parent
8c8614e4f8
commit
9d6d01f845
@ -75,7 +75,7 @@ class ConvexHull(inkex.EffectExtension):
|
||||
line_attribs['transform'] = cloneTransform
|
||||
etree.SubElement(g, inkex.addNS('path', 'svg' ), line_attribs)
|
||||
|
||||
def getControlPoints(self, element, n_array = None): #this does the same as "CTRL + SHIFT + K"
|
||||
def getControlPoints(self, element, n_array = None):
|
||||
if n_array == None:
|
||||
n_array = []
|
||||
if element.tag == inkex.addNS('path','svg'):
|
||||
|
@ -8,6 +8,7 @@
|
||||
<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="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="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="roundingDigits" type="int" min="0" max="16" gui-text="Digits for rounding" gui-description="Controls how (nearly) coplanar lines are handled.">3</param>
|
||||
<separator/>
|
||||
<hbox>
|
||||
<vbox>
|
||||
@ -27,6 +28,8 @@
|
||||
<option value="pt">pt</option>
|
||||
<option value="px">px</option>
|
||||
</param>
|
||||
<param name="writeTwoDSTL" type="bool" gui-text="Write 2D STL unfoldings">false</param>
|
||||
<param name="TwoDSTLdir" type="path" mode="folder" gui-text="Location to save exported 2D STL">./inkscape_export/</param>
|
||||
</vbox>
|
||||
<separator/>
|
||||
<vbox>
|
||||
@ -35,7 +38,7 @@
|
||||
<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="saturationsForAngles" type="bool" gui-text="Adjust color saturation for folding edges" gui-description="The larger the angle the darker the color">false</param>
|
||||
<param name="separateGluePairsByColor" type="bool" gui-text="Separate glue pairs by color">false</param>
|
||||
<param name="separateGluePairsByColor" type="bool" gui-text="Separate glue tab pairs by color" gui-description="Generates random color sets for glue tab pairs">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>
|
||||
@ -43,9 +46,17 @@
|
||||
<param name="colorMountainPerforates" type="color" appearance="colorbutton" gui-text="Mountain perforation edges">879076607</param>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<separator/>
|
||||
<label appearance="header">Post Processing</label>
|
||||
</page>
|
||||
<page name="tab_postprocessing" gui-text="Post Processing">
|
||||
<label appearance="header">Joinery</label>
|
||||
<label>Joinery only works on ungrouped paths.</label>
|
||||
<param name="joineryMode" type="bool" gui-text="Enable joinery mode" gui-description="Makes flat file instead creating groups. Guarantees compability for joinery.">false</param>
|
||||
<label appearance="url">https://clementzheng.github.io/joinery</label>
|
||||
<label appearance="url">https://www.instructables.com/Joinery-Joints-for-Laser-Cut-Assemblies</label>
|
||||
<label appearance="header">Origami Simulator</label>
|
||||
<label appearance="url">https://origamisimulator.org</label>
|
||||
<label appearance="url">https://github.com/amandaghassaei/OrigamiSimulator</label>
|
||||
<param name="origamiSimulatorMode" type="bool" gui-text="Enable origami simulator mode" gui-description="Overwrites styles to be compatible to origami simulator.">false</param>
|
||||
</page>
|
||||
<page name="tab_about" gui-text="About">
|
||||
<label appearance="header">Paperfold for Inkscape</label>
|
||||
|
@ -19,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: 08.05.2021
|
||||
Last patch: 10.05.2021
|
||||
License: GNU GPL v3
|
||||
|
||||
To run this you need to install OpenMesh with python pip.
|
||||
@ -38,17 +38,18 @@ possible import file types -> https://www.graphics.rwth-aachen.de/media/openmesh
|
||||
|
||||
todo:
|
||||
- debug coplanar color for edges for some cases
|
||||
- remove empty groups (text)
|
||||
- abort if 0 faces
|
||||
- give hints for joinery preparations (apply transform, ungroup, ...)
|
||||
- update documentation accordingly
|
||||
- make angleRange global for complete unfolding (to match glue pairs between multiple unfoldings)
|
||||
- add angleRange to stats
|
||||
|
||||
- option to render all triangles in a detached way (overlapping lines/independent) + merge coplanar adjacent triangles to polygons
|
||||
- write tab and slot generator (like joinery/polyhedra extension)
|
||||
- fstl preview
|
||||
- origami simulator docu + add support for opacities
|
||||
- fix line: dualGraph.add_edge(face1.idx(), face2.idx(), idx=edge.idx(), weight=edgeweight) # #might fail without throwing any error ...
|
||||
"""
|
||||
|
||||
# Compute the third point of a triangle when two points and all edge lengths are given
|
||||
def getThirdPoint(v0, v1, l01, l12, l20):
|
||||
class Unfold(inkex.EffectExtension):
|
||||
|
||||
# Compute the third point of a triangle when two points and all edge lengths are given
|
||||
def getThirdPoint(self, v0, v1, l01, l12, l20):
|
||||
v2rotx = (l01 ** 2 + l20 ** 2 - l12 ** 2) / (2 * l01)
|
||||
v2roty0 = np.sqrt((l01 + l20 + l12) * (l01 + l20 - l12) * (l01 - l20 + l12) * (-l01 + l20 + l12)) / (2 * l01)
|
||||
|
||||
@ -63,8 +64,8 @@ def getThirdPoint(v0, v1, l01, l12, l20):
|
||||
return [v2trans0 + v0, v2trans1 + v0]
|
||||
|
||||
|
||||
# Check if two lines intersect
|
||||
def lineIntersection(v1, v2, v3, v4, epsilon):
|
||||
# Check if two lines intersect
|
||||
def lineIntersection(self, v1, v2, v3, v4, epsilon):
|
||||
d = (v4[1] - v3[1]) * (v2[0] - v1[0]) - (v4[0] - v3[0]) * (v2[1] - v1[1])
|
||||
u = (v4[0] - v3[0]) * (v1[1] - v3[1]) - (v4[1] - v3[1]) * (v1[0] - v3[0])
|
||||
v = (v2[0] - v1[0]) * (v1[1] - v3[1]) - (v2[1] - v1[1]) * (v1[0] - v3[0])
|
||||
@ -72,8 +73,8 @@ def lineIntersection(v1, v2, v3, v4, epsilon):
|
||||
u, v, d = -u, -v, -d
|
||||
return ((0 + epsilon) <= u <= (d - epsilon)) and ((0 + epsilon) <= v <= (d - epsilon))
|
||||
|
||||
# Check if a point lies inside a triangle
|
||||
def pointInTriangle(A, B, C, P, epsilon):
|
||||
# Check if a point lies inside a triangle
|
||||
def pointInTriangle(self, A, B, C, P, epsilon):
|
||||
v0 = [C[0] - A[0], C[1] - A[1]]
|
||||
v1 = [B[0] - A[0], B[1] - A[1]]
|
||||
v2 = [P[0] - A[0], P[1] - A[1]]
|
||||
@ -86,39 +87,40 @@ def pointInTriangle(A, B, C, P, epsilon):
|
||||
return u >= (0 + epsilon) and v >= (0 + epsilon) and (u + v) <= (d - epsilon)
|
||||
|
||||
|
||||
# Check if two triangles intersect
|
||||
def triangleIntersection(t1, t2, epsilon):
|
||||
if lineIntersection(t1[0], t1[1], t2[0], t2[1], epsilon): return True
|
||||
if lineIntersection(t1[0], t1[1], t2[0], t2[2], epsilon): return True
|
||||
if lineIntersection(t1[0], t1[1], t2[1], t2[2], epsilon): return True
|
||||
if lineIntersection(t1[0], t1[2], t2[0], t2[1], epsilon): return True
|
||||
if lineIntersection(t1[0], t1[2], t2[0], t2[2], epsilon): return True
|
||||
if lineIntersection(t1[0], t1[2], t2[1], t2[2], epsilon): return True
|
||||
if lineIntersection(t1[1], t1[2], t2[0], t2[1], epsilon): return True
|
||||
if lineIntersection(t1[1], t1[2], t2[0], t2[2], epsilon): return True
|
||||
if lineIntersection(t1[1], t1[2], t2[1], t2[2], epsilon): return True
|
||||
# Check if two triangles intersect
|
||||
def triangleIntersection(self, t1, t2, epsilon):
|
||||
if self.lineIntersection(t1[0], t1[1], t2[0], t2[1], epsilon): return True
|
||||
if self.lineIntersection(t1[0], t1[1], t2[0], t2[2], epsilon): return True
|
||||
if self.lineIntersection(t1[0], t1[1], t2[1], t2[2], epsilon): return True
|
||||
if self.lineIntersection(t1[0], t1[2], t2[0], t2[1], epsilon): return True
|
||||
if self.lineIntersection(t1[0], t1[2], t2[0], t2[2], epsilon): return True
|
||||
if self.lineIntersection(t1[0], t1[2], t2[1], t2[2], epsilon): return True
|
||||
if self.lineIntersection(t1[1], t1[2], t2[0], t2[1], epsilon): return True
|
||||
if self.lineIntersection(t1[1], t1[2], t2[0], t2[2], epsilon): return True
|
||||
if self.lineIntersection(t1[1], t1[2], t2[1], t2[2], epsilon): return True
|
||||
inTri = True
|
||||
inTri = inTri and pointInTriangle(t1[0], t1[1], t1[2], t2[0], epsilon)
|
||||
inTri = inTri and pointInTriangle(t1[0], t1[1], t1[2], t2[1], epsilon)
|
||||
inTri = inTri and pointInTriangle(t1[0], t1[1], t1[2], t2[2], epsilon)
|
||||
inTri = inTri and self.pointInTriangle(t1[0], t1[1], t1[2], t2[0], epsilon)
|
||||
inTri = inTri and self.pointInTriangle(t1[0], t1[1], t1[2], t2[1], epsilon)
|
||||
inTri = inTri and self.pointInTriangle(t1[0], t1[1], t1[2], t2[2], epsilon)
|
||||
if inTri == True: return True
|
||||
inTri = True
|
||||
inTri = inTri and pointInTriangle(t2[0], t2[1], t2[2], t1[0], epsilon)
|
||||
inTri = inTri and pointInTriangle(t2[0], t2[1], t2[2], t1[1], epsilon)
|
||||
inTri = inTri and pointInTriangle(t2[0], t2[1], t2[2], t1[2], epsilon)
|
||||
inTri = inTri and self.pointInTriangle(t2[0], t2[1], t2[2], t1[0], epsilon)
|
||||
inTri = inTri and self.pointInTriangle(t2[0], t2[1], t2[2], t1[1], epsilon)
|
||||
inTri = inTri and self.pointInTriangle(t2[0], t2[1], t2[2], t1[2], epsilon)
|
||||
if inTri == True: return True
|
||||
return False
|
||||
|
||||
|
||||
# Functions for visualisation and output
|
||||
def addVisualisationData(mesh, unfoldedMesh, originalHalfedges, unfoldedHalfedges, glueNumber, foldingDirection):
|
||||
# Functions for visualisation and output
|
||||
def addVisualisationData(self, mesh, unfoldedMesh, originalHalfedges, unfoldedHalfedges, glueNumber, foldingDirection):
|
||||
for i in range(3):
|
||||
foldingDirection[unfoldedMesh.edge_handle(unfoldedHalfedges[i]).idx()] = round(math.degrees(mesh.calc_dihedral_angle(originalHalfedges[i])), 3)
|
||||
foldingDirection[unfoldedMesh.edge_handle(unfoldedHalfedges[i]).idx()] = round(math.degrees(mesh.calc_dihedral_angle(originalHalfedges[i])), self.options.roundingDigits)
|
||||
# Information, which edges belong together
|
||||
glueNumber[unfoldedMesh.edge_handle(unfoldedHalfedges[i]).idx()] = mesh.edge_handle(originalHalfedges[i]).idx()
|
||||
|
||||
# Function that unwinds a spanning tree
|
||||
def unfoldSpanningTree(mesh, spanningTree):
|
||||
# Function that unwinds a spanning tree
|
||||
def unfoldSpanningTree(self, mesh, spanningTree):
|
||||
try:
|
||||
unfoldedMesh = om.TriMesh() # the unfolded mesh
|
||||
|
||||
numFaces = mesh.n_faces()
|
||||
@ -152,7 +154,7 @@ def unfoldSpanningTree(mesh, spanningTree):
|
||||
secondUnfoldedPoint = np.array([edgelengths[0], 0, 0])
|
||||
|
||||
# We calculate the third point of the triangle from the first two. There are two possibilities
|
||||
[thirdUnfolded0, thirdUnfolded1] = getThirdPoint(firstUnfoldedPoint, secondUnfoldedPoint, edgelengths[0],
|
||||
[thirdUnfolded0, thirdUnfolded1] = self.getThirdPoint(firstUnfoldedPoint, secondUnfoldedPoint, edgelengths[0],
|
||||
edgelengths[1],
|
||||
edgelengths[2])
|
||||
if thirdUnfolded0[1] > 0:
|
||||
@ -183,7 +185,7 @@ def unfoldSpanningTree(mesh, spanningTree):
|
||||
# Associated triangle in 3D mesh
|
||||
connections[unfoldedFace.idx()] = startingTriangle.idx()
|
||||
# Folding direction and adhesive number
|
||||
addVisualisationData(mesh, unfoldedMesh, originalHalfEdges, unfoldedHalfEdges, glueNumber, foldingDirection)
|
||||
self.addVisualisationData(mesh, unfoldedMesh, originalHalfEdges, unfoldedHalfEdges, glueNumber, foldingDirection)
|
||||
|
||||
halfEdgeConnections = {firstHalfEdge.idx(): firstUnfoldedHalfEdge.idx(),
|
||||
secondHalfEdge.idx(): secondUnfoldedHalfEdge.idx(),
|
||||
@ -221,7 +223,7 @@ def unfoldSpanningTree(mesh, spanningTree):
|
||||
mesh.calc_edge_length(thirdHalfEdgeInFace)]
|
||||
|
||||
# We calculate the two possibilities for the third point in the triangle
|
||||
[newUnfoldedVertex0, newUnfoldedVertex1] = getThirdPoint(unfoldedMesh.point(unfoldedFromVertex),
|
||||
[newUnfoldedVertex0, newUnfoldedVertex1] = self.getThirdPoint(unfoldedMesh.point(unfoldedFromVertex),
|
||||
unfoldedMesh.point(unfoldedToVertex), edgelengths[0],
|
||||
edgelengths[1], edgelengths[2])
|
||||
|
||||
@ -236,12 +238,12 @@ def unfoldSpanningTree(mesh, spanningTree):
|
||||
unfoldedHalfEdges = [unfoldedLastHalfEdge, secondUnfoldedHalfEdge, thirdUnfoldedHalfEdge]
|
||||
|
||||
# Saving the information about edges and page
|
||||
# Dotted line in the output
|
||||
# Dotted one's in the output
|
||||
unfoldedLastEdge = unfoldedMesh.edge_handle(unfoldedLastHalfEdge)
|
||||
isFoldingEdge[unfoldedLastEdge.idx()] = True
|
||||
|
||||
# Gluing number and folding direction
|
||||
addVisualisationData(mesh, unfoldedMesh, originalHalfEdges, unfoldedHalfEdges, glueNumber, foldingDirection)
|
||||
self.addVisualisationData(mesh, unfoldedMesh, originalHalfEdges, unfoldedHalfEdges, glueNumber, foldingDirection)
|
||||
|
||||
# Related page
|
||||
connections[newface.idx()] = dualEdge[1]
|
||||
@ -251,25 +253,34 @@ def unfoldSpanningTree(mesh, spanningTree):
|
||||
halfEdgeConnections[originalHalfEdges[i].idx()] = unfoldedHalfEdges[i].idx()
|
||||
|
||||
return [unfoldedMesh, isFoldingEdge, connections, glueNumber, foldingDirection]
|
||||
except Exception as e:
|
||||
inkex.utils.debug(str(e))
|
||||
inkex.utils.debug("Error: model could not be unfolded. Check for:")
|
||||
inkex.utils.debug(" - watertight model / intact hull")
|
||||
inkex.utils.debug(" - duplicated edges or faces")
|
||||
inkex.utils.debug(" - detached faces or holes")
|
||||
inkex.utils.debug(" - missing units")
|
||||
inkex.utils.debug(" - missing coordinate system")
|
||||
exit(1)
|
||||
|
||||
def unfold(mesh, maxNumFaces, printStats):
|
||||
|
||||
def unfold(self, mesh):
|
||||
# Calculate the number of surfaces, edges and corners, as well as the length of the longest shortest edge
|
||||
numEdges = mesh.n_edges()
|
||||
numVertices = mesh.n_vertices()
|
||||
numFaces = mesh.n_faces()
|
||||
|
||||
if numFaces > maxNumFaces:
|
||||
inkex.utils.debug("Aborted. Target STL file has " + str(numFaces) + " faces, but " + str(maxNumFaces) + " are allowed.")
|
||||
if numFaces > self.options.maxNumFaces:
|
||||
inkex.utils.debug("Aborted. Target STL file has " + str(numFaces) + " faces, but only " + str(maxNumFaces) + " are allowed.")
|
||||
exit(1)
|
||||
|
||||
if printStats is True:
|
||||
if self.options.printStats is True:
|
||||
inkex.utils.debug("Input STL mesh stats:")
|
||||
inkex.utils.debug("* Number of edges: " + str(numEdges))
|
||||
inkex.utils.debug("* Number of vertices: " + str(numVertices))
|
||||
inkex.utils.debug("* Number of faces: " + str(numFaces))
|
||||
inkex.utils.debug("-----------------------------------------------------------")
|
||||
|
||||
|
||||
# Generate the dual graph of the mesh and calculate the weights
|
||||
dualGraph = nx.Graph()
|
||||
|
||||
@ -285,12 +296,15 @@ def unfold(mesh, maxNumFaces, printStats):
|
||||
|
||||
# All edges in the net
|
||||
for edge in mesh.edges():
|
||||
#inkex.utils.debug("edge.idx = " + str(edge.idx()))
|
||||
|
||||
# The two sides adjacent to the edge
|
||||
face1 = mesh.face_handle(mesh.halfedge_handle(edge, 0))
|
||||
face2 = mesh.face_handle(mesh.halfedge_handle(edge, 1))
|
||||
|
||||
# The weight
|
||||
edgeweight = 1.0 - (mesh.calc_edge_length(edge) - minLength) / (maxLength - minLength)
|
||||
#inkex.utils.debug("edgeweight = " + str(edgeweight))
|
||||
|
||||
# Calculate the centres of the pages (only necessary for visualisation)
|
||||
center1 = (0, 0)
|
||||
@ -303,13 +317,14 @@ def unfold(mesh, maxNumFaces, printStats):
|
||||
# Add the new nodes and edge to the dual graph
|
||||
dualGraph.add_node(face1.idx(), pos=center1)
|
||||
dualGraph.add_node(face2.idx(), pos=center2)
|
||||
dualGraph.add_edge(face1.idx(), face2.idx(), idx=edge.idx(), weight=edgeweight)
|
||||
dualGraph.add_edge(face1.idx(), face2.idx(), idx=edge.idx(), weight=edgeweight) # #might fail without throwing any error ...
|
||||
|
||||
|
||||
# Calculate the minimum spanning tree
|
||||
spanningTree = nx.minimum_spanning_tree(dualGraph)
|
||||
|
||||
# Unfold the tree
|
||||
fullUnfolding = unfoldSpanningTree(mesh, spanningTree)
|
||||
fullUnfolding = self.unfoldSpanningTree(mesh, spanningTree)
|
||||
[unfoldedMesh, isFoldingEdge, connections, glueNumber, foldingDirection] = fullUnfolding
|
||||
|
||||
|
||||
@ -327,7 +342,7 @@ def unfold(mesh, maxNumFaces, printStats):
|
||||
triangle1.append(unfoldedMesh.point(unfoldedMesh.from_vertex_handle(halfedge)))
|
||||
for halfedge in unfoldedMesh.fh(face2):
|
||||
triangle2.append(unfoldedMesh.point(unfoldedMesh.from_vertex_handle(halfedge)))
|
||||
if triangleIntersection(triangle1, triangle2, epsilon):
|
||||
if self.triangleIntersection(triangle1, triangle2, epsilon):
|
||||
faceIntersections.append([connections[face1.idx()], connections[face2.idx()]])
|
||||
|
||||
# Find the paths
|
||||
@ -397,12 +412,13 @@ def unfold(mesh, maxNumFaces, printStats):
|
||||
# Unfolding of the components
|
||||
unfoldings = []
|
||||
for component in connectedComponentList:
|
||||
unfoldings.append(unfoldSpanningTree(mesh, spanningTree.subgraph(component)))
|
||||
unfoldings.append(self.unfoldSpanningTree(mesh, spanningTree.subgraph(component)))
|
||||
|
||||
|
||||
return fullUnfolding, unfoldings
|
||||
|
||||
|
||||
def findBoundingBox(mesh):
|
||||
def findBoundingBox(self, mesh):
|
||||
firstpoint = mesh.point(mesh.vertex_handle(0))
|
||||
xmin = firstpoint[0]
|
||||
xmax = firstpoint[0]
|
||||
@ -423,7 +439,7 @@ def findBoundingBox(mesh):
|
||||
return [xmin, ymin, boxSize]
|
||||
|
||||
|
||||
def writeSVG(self, unfolding, size, randomColorSet):
|
||||
def writeSVG(self, unfolding, size, randomColorSet):
|
||||
mesh = unfolding[0]
|
||||
isFoldingEdge = unfolding[1]
|
||||
glueNumber = unfolding[3]
|
||||
@ -433,12 +449,12 @@ def writeSVG(self, unfolding, size, randomColorSet):
|
||||
gluePairs = 0
|
||||
mountainCuts = 0
|
||||
valleyCuts = 0
|
||||
coplanarLines = 0
|
||||
coplanarEdges = 0
|
||||
mountainPerforations = 0
|
||||
valleyPerforations = 0
|
||||
|
||||
# Calculate the bounding box
|
||||
[xmin, ymin, boxSize] = findBoundingBox(unfolding[0])
|
||||
[xmin, ymin, boxSize] = self.findBoundingBox(unfolding[0])
|
||||
|
||||
if size > 0:
|
||||
boxSize = size
|
||||
@ -472,6 +488,15 @@ def writeSVG(self, unfolding, size, randomColorSet):
|
||||
textGroup.add(textFacesGroup)
|
||||
textGroup.add(textEdgesGroup)
|
||||
|
||||
#we could write the unfolded mesh as a 2D stl file to disk if we like:
|
||||
if self.options.writeTwoDSTL is True:
|
||||
if not os.path.exists(self.options.TwoDSTLdir):
|
||||
inkex.utils.debug("Export location for 2D STL unfoldings does not exist. Please select a another dir and try again.")
|
||||
exit(1)
|
||||
else:
|
||||
om.write_mesh(os.path.join(self.options.TwoDSTLdir, uniqueMainId + "-paperfold-page.stl"), mesh)
|
||||
|
||||
|
||||
if self.options.printTriangleNumbers is True:
|
||||
|
||||
for face in mesh.faces():
|
||||
@ -524,7 +549,7 @@ def writeSVG(self, unfolding, size, randomColorSet):
|
||||
line.set("id", uniqueMainId + "-coplanar-edge-" + str(edge.idx()))
|
||||
#if self.options.importCoplanarEdges is False:
|
||||
# line.delete()
|
||||
coplanarLines += 1
|
||||
coplanarEdges += 1
|
||||
|
||||
lineStyle.update({"stroke-width":str(strokewidth)})
|
||||
lineStyle.update({"stroke-linecap":"butt"})
|
||||
@ -614,30 +639,32 @@ def writeSVG(self, unfolding, size, randomColorSet):
|
||||
tspanText.append("{:0.2f} {}".format(self.options.scalefactor * math.hypot(vertex1[0] - vertex0[0], vertex1[1] - vertex0[1]), unitToPrint))
|
||||
tspan.text = " | ".join(tspanText)
|
||||
|
||||
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:
|
||||
if tspan.text == "": #if no text we remove again to clean up
|
||||
text.delete()
|
||||
tspan.delete()
|
||||
|
||||
#delete unrequired groups if no text labels
|
||||
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:
|
||||
textGroup.delete()
|
||||
textFacesGroup.delete()
|
||||
textEdgesGroup.delete()
|
||||
if len(textFacesGroup) == 0:
|
||||
textFacesGroup.delete() #delete if empty set
|
||||
|
||||
if len(textFacesGroup) == 0:
|
||||
textEdgesGroup.delete() #delete if empty set
|
||||
|
||||
if len(textGroup) == 0:
|
||||
textGroup.delete() #delete if empty set
|
||||
|
||||
if self.options.printStats is True:
|
||||
inkex.utils.debug("Folding edges stats:")
|
||||
inkex.utils.debug(" * Number of mountain cuts: " + str(mountainCuts))
|
||||
inkex.utils.debug(" * Number of valley cuts: " + str(valleyCuts))
|
||||
inkex.utils.debug(" * Number of coplanar lines: " + str(coplanarLines))
|
||||
inkex.utils.debug(" * Number of coplanar edges: " + str(coplanarEdges))
|
||||
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: {:0.0f}".format(gluePairs / 2))
|
||||
inkex.utils.debug(" * Number of glue pairs: {:0.0f}".format(gluePairs / 2))
|
||||
inkex.utils.debug(" * min angle: {:0.2f}".format(minAngle))
|
||||
inkex.utils.debug(" * max angle: {:0.2f}".format(maxAngle))
|
||||
inkex.utils.debug(" * Edge angle range: {:0.2f}".format(angleRange))
|
||||
|
||||
return paperfoldPageGroup
|
||||
|
||||
class Unfold(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("--tab")
|
||||
|
||||
@ -645,6 +672,7 @@ class Unfold(inkex.EffectExtension):
|
||||
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("--scalefactor", type=float, default=1.0, help="Manual scale factor")
|
||||
pars.add_argument("--roundingDigits", type=int, default=3, help="Digits for rounding")
|
||||
|
||||
#Output
|
||||
pars.add_argument("--printGluePairNumbers", type=inkex.Boolean, default=False, help="Print glue pair numbers on cut edges")
|
||||
@ -652,10 +680,12 @@ class Unfold(inkex.EffectExtension):
|
||||
pars.add_argument("--printLengths", type=inkex.Boolean, default=False, help="Print lengths on edges")
|
||||
pars.add_argument("--printTriangleNumbers", type=inkex.Boolean, default=False, help="Print triangle numbers on faces")
|
||||
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("--printStats", type=inkex.Boolean, default=False, 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("--extraborderUnits")
|
||||
pars.add_argument("--writeTwoDSTL", type=inkex.Boolean, default=False, help="Write 2D STL unfoldings")
|
||||
pars.add_argument("--TwoDSTLdir", default="./inkscape_export/", help="Location to save exported 2D STL")
|
||||
|
||||
#Style
|
||||
pars.add_argument("--fontSize", type=int, default=15, help="Label font size (%)")
|
||||
@ -669,6 +699,10 @@ class Unfold(inkex.EffectExtension):
|
||||
pars.add_argument("--colorValleyPerforates", type=Color, default='3422552319', help="Valley perforation edges")
|
||||
pars.add_argument("--colorMountainPerforates", type=Color, default='879076607', help="Mountain perforation edges")
|
||||
|
||||
#Post Processing
|
||||
pars.add_argument("--joineryMode", type=inkex.Boolean, default=False, help="Enable joinery mode")
|
||||
pars.add_argument("--origamiSimulatorMode", type=inkex.Boolean, default=False, help="Enable origami simulator mode")
|
||||
|
||||
def effect(self):
|
||||
if not os.path.exists(self.options.inputfile):
|
||||
inkex.utils.debug("The input file does not exist. Please select a proper file and try again.")
|
||||
@ -676,12 +710,21 @@ class Unfold(inkex.EffectExtension):
|
||||
mesh = om.read_trimesh(self.options.inputfile)
|
||||
#mesh = om.read_polymesh(self.options.inputfile) #we must work with triangles instead of polygons because the algorithm works with that
|
||||
|
||||
fullUnfolded, unfoldedComponents = unfold(mesh, self.options.maxNumFaces, self.options.printStats)
|
||||
fullUnfolded, unfoldedComponents = self.unfold(mesh)
|
||||
|
||||
|
||||
#if len(unfoldedComponents) == 0:
|
||||
# inkex.utils.debug("Error: no components were unfolded.")
|
||||
# exit(1)
|
||||
|
||||
if self.options.printStats is True:
|
||||
inkex.utils.debug("Unfolding components: {:0.0f}".format(len(unfoldedComponents)))
|
||||
|
||||
# Compute maxSize of the components
|
||||
# All components must be scaled to the same size as the largest component
|
||||
maxSize = 0
|
||||
for unfolding in unfoldedComponents:
|
||||
[xmin, ymin, boxSize] = findBoundingBox(unfolding[0])
|
||||
[xmin, ymin, boxSize] = self.findBoundingBox(unfolding[0])
|
||||
if boxSize > maxSize:
|
||||
maxSize = boxSize
|
||||
|
||||
@ -694,10 +737,26 @@ class Unfold(inkex.EffectExtension):
|
||||
if newColor not in randomColorSet:
|
||||
randomColorSet.append(newColor)
|
||||
|
||||
#some mode configs:
|
||||
if self.options.joineryMode is True:
|
||||
self.options.separateGluePairsByColor = True #we need random colors in this mode
|
||||
|
||||
if self.options.origamiSimulatorMode is True:
|
||||
self.options.joineryMode = True #we set to true even if false because we need the same flat structure for origami simulator
|
||||
self.options.separateGluePairsByColor = False #we need to have no weird random colors in this mode
|
||||
self.options.colorMountainCut = "#000000" #black
|
||||
self.options.colorValleyCut = "#000000" #black
|
||||
self.options.colorCoplanarEdges = "#000000" #black
|
||||
self.options.colorMountainPerforates = "#ff0000" #red
|
||||
self.options.colorValleyPerforates = "#0000ff" #blue
|
||||
|
||||
# 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, randomColorSet)
|
||||
if self.options.printStats is True:
|
||||
inkex.utils.debug("-----------------------------------------------------------")
|
||||
inkex.utils.debug("Unfolding component nr.: {:0.0f}".format(i))
|
||||
paperfoldPageGroup = self.writeSVG(unfoldedComponents[i], maxSize, randomColorSet)
|
||||
#translate the groups next to each other to remove overlappings
|
||||
if i != 0:
|
||||
previous_bbox = paperfoldMainGroup[i-1].bounding_box()
|
||||
@ -715,12 +774,22 @@ class Unfold(inkex.EffectExtension):
|
||||
if self.options.resizetoimport:
|
||||
bbox = paperfoldMainGroup.bounding_box()
|
||||
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.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)
|
||||
root.set('width', str(bbox.width + 2 * offset) + self.svg.unit)
|
||||
root.set('height', str(bbox.height + 2 * offset) + self.svg.unit)
|
||||
|
||||
#if set, we move all edges (path elements) to the top level
|
||||
if self.options.joineryMode is True:
|
||||
for paperfoldPage in paperfoldMainGroup.getchildren():
|
||||
for child in paperfoldPage:
|
||||
if "-edges" in child.get('id'):
|
||||
for edge in child:
|
||||
edgeTransform = edge.composed_transform()
|
||||
self.document.getroot().append(edge)
|
||||
edge.transform = edgeTransform
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Unfold().run()
|
Reference in New Issue
Block a user