This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/papercraft/papercraft_unfold.py

278 lines
15 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
import sys
import os
import inkex
import tempfile
import subprocess
from subprocess import Popen, PIPE
from lxml import etree
#specific imports for model-converter-python
import functools as fc
import d3.model.tools as mt
from d3.model.basemodel import Vector
"""
Extension for InkScape 1.0
Import any DWG or DXF file using ODA File Converter, sk1 UniConvertor, ezdxf and more tools.
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 08.09.2020
Last patch: 08.09.2020
License: GNU GPL v3
#################################################################
This tool converts a lot of different formats into STL Format. The STL then gets unfolded (flattened) to make a papercraft model. You can select between different engines. The used tools are
- converters
- openjscad
- model-converter-python
- repairers
- ADMesh
- unfolders
- osresearch/papercraft
#################################################################
openjscad [-v] <file> [-of <format>] [-o <output>]
<file> : input file (Supported types: .jscad, .js, .scad, .stl, .amf, .obj, .gcode, .svg, .json)
<output>: output file (Supported types: .jscad, .stl, .amf, .dxf, .svg, .json)
<format>: 'jscad', 'stla' (STL ASCII, default), 'stlb' (STL Binary), 'amf', 'dxf', 'svg', 'json'
#################################################################
ADMesh version 0.99.0dev
Copyright (C) 1995, 1996 Anthony D. Martin
Usage: /usr/share/inkscape/extensions/mightyscape-1.X/extensions/fablabchemnitz/papercraft/.libs/admesh [OPTION]... file
--x-rotate=angle Rotate CCW about x-axis by angle degrees
--y-rotate=angle Rotate CCW about y-axis by angle degrees
--z-rotate=angle Rotate CCW about z-axis by angle degrees
--xy-mirror Mirror about the xy plane
--yz-mirror Mirror about the yz plane
--xz-mirror Mirror about the xz plane
--scale=factor Scale the file by factor (multiply by factor)
--scale-xyz=x,y,z Scale the file by a non uniform factor
--translate=x,y,z Translate the file to x, y, and z
--merge=name Merge file called name with input file
-e, --exact Only check for perfectly matched edges
-n, --nearby Find and connect nearby facets. Correct bad facets
-t, --tolerance=tol Initial tolerance to use for nearby check = tol
-i, --iterations=i Number of iterations for nearby check = i
-m, --increment=inc Amount to increment tolerance after iteration=inc
-u, --remove-unconnected Remove facets that have 0 neighbors
-f, --fill-holes Add facets to fill holes
-d, --normal-directions Check and fix direction of normals(ie cw, ccw)
--reverse-all Reverse the directions of all facets and normals
-v, --normal-values Check and fix normal values
-c, --no-check Don't do any check on input file
-b, --write-binary-stl=name Output a binary STL file called name
-a, --write-ascii-stl=name Output an ascii STL file called name
--write-off=name Output a Geomview OFF format file called name
--write-dxf=name Output a DXF format file called name
--write-vrml=name Output a VRML format file called name
--help Display this help and exit
--version Output version information and exit
The functions are executed in the same order as the options shown here.
So check here to find what happens if, for example, --translate and --merge
options are specified together. The order of the options specified on the
command line is not important.
#################################################################
Module licenses
- papercraft - 26307b8 (https://github.com/osresearch/papercraft) - GPL v2 License
- openjscad - 1.6.1 (https://github.com/jscad/OpenJSCAD.org) - MIT License and other (installed using npm install -g @jscad/openjscad)
- model-converter - commit a8d809a (https://github.com/tforgione/model-converter-python) - MIT License
- admesh - 0.98.3 (https://github.com/admesh/admesh) - GPL License
#TODO
- admesh Parameter als extra Tab in InkScape
- fix canvas resize
- Windows Executables
- Doku Seite mit HowTo Compile / install openjscad + admesh
"""
class Unfold(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.arg_parser.add_argument("--tab")
self.arg_parser.add_argument("--inputfile")
self.arg_parser.add_argument("--generatelabels", type=inkex.Boolean, default=True, help="Generate labels for edges")
self.arg_parser.add_argument("--resizetoimport", type=inkex.Boolean, default=True, help="Resize the canvas to the imported drawing's bounding box")
self.arg_parser.add_argument("--extraborder", type=float, default=0.0)
self.arg_parser.add_argument("--extraborder_units")
self.arg_parser.add_argument("--show_fstl", type=inkex.Boolean, default=True, help="Show converted (and fixed) STL in fstl Viewer")
self.arg_parser.add_argument("--fixmesh", type=inkex.Boolean, default=True)
self.arg_parser.add_argument("--exact", type=inkex.Boolean, default=True, help="Only check for perfectly matched edges")
self.arg_parser.add_argument("--nearby", type=inkex.Boolean, default=True, help="Find and connect nearby facets. Correct bad facets")
self.arg_parser.add_argument("--tolerance", type=float, default=0.0, help="Initial tolerance to use for nearby check")
self.arg_parser.add_argument("--iterations", type=int, default=1, help="Number of iterations for nearby check")
self.arg_parser.add_argument("--increment", type=float, default=0.0, help="Amount to increment tolerance after iteration")
self.arg_parser.add_argument("--remove_unconnected", type=inkex.Boolean, default=True, help="Remove facets that have 0 neighbors")
self.arg_parser.add_argument("--fill_holes", type=inkex.Boolean, default=True, help="Add facets to fill holes")
self.arg_parser.add_argument("--normal_directions", type=inkex.Boolean, default=True, help="Check and fix direction of normals (ie cw, ccw)")
self.arg_parser.add_argument("--reverse_all", type=inkex.Boolean, default=True, help="Reverse the directions of all facets and normals")
self.arg_parser.add_argument("--normal_values", type=inkex.Boolean, default=True, help="Check and fix normal values")
self.arg_parser.add_argument("--xy_mirror", type=inkex.Boolean, default=True)
self.arg_parser.add_argument("--yz_mirror", type=inkex.Boolean, default=True)
self.arg_parser.add_argument("--xz_mirror", type=inkex.Boolean, default=True)
self.arg_parser.add_argument("--scale", type=float, default=1.0)
def effect(self):
import_formats_model_converter=[".obj", ".off", ".ply"] #we could also handle stl but openscad does the better job
import_formats_openjscad=[".jscad", ".js", ".scad", ".stl", ".amf", ".gcode", ".json"] #we could also handle obj but model converter does the better job
inputfile = self.options.inputfile
if not os.path.exists(inputfile):
inkex.utils.debug("The input file does not exist. Please select a proper file and try again.")
exit(1)
inputfile_format = (os.path.splitext(os.path.basename(inputfile))[1]).lower()
if inputfile_format not in import_formats_model_converter and inputfile_format not in import_formats_openjscad:
inkex.utils.debug("The input file format cannot be converted to a required format for flattening.")
exit(1)
converted_inputfile = os.path.join(tempfile.gettempdir(), os.path.splitext(os.path.basename(inputfile))[0] + ".stl")
if os.path.exists(converted_inputfile):
os.remove(converted_inputfile) #remove previously generated conversion file
if inputfile_format in import_formats_model_converter:
up_conversion = None
with open(converted_inputfile, 'w') as f:
f.write(mt.convert(inputfile, converted_inputfile, up_conversion))
if inputfile_format in import_formats_openjscad:
if os.name=="nt":
jscad_cmd = "openjscad\\node_modules\\.bin\\openjscad.cmd "
else:
jscad_cmd = "./openjscad/node_modules/.bin/openjscad "
jscad_cmd += "\"" + inputfile + "\" -of stlb -o \"" + converted_inputfile + "\""
p = Popen(jscad_cmd, shell=True, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
p.wait()
if p.returncode != 0 or len(stderr) > 0:
inkex.utils.debug("first openjscad conversion failed: %d %s %s" % (p.returncode, stdout, stderr))
#we do this to convert possibly previously generated ASCII STL from model converter to binary STL to ensure always binary STLs
if os.name=="nt":
jscad2_cmd = "openjscad\\node_modules\\.bin\\openjscad.cmd "
else:
jscad2_cmd = "./openjscad/node_modules/.bin/openjscad "
jscad2_cmd += "\"" + converted_inputfile + "\" -of stlb -o \"" + converted_inputfile + "\""
p = Popen(jscad2_cmd, shell=True, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
p.wait()
if p.returncode != 0 or len(stderr) > 0:
inkex.utils.debug("second openjscad conversion failed: %d %s %s" % (p.returncode, stdout, stderr))
if not os.path.exists(converted_inputfile):
inkex.utils.debug("Cannot find conversion output. Unable to continue")
exit(1)
# Run ADMesh mesh fixer to overwrite the STL with fixed output (binary output too)
if self.options.fixmesh == True:
if os.name=="nt":
admesh_cmd = "admesh\\admesh.exe "
else:
admesh_cmd = "./admesh/admesh "
if self.options.xy_mirror == True: admesh_cmd += "--xy-mirror "
if self.options.yz_mirror == True: admesh_cmd += "--yz-mirror "
if self.options.xz_mirror == True: admesh_cmd += "--xz-mirror "
if self.options.scale != 1.0: admesh_cmd += "--scale " + str(self.options.scale) + " "
if self.options.exact == True: admesh_cmd += "--exact "
if self.options.nearby == True: admesh_cmd += "--nearby "
if self.options.tolerance > 0.0: admesh_cmd += "--tolerance " + str(self.options.tolerance) + " "
if self.options.iterations > 1: admesh_cmd += "--iterations " + str(self.options.iterations) + " "
if self.options.increment > 0.0: admesh_cmd += "--increment " + str(self.options.increment) + " "
if self.options.remove_unconnected == True: admesh_cmd += "--remove-unconnected "
if self.options.normal_directions == True: admesh_cmd += "--normal-directions "
if self.options.fill_holes == True: admesh_cmd += "--fill-holes "
if self.options.reverse_all == True: admesh_cmd += "--reverse-all "
if self.options.normal_values == True: admesh_cmd += "--normal-values "
admesh_cmd += "\"" + converted_inputfile + "\" "
admesh_cmd += "-b \"" + converted_inputfile + "\""
p = Popen(admesh_cmd, shell=True, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
p.wait()
if p.returncode != 0:
inkex.utils.debug("admesh failed: %d %s %s" % (p.returncode, stdout, stderr))
exit(1)
# Run papercraft flattening
converted_flattenfile = os.path.join(tempfile.gettempdir(), os.path.splitext(os.path.basename(inputfile))[0] + ".svg")
if os.path.exists(converted_flattenfile):
os.remove(converted_flattenfile) #remove previously generated conversion file
if self.options.generatelabels:
unfold_exec = "unfold_labels"
else:
unfold_exec = "unfold_nolabels"
if os.name=="nt":
papercraft_cmd = "unfold\\" + unfold_exec + ".exe" + " < \"" + converted_inputfile + "\" > \"" + converted_flattenfile + "\""
else:
papercraft_cmd = "./unfold/" + unfold_exec + " < \"" + converted_inputfile + "\" > \"" + converted_flattenfile + "\""
p = Popen(papercraft_cmd, shell=True, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
p.wait()
if p.returncode != 0:
inkex.utils.debug("osresearch/papercraft unfold failed: %d %s %s" % (p.returncode, stdout, stderr))
# Open converted output in fstl
if self.options.show_fstl == True:
if os.name=="nt":
fstl_cmd = "fstl\\fstl.exe \"" + converted_inputfile + "\""
else:
fstl_cmd = "./fstl/fstl \"" + converted_inputfile + "\""
p = Popen(fstl_cmd, shell=True)
p.wait()
# Write the generated SVG into InkScape's canvas
try:
stream = open(converted_flattenfile, 'r')
except FileNotFoundError as e:
inkex.utils.debug("There was no SVG output generated. Cannot continue")
exit(1)
p = etree.XMLParser(huge_tree=True)
doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True)).getroot()
stream.close()
doc.set('id', self.svg.get_unique_id('papercraft_unfold'))
self.document.getroot().append(doc)
#adjust viewport and width/height to have the import at the center of the canvas - unstable at the moment.
if self.options.resizetoimport:
elements = []
for child in doc.getchildren():
#if child.tag == inkex.addNS('g','svg'):
elements.append(child)
#build sum of bounding boxes and ignore errors for faulty elements (sum function often fails for that usecase!)
bbox = None
try:
bbox = elements[0].bounding_box() #init with the first bounding box of the tree (and hope that it is not a faulty one)
except Exception as e:
#inkex.utils.debug(str(e))
pass
count = 0
for element in elements:
if count != 0: #skip the first
try:
#bbox.add(element.bounding_box())
bbox += element.bounding_box()
except Exception as e:
#inkex.utils.debug(str(e))
pass
count += 1 #some stupid counter
if bbox is not None:
root = self.svg.getElement('//svg:svg');
offset = self.svg.unittouu(str(self.options.extraborder) + self.options.extraborder_units)
root.set('viewBox', '%f %f %f %f' % (bbox.left - offset, bbox.top - offset, bbox.width + 2 * offset, bbox.height + 2 * offset))
root.set('width', bbox.width + 2 * offset)
root.set('height', bbox.height + 2 * offset)
if __name__ == '__main__':
Unfold().run()