Merge branch 'blender-v3.3-release'
[blender-addons.git] / io_mesh_stl / __init__.py
blobe68a262bb5a4d1f4b2b665d97d3b096fbb5b6032
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "STL format",
5 "author": "Guillaume Bouchard (Guillaum)",
6 "version": (1, 1, 3),
7 "blender": (2, 81, 6),
8 "location": "File > Import-Export",
9 "description": "Import-Export STL files",
10 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_stl.html",
11 "support": 'OFFICIAL',
12 "category": "Import-Export",
16 # @todo write the wiki page
18 """
19 Import-Export STL files (binary or ascii)
21 - Import automatically remove the doubles.
22 - Export can export with/without modifiers applied
24 Issues:
26 Import:
27 - Does not handle endian
28 """
30 if "bpy" in locals():
31 import importlib
32 if "stl_utils" in locals():
33 importlib.reload(stl_utils)
34 if "blender_utils" in locals():
35 importlib.reload(blender_utils)
37 import bpy
38 from bpy.props import (
39 StringProperty,
40 BoolProperty,
41 CollectionProperty,
42 EnumProperty,
43 FloatProperty,
44 FloatVectorProperty,
46 from bpy_extras.io_utils import (
47 ImportHelper,
48 ExportHelper,
49 orientation_helper,
50 axis_conversion,
52 from bpy.types import (
53 Operator,
54 OperatorFileListElement,
58 @orientation_helper(axis_forward='Y', axis_up='Z')
59 class ImportSTL(Operator, ImportHelper):
60 bl_idname = "import_mesh.stl"
61 bl_label = "Import STL"
62 bl_description = "Load STL triangle mesh data"
63 bl_options = {'UNDO'}
65 filename_ext = ".stl"
67 filter_glob: StringProperty(
68 default="*.stl",
69 options={'HIDDEN'},
71 files: CollectionProperty(
72 name="File Path",
73 type=OperatorFileListElement,
75 directory: StringProperty(
76 subtype='DIR_PATH',
78 global_scale: FloatProperty(
79 name="Scale",
80 soft_min=0.001, soft_max=1000.0,
81 min=1e-6, max=1e6,
82 default=1.0,
84 use_scene_unit: BoolProperty(
85 name="Scene Unit",
86 description="Apply current scene's unit (as defined by unit scale) to imported data",
87 default=False,
89 use_facet_normal: BoolProperty(
90 name="Facet Normals",
91 description="Use (import) facet normals (note that this will still give flat shading)",
92 default=False,
95 def execute(self, context):
96 import os
97 from mathutils import Matrix
98 from . import stl_utils
99 from . import blender_utils
101 paths = [os.path.join(self.directory, name.name) for name in self.files]
103 scene = context.scene
105 # Take into account scene's unit scale, so that 1 inch in Blender gives 1 inch elsewhere! See T42000.
106 global_scale = self.global_scale
107 if scene.unit_settings.system != 'NONE' and self.use_scene_unit:
108 global_scale /= scene.unit_settings.scale_length
110 global_matrix = axis_conversion(
111 from_forward=self.axis_forward,
112 from_up=self.axis_up,
113 ).to_4x4() @ Matrix.Scale(global_scale, 4)
115 if not paths:
116 paths.append(self.filepath)
118 if bpy.ops.object.mode_set.poll():
119 bpy.ops.object.mode_set(mode='OBJECT')
121 if bpy.ops.object.select_all.poll():
122 bpy.ops.object.select_all(action='DESELECT')
124 for path in paths:
125 objName = bpy.path.display_name_from_filepath(path)
126 tris, tri_nors, pts = stl_utils.read_stl(path)
127 tri_nors = tri_nors if self.use_facet_normal else None
128 blender_utils.create_and_link_mesh(objName, tris, tri_nors, pts, global_matrix)
130 return {'FINISHED'}
132 def draw(self, context):
133 pass
136 class STL_PT_import_transform(bpy.types.Panel):
137 bl_space_type = 'FILE_BROWSER'
138 bl_region_type = 'TOOL_PROPS'
139 bl_label = "Transform"
140 bl_parent_id = "FILE_PT_operator"
142 @classmethod
143 def poll(cls, context):
144 sfile = context.space_data
145 operator = sfile.active_operator
147 return operator.bl_idname == "IMPORT_MESH_OT_stl"
149 def draw(self, context):
150 layout = self.layout
151 layout.use_property_split = True
152 layout.use_property_decorate = False # No animation.
154 sfile = context.space_data
155 operator = sfile.active_operator
157 layout.prop(operator, "global_scale")
158 layout.prop(operator, "use_scene_unit")
160 layout.prop(operator, "axis_forward")
161 layout.prop(operator, "axis_up")
164 class STL_PT_import_geometry(bpy.types.Panel):
165 bl_space_type = 'FILE_BROWSER'
166 bl_region_type = 'TOOL_PROPS'
167 bl_label = "Geometry"
168 bl_parent_id = "FILE_PT_operator"
170 @classmethod
171 def poll(cls, context):
172 sfile = context.space_data
173 operator = sfile.active_operator
175 return operator.bl_idname == "IMPORT_MESH_OT_stl"
177 def draw(self, context):
178 layout = self.layout
179 layout.use_property_split = True
180 layout.use_property_decorate = False # No animation.
182 sfile = context.space_data
183 operator = sfile.active_operator
185 layout.prop(operator, "use_facet_normal")
188 @orientation_helper(axis_forward='Y', axis_up='Z')
189 class ExportSTL(Operator, ExportHelper):
190 bl_idname = "export_mesh.stl"
191 bl_label = "Export STL"
192 bl_description = """Save STL triangle mesh data"""
194 filename_ext = ".stl"
195 filter_glob: StringProperty(default="*.stl", options={'HIDDEN'})
197 use_selection: BoolProperty(
198 name="Selection Only",
199 description="Export selected objects only",
200 default=False,
202 global_scale: FloatProperty(
203 name="Scale",
204 min=0.01, max=1000.0,
205 default=1.0,
207 use_scene_unit: BoolProperty(
208 name="Scene Unit",
209 description="Apply current scene's unit (as defined by unit scale) to exported data",
210 default=False,
212 ascii: BoolProperty(
213 name="Ascii",
214 description="Save the file in ASCII file format",
215 default=False,
217 use_mesh_modifiers: BoolProperty(
218 name="Apply Modifiers",
219 description="Apply the modifiers before saving",
220 default=True,
222 batch_mode: EnumProperty(
223 name="Batch Mode",
224 items=(
225 ('OFF', "Off", "All data in one file"),
226 ('OBJECT', "Object", "Each object as a file"),
229 global_space: FloatVectorProperty(
230 name="Global Space",
231 description="Export in this reference space",
232 subtype='MATRIX',
233 size=(4, 4),
236 @property
237 def check_extension(self):
238 return self.batch_mode == 'OFF'
240 def execute(self, context):
241 import os
242 import itertools
243 from mathutils import Matrix
244 from . import stl_utils
245 from . import blender_utils
247 keywords = self.as_keywords(
248 ignore=(
249 "axis_forward",
250 "axis_up",
251 "use_selection",
252 "global_scale",
253 "check_existing",
254 "filter_glob",
255 "use_scene_unit",
256 "use_mesh_modifiers",
257 "batch_mode",
258 "global_space",
262 scene = context.scene
263 if self.use_selection:
264 data_seq = context.selected_objects
265 else:
266 data_seq = scene.objects
268 # Take into account scene's unit scale, so that 1 inch in Blender gives 1 inch elsewhere! See T42000.
269 global_scale = self.global_scale
270 if scene.unit_settings.system != 'NONE' and self.use_scene_unit:
271 global_scale *= scene.unit_settings.scale_length
273 global_matrix = axis_conversion(
274 to_forward=self.axis_forward,
275 to_up=self.axis_up,
276 ).to_4x4() @ Matrix.Scale(global_scale, 4)
278 if self.properties.is_property_set("global_space"):
279 global_matrix = global_matrix @ self.global_space.inverted()
281 if self.batch_mode == 'OFF':
282 faces = itertools.chain.from_iterable(
283 blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers)
284 for ob in data_seq)
286 stl_utils.write_stl(faces=faces, **keywords)
287 elif self.batch_mode == 'OBJECT':
288 prefix = os.path.splitext(self.filepath)[0]
289 keywords_temp = keywords.copy()
290 for ob in data_seq:
291 faces = blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers)
292 keywords_temp["filepath"] = prefix + bpy.path.clean_name(ob.name) + ".stl"
293 stl_utils.write_stl(faces=faces, **keywords_temp)
295 return {'FINISHED'}
297 def draw(self, context):
298 pass
301 class STL_PT_export_main(bpy.types.Panel):
302 bl_space_type = 'FILE_BROWSER'
303 bl_region_type = 'TOOL_PROPS'
304 bl_label = ""
305 bl_parent_id = "FILE_PT_operator"
306 bl_options = {'HIDE_HEADER'}
308 @classmethod
309 def poll(cls, context):
310 sfile = context.space_data
311 operator = sfile.active_operator
313 return operator.bl_idname == "EXPORT_MESH_OT_stl"
315 def draw(self, context):
316 layout = self.layout
317 layout.use_property_split = True
318 layout.use_property_decorate = False # No animation.
320 sfile = context.space_data
321 operator = sfile.active_operator
323 layout.prop(operator, "ascii")
324 layout.prop(operator, "batch_mode")
327 class STL_PT_export_include(bpy.types.Panel):
328 bl_space_type = 'FILE_BROWSER'
329 bl_region_type = 'TOOL_PROPS'
330 bl_label = "Include"
331 bl_parent_id = "FILE_PT_operator"
333 @classmethod
334 def poll(cls, context):
335 sfile = context.space_data
336 operator = sfile.active_operator
338 return operator.bl_idname == "EXPORT_MESH_OT_stl"
340 def draw(self, context):
341 layout = self.layout
342 layout.use_property_split = True
343 layout.use_property_decorate = False # No animation.
345 sfile = context.space_data
346 operator = sfile.active_operator
348 layout.prop(operator, "use_selection")
351 class STL_PT_export_transform(bpy.types.Panel):
352 bl_space_type = 'FILE_BROWSER'
353 bl_region_type = 'TOOL_PROPS'
354 bl_label = "Transform"
355 bl_parent_id = "FILE_PT_operator"
357 @classmethod
358 def poll(cls, context):
359 sfile = context.space_data
360 operator = sfile.active_operator
362 return operator.bl_idname == "EXPORT_MESH_OT_stl"
364 def draw(self, context):
365 layout = self.layout
366 layout.use_property_split = True
367 layout.use_property_decorate = False # No animation.
369 sfile = context.space_data
370 operator = sfile.active_operator
372 layout.prop(operator, "global_scale")
373 layout.prop(operator, "use_scene_unit")
375 layout.prop(operator, "axis_forward")
376 layout.prop(operator, "axis_up")
379 class STL_PT_export_geometry(bpy.types.Panel):
380 bl_space_type = 'FILE_BROWSER'
381 bl_region_type = 'TOOL_PROPS'
382 bl_label = "Geometry"
383 bl_parent_id = "FILE_PT_operator"
385 @classmethod
386 def poll(cls, context):
387 sfile = context.space_data
388 operator = sfile.active_operator
390 return operator.bl_idname == "EXPORT_MESH_OT_stl"
392 def draw(self, context):
393 layout = self.layout
394 layout.use_property_split = True
395 layout.use_property_decorate = False # No animation.
397 sfile = context.space_data
398 operator = sfile.active_operator
400 layout.prop(operator, "use_mesh_modifiers")
403 def menu_import(self, context):
404 self.layout.operator(ImportSTL.bl_idname, text="Stl (.stl)")
407 def menu_export(self, context):
408 self.layout.operator(ExportSTL.bl_idname, text="Stl (.stl)")
411 classes = (
412 ImportSTL,
413 STL_PT_import_transform,
414 STL_PT_import_geometry,
415 ExportSTL,
416 STL_PT_export_main,
417 STL_PT_export_include,
418 STL_PT_export_transform,
419 STL_PT_export_geometry,
423 def register():
424 for cls in classes:
425 bpy.utils.register_class(cls)
427 bpy.types.TOPBAR_MT_file_import.append(menu_import)
428 bpy.types.TOPBAR_MT_file_export.append(menu_export)
431 def unregister():
432 for cls in classes:
433 bpy.utils.unregister_class(cls)
435 bpy.types.TOPBAR_MT_file_import.remove(menu_import)
436 bpy.types.TOPBAR_MT_file_export.remove(menu_export)
439 if __name__ == "__main__":
440 register()