Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'io_mesh_stl')
-rw-r--r--io_mesh_stl/__init__.py152
-rw-r--r--io_mesh_stl/blender_utils.py50
-rw-r--r--io_mesh_stl/stl_utils.py228
3 files changed, 430 insertions, 0 deletions
diff --git a/io_mesh_stl/__init__.py b/io_mesh_stl/__init__.py
new file mode 100644
index 00000000..1858c17b
--- /dev/null
+++ b/io_mesh_stl/__init__.py
@@ -0,0 +1,152 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Import/Export STL files (binary or ascii)
+
+- Import automatically remove the doubles.
+- Export can export with/without modifiers applied
+
+Issues:
+
+Import:
+ - Does not handle the normal of the triangles
+ - Does not handle endien
+
+Export:
+ - Does not do the object space transformation
+ - Export only one object (the selected one)
+"""
+
+bl_addon_info = {
+ 'name': 'Import/Export: STL format',
+ 'author': 'Guillaume Bouchard (Guillaum)',
+ 'version': '1',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Import/Export > Stl',
+ 'description': 'Import/Export Stl files',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/File I-O/STL', # @todo write the page
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?' \
+ 'func=detail&aid=22837&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+import bpy
+from bpy.props import *
+
+from io_mesh_stl import stl_utils, blender_utils
+
+
+class StlImporter(bpy.types.Operator):
+ '''
+ Load STL triangle mesh data
+ '''
+ bl_idname = "import_mesh.stl"
+ bl_label = "Import STL"
+
+ filepath = StringProperty(name="File Path",
+ description="File path used for importing "
+ "the STL file",
+ maxlen=1024,
+ default="")
+
+ def execute(self, context):
+ objName = bpy.utils.display_name(self.properties.filepath.split("\\")[-1].split("/")[-1])
+ tris, pts = stl_utils.read_stl(self.properties.filepath)
+
+ blender_utils.create_and_link_mesh(objName, tris, pts)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+
+ return {'RUNNING_MODAL'}
+
+
+class StlExporter(bpy.types.Operator):
+ '''
+ Save STL triangle mesh data from the active object
+ '''
+ bl_idname = "export_mesh.stl"
+ bl_label = "Export STL"
+
+ filepath = StringProperty(name="File Path",
+ description="File path used for exporting "
+ "the active object to STL file",
+ maxlen=1024,
+ default="")
+ check_existing = BoolProperty(name="Check Existing",
+ description="Check and warn on "
+ "overwriting existing files",
+ default=True,
+ options={'HIDDEN'})
+
+ ascii = BoolProperty(name="Ascii",
+ description="Save the file in ASCII file format",
+ default=False)
+ apply_modifiers = BoolProperty(name="Apply Modifiers",
+ description="Apply the modifiers "
+ "before saving",
+ default=True)
+
+ def execute(self, context):
+ ob = context.active_object
+
+ faces = blender_utils.faces_from_mesh(ob,
+ self.properties.apply_modifiers)
+ stl_utils.write_stl(self.properties.filepath, faces, self.properties.ascii)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+ return {'RUNNING_MODAL'}
+
+
+def menu_import(self, context):
+ self.layout.operator(StlImporter.bl_idname,
+ text="Stl (.stl)").filepath = "*.stl"
+
+
+def menu_export(self, context):
+ import os
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".stl"
+ self.layout.operator(StlExporter.bl_idname,
+ text="Stl (.stl)").filepath = default_path
+
+
+def register():
+ bpy.types.register(StlImporter)
+ bpy.types.register(StlExporter)
+ bpy.types.INFO_MT_file_import.append(menu_import)
+ bpy.types.INFO_MT_file_export.append(menu_export)
+
+
+def unregister():
+ bpy.types.unregister(StlImporter)
+ bpy.types.unregister(StlExporter)
+ bpy.types.INFO_MT_file_import.remove(menu_import)
+ bpy.types.INFO_MT_file_export.remove(menu_export)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/io_mesh_stl/blender_utils.py b/io_mesh_stl/blender_utils.py
new file mode 100644
index 00000000..1bade61d
--- /dev/null
+++ b/io_mesh_stl/blender_utils.py
@@ -0,0 +1,50 @@
+import bpy
+
+
+def create_and_link_mesh(name, faces, points):
+ '''
+ Create a blender mesh and object called name from a list of
+ *points* and *faces* and link it in the current scene.
+ '''
+
+ mesh = bpy.data.meshes.new(name)
+ mesh.from_pydata(points, [], faces)
+
+ ob = bpy.data.objects.new(name, mesh)
+ bpy.context.scene.objects.link(ob)
+
+ # update mesh to allow proper display
+ mesh.update()
+
+
+def faces_from_mesh(ob, apply_modifier=False, triangulate=True):
+ '''
+ From an object, return a generator over a list of faces.
+
+ Each faces is a list of his vertexes. Each vertex is a tuple of
+ his coordinate.
+
+ apply_modifier
+ Apply the preview modifier to the returned liste
+
+ triangulate
+ Split the quad into two triangles
+ '''
+
+ # get the modifiers
+ mesh = ob.create_mesh(bpy.context.scene,
+ True, "PREVIEW") if apply_modifier else ob.data
+
+ def iter_face_index():
+ '''
+ From a list of faces, return the face triangulated if needed.
+ '''
+ for face in mesh.faces:
+ if triangulate and len(face.verts) == 4:
+ yield face.verts[:3]
+ yield face.verts[2:] + [face.verts[0]]
+ else:
+ yield list(face.verts)
+
+ return ([tuple(mesh.verts[index].co)
+ for index in indexes] for indexes in iter_face_index())
diff --git a/io_mesh_stl/stl_utils.py b/io_mesh_stl/stl_utils.py
new file mode 100644
index 00000000..34db045c
--- /dev/null
+++ b/io_mesh_stl/stl_utils.py
@@ -0,0 +1,228 @@
+'''
+Import and export STL files
+
+Used as a blender script, it load all the stl files in the scene:
+
+blender -P stl_utils.py -- file1.stl file2.stl file3.stl ...
+'''
+
+import struct
+import mmap
+import contextlib
+import itertools
+
+# TODO: endien
+
+
+@contextlib.contextmanager
+def mmap_file(filename):
+ '''
+ Context manager over the data of an mmap'ed file (Read ONLY).
+
+
+ Example:
+
+ with mmap_file(filename) as m:
+ m.read()
+ print m[10:50]
+ '''
+ with open(filename, 'rb') as file:
+ # check http://bugs.python.org/issue8046 to have mmap context
+ # manager fixed in python
+ map = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
+ yield map
+ map.close()
+
+
+class ListDict(dict):
+ '''
+ Set struct with order.
+
+ You can:
+ - insert data into without doubles
+ - get the list of data in insertion order with self.list
+
+ Like collections.OrderedDict, but quicker, can be replaced if
+ ODict is optimised.
+ '''
+
+ def __init__(self):
+ dict.__init__(self)
+ self.list = []
+ self._len = 0
+
+ def add(self, item):
+ '''
+ Add a value to the Set, return its position in it.
+ '''
+ value = self.setdefault(item, self._len)
+ if value == self._len:
+ self.list.append(item)
+ self._len += 1
+
+ return value
+
+
+def _binary_read(data):
+ # an stl binary file is
+ # - 80 bytes of description
+ # - 2 bytes of size (unsigned int)
+ # - size triangles :
+ #
+ # - 12 bytes of normal
+ # - 9 * 4 bytes of coordinate (3*3 floats)
+ # - 2 bytes of garbage (usually 0)
+
+ # OFFSET for the first byte of coordinate (headers + first normal bytes)
+ # STRIDE between each triangle (first normal + coordinates + garbage)
+ OFFSET, STRIDE = 84 + 12, 12 * 4 + 2
+
+ # read header size, ignore description
+ size = struct.unpack_from('<I', data, 80)[0]
+ unpack = struct.Struct('<9f').unpack_from
+
+ for i in range(size):
+ # read the points coordinates of each triangle
+ pt = unpack(data, OFFSET + STRIDE * i)
+ yield pt[:3], pt[3:6], pt[6:]
+
+
+def _ascii_read(data):
+ # an stl ascii file is like
+ # HEADER: solid some name
+ # for each face:
+ #
+ # facet normal x y z
+ # outerloop
+ # vertex x y z
+ # vertex x y z
+ # vertex x y z
+ # endloop
+ # endfacet
+
+ # strip header
+ data.readline()
+
+ while True:
+ # strip facet normal // or end
+ data.readline()
+
+ # strip outer loup, in case of ending, break the loop
+ if not data.readline():
+ break
+
+ yield [tuple(map(float, data.readline().split()[1:]))
+ for _ in range(3)]
+
+ # strip facet normalend and outerloop end
+ data.readline()
+ data.readline()
+
+
+def _binary_write(filename, faces):
+ with open(filename, 'wb') as data:
+ # header
+ # we write padding at header begginning to avoid to
+ # call len(list(faces)) which may be expensive
+ data.write(struct.calcsize('<80sI') * b'\0')
+
+ # 3 vertex == 9f
+ pack = struct.Struct('<9f').pack
+ # pad is to remove normal, we do use them
+ pad = b'\0' * struct.calcsize('<3f')
+
+ nb = 0
+ for verts in faces:
+ # write pad as normal + vertexes + pad as attributes
+ data.write(pad + pack(*itertools.chain.from_iterable(verts)))
+ data.write(b'\0\0')
+ nb += 1
+
+ # header, with correct value now
+ data.seek(0)
+ data.write(struct.pack('<80sI', b"Exported from blender", nb))
+
+
+def _ascii_write(filename, faces):
+ with open(filename, 'w') as data:
+ data.write('solid Exported from blender\n')
+
+ for face in faces:
+ data.write('''facet normal 0 0 0\nouter loop\n''')
+ for vert in face:
+ data.write('vertex %f %f %f\n' % vert)
+ data.write('endloop\nendfacet\n')
+
+ data.write('endsolid Exported from blender\n')
+
+
+def write_stl(filename, faces, ascii=False):
+ '''
+ Write a stl file from faces,
+
+ filename
+ output filename
+
+ faces
+ iterable of tuple of 3 vertex, vertex is tuple of 3 coordinates as float
+
+ ascii
+ save the file in ascii format (very huge)
+ '''
+ (_ascii_write if ascii else _binary_write)(filename, faces)
+
+
+def read_stl(filename):
+ '''
+ Return the triangles and points of an stl binary file.
+
+ Please note that this process can take lot of time if the file is
+ huge (~1m30 for a 1 Go stl file on an quad core i7).
+
+ - returns a tuple(triangles, points).
+
+ triangles
+ A list of triangles, each triangle as a tuple of 3 index of
+ point in *points*.
+
+ points
+ An indexed list of points, each point is a tuple of 3 float
+ (xyz).
+
+ Example of use:
+
+ >>> tris, pts = read_stl(filename, lambda x:)
+ >>> pts = list(pts)
+ >>>
+ >>> # print the coordinate of the triangle n
+ >>> print([pts[i] for i in tris[n]])
+ '''
+
+ tris, pts = [], ListDict()
+
+ with mmap_file(filename) as data:
+ # check for ascii or binary
+ gen = _ascii_read if data.read(5) == b'solid' else _binary_read
+
+ for pt in gen(data):
+ # Add the triangle and the point.
+ # If the point is allready in the list of points, the
+ # index returned by pts.add() will be the one from the
+ # first equal point inserted.
+ tris.append([pts.add(p) for p in pt])
+
+ return tris, pts.list
+
+
+if __name__ == '__main__':
+ import sys
+ import bpy
+ from io_mesh_stl import blender_utils
+
+ filenames = sys.argv[sys.argv.index('--') + 1:]
+
+ for filename in filenames:
+ objName = bpy.utils.display_name(filename)
+ tris, pts = read_stl(filename)
+
+ blender_utils.create_and_link_mesh(objName, tris, pts)