Import images: add file handler
[blender-addons.git] / io_mesh_stl / __init__.py
blob8d711300eca053e8c43ccf8880f7ee6290bb2599
1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "STL format (legacy)",
7 "author": "Guillaume Bouchard (Guillaum)",
8 "version": (1, 1, 3),
9 "blender": (2, 81, 6),
10 "location": "File > Import-Export",
11 "description": "Import-Export STL files",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_stl.html",
13 "support": 'OFFICIAL',
14 "category": "Import-Export",
18 # @todo write the wiki page
20 """
21 Import-Export STL files (binary or ascii)
23 - Import automatically remove the doubles.
24 - Export can export with/without modifiers applied
26 Issues:
28 Import:
29 - Does not handle endian
30 """
32 if "bpy" in locals():
33 import importlib
34 if "stl_utils" in locals():
35 importlib.reload(stl_utils)
36 if "blender_utils" in locals():
37 importlib.reload(blender_utils)
39 import bpy
40 from bpy.props import (
41 StringProperty,
42 BoolProperty,
43 CollectionProperty,
44 EnumProperty,
45 FloatProperty,
46 FloatVectorProperty,
48 from bpy_extras.io_utils import (
49 ImportHelper,
50 ExportHelper,
51 orientation_helper,
52 axis_conversion,
54 from bpy.types import (
55 Operator,
56 OperatorFileListElement,
60 @orientation_helper(axis_forward='Y', axis_up='Z')
61 class ImportSTL(Operator, ImportHelper):
62 bl_idname = "import_mesh.stl"
63 bl_label = "Import STL (legacy)"
64 bl_description = "Load STL triangle mesh data"
65 bl_options = {'UNDO'}
67 filename_ext = ".stl"
69 filter_glob: StringProperty(
70 default="*.stl",
71 options={'HIDDEN'},
73 files: CollectionProperty(
74 name="File Path",
75 type=OperatorFileListElement,
77 directory: StringProperty(
78 subtype='DIR_PATH',
80 global_scale: FloatProperty(
81 name="Scale",
82 soft_min=0.001, soft_max=1000.0,
83 min=1e-6, max=1e6,
84 default=1.0,
86 use_scene_unit: BoolProperty(
87 name="Scene Unit",
88 description="Apply current scene's unit (as defined by unit scale) to imported data",
89 default=False,
91 use_facet_normal: BoolProperty(
92 name="Facet Normals",
93 description="Use (import) facet normals (note that this will still give flat shading)",
94 default=False,
97 def execute(self, context):
98 import os
99 from mathutils import Matrix
100 from . import stl_utils
101 from . import blender_utils
103 paths = [os.path.join(self.directory, name.name) for name in self.files]
105 scene = context.scene
107 # Take into account scene's unit scale, so that 1 inch in Blender gives 1 inch elsewhere! See T42000.
108 global_scale = self.global_scale
109 if scene.unit_settings.system != 'NONE' and self.use_scene_unit:
110 global_scale /= scene.unit_settings.scale_length
112 global_matrix = axis_conversion(
113 from_forward=self.axis_forward,
114 from_up=self.axis_up,
115 ).to_4x4() @ Matrix.Scale(global_scale, 4)
117 if not paths:
118 paths.append(self.filepath)
120 if bpy.ops.object.mode_set.poll():
121 bpy.ops.object.mode_set(mode='OBJECT')
123 if bpy.ops.object.select_all.poll():
124 bpy.ops.object.select_all(action='DESELECT')
126 for path in paths:
127 objName = bpy.path.display_name_from_filepath(path)
128 tris, tri_nors, pts = stl_utils.read_stl(path)
129 tri_nors = tri_nors if self.use_facet_normal else None
130 blender_utils.create_and_link_mesh(objName, tris, tri_nors, pts, global_matrix)
132 return {'FINISHED'}
134 def draw(self, context):
135 pass
138 class STL_PT_import_transform(bpy.types.Panel):
139 bl_space_type = 'FILE_BROWSER'
140 bl_region_type = 'TOOL_PROPS'
141 bl_label = "Transform"
142 bl_parent_id = "FILE_PT_operator"
144 @classmethod
145 def poll(cls, context):
146 sfile = context.space_data
147 operator = sfile.active_operator
149 return operator.bl_idname == "IMPORT_MESH_OT_stl"
151 def draw(self, context):
152 layout = self.layout
153 layout.use_property_split = True
154 layout.use_property_decorate = False # No animation.
156 sfile = context.space_data
157 operator = sfile.active_operator
159 layout.prop(operator, "global_scale")
160 layout.prop(operator, "use_scene_unit")
162 layout.prop(operator, "axis_forward")
163 layout.prop(operator, "axis_up")
166 class STL_PT_import_geometry(bpy.types.Panel):
167 bl_space_type = 'FILE_BROWSER'
168 bl_region_type = 'TOOL_PROPS'
169 bl_label = "Geometry"
170 bl_parent_id = "FILE_PT_operator"
172 @classmethod
173 def poll(cls, context):
174 sfile = context.space_data
175 operator = sfile.active_operator
177 return operator.bl_idname == "IMPORT_MESH_OT_stl"
179 def draw(self, context):
180 layout = self.layout
181 layout.use_property_split = True
182 layout.use_property_decorate = False # No animation.
184 sfile = context.space_data
185 operator = sfile.active_operator
187 layout.prop(operator, "use_facet_normal")
190 @orientation_helper(axis_forward='Y', axis_up='Z')
191 class ExportSTL(Operator, ExportHelper):
192 bl_idname = "export_mesh.stl"
193 bl_label = "Export STL (legacy)"
194 bl_description = """Save STL triangle mesh data"""
196 filename_ext = ".stl"
197 filter_glob: StringProperty(default="*.stl", options={'HIDDEN'})
199 use_selection: BoolProperty(
200 name="Selection Only",
201 description="Export selected objects only",
202 default=False,
204 global_scale: FloatProperty(
205 name="Scale",
206 min=0.01, max=1000.0,
207 default=1.0,
209 use_scene_unit: BoolProperty(
210 name="Scene Unit",
211 description="Apply current scene's unit (as defined by unit scale) to exported data",
212 default=False,
214 ascii: BoolProperty(
215 name="Ascii",
216 description="Save the file in ASCII file format",
217 default=False,
219 use_mesh_modifiers: BoolProperty(
220 name="Apply Modifiers",
221 description="Apply the modifiers before saving",
222 default=True,
224 batch_mode: EnumProperty(
225 name="Batch Mode",
226 items=(
227 ('OFF', "Off", "All data in one file"),
228 ('OBJECT', "Object", "Each object as a file"),
231 global_space: FloatVectorProperty(
232 name="Global Space",
233 description="Export in this reference space",
234 subtype='MATRIX',
235 size=(4, 4),
238 @property
239 def check_extension(self):
240 return self.batch_mode == 'OFF'
242 def execute(self, context):
243 import os
244 import itertools
245 from mathutils import Matrix
246 from . import stl_utils
247 from . import blender_utils
249 keywords = self.as_keywords(
250 ignore=(
251 "axis_forward",
252 "axis_up",
253 "use_selection",
254 "global_scale",
255 "check_existing",
256 "filter_glob",
257 "use_scene_unit",
258 "use_mesh_modifiers",
259 "batch_mode",
260 "global_space",
264 scene = context.scene
265 if self.use_selection:
266 data_seq = context.selected_objects
267 else:
268 data_seq = scene.objects
270 # Take into account scene's unit scale, so that 1 inch in Blender gives 1 inch elsewhere! See T42000.
271 global_scale = self.global_scale
272 if scene.unit_settings.system != 'NONE' and self.use_scene_unit:
273 global_scale *= scene.unit_settings.scale_length
275 global_matrix = axis_conversion(
276 to_forward=self.axis_forward,
277 to_up=self.axis_up,
278 ).to_4x4() @ Matrix.Scale(global_scale, 4)
280 if self.properties.is_property_set("global_space"):
281 global_matrix = global_matrix @ self.global_space.inverted()
283 if self.batch_mode == 'OFF':
284 faces = itertools.chain.from_iterable(
285 blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers)
286 for ob in data_seq)
288 stl_utils.write_stl(faces=faces, **keywords)
289 elif self.batch_mode == 'OBJECT':
290 prefix = os.path.splitext(self.filepath)[0]
291 keywords_temp = keywords.copy()
292 for ob in data_seq:
293 faces = blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers)
294 keywords_temp["filepath"] = prefix + bpy.path.clean_name(ob.name) + ".stl"
295 stl_utils.write_stl(faces=faces, **keywords_temp)
297 return {'FINISHED'}
299 def draw(self, context):
300 pass
303 class STL_PT_export_main(bpy.types.Panel):
304 bl_space_type = 'FILE_BROWSER'
305 bl_region_type = 'TOOL_PROPS'
306 bl_label = ""
307 bl_parent_id = "FILE_PT_operator"
308 bl_options = {'HIDE_HEADER'}
310 @classmethod
311 def poll(cls, context):
312 sfile = context.space_data
313 operator = sfile.active_operator
315 return operator.bl_idname == "EXPORT_MESH_OT_stl"
317 def draw(self, context):
318 layout = self.layout
319 layout.use_property_split = True
320 layout.use_property_decorate = False # No animation.
322 sfile = context.space_data
323 operator = sfile.active_operator
325 layout.prop(operator, "ascii")
326 layout.prop(operator, "batch_mode")
329 class STL_PT_export_include(bpy.types.Panel):
330 bl_space_type = 'FILE_BROWSER'
331 bl_region_type = 'TOOL_PROPS'
332 bl_label = "Include"
333 bl_parent_id = "FILE_PT_operator"
335 @classmethod
336 def poll(cls, context):
337 sfile = context.space_data
338 operator = sfile.active_operator
340 return operator.bl_idname == "EXPORT_MESH_OT_stl"
342 def draw(self, context):
343 layout = self.layout
344 layout.use_property_split = True
345 layout.use_property_decorate = False # No animation.
347 sfile = context.space_data
348 operator = sfile.active_operator
350 layout.prop(operator, "use_selection")
353 class STL_PT_export_transform(bpy.types.Panel):
354 bl_space_type = 'FILE_BROWSER'
355 bl_region_type = 'TOOL_PROPS'
356 bl_label = "Transform"
357 bl_parent_id = "FILE_PT_operator"
359 @classmethod
360 def poll(cls, context):
361 sfile = context.space_data
362 operator = sfile.active_operator
364 return operator.bl_idname == "EXPORT_MESH_OT_stl"
366 def draw(self, context):
367 layout = self.layout
368 layout.use_property_split = True
369 layout.use_property_decorate = False # No animation.
371 sfile = context.space_data
372 operator = sfile.active_operator
374 layout.prop(operator, "global_scale")
375 layout.prop(operator, "use_scene_unit")
377 layout.prop(operator, "axis_forward")
378 layout.prop(operator, "axis_up")
381 class STL_PT_export_geometry(bpy.types.Panel):
382 bl_space_type = 'FILE_BROWSER'
383 bl_region_type = 'TOOL_PROPS'
384 bl_label = "Geometry"
385 bl_parent_id = "FILE_PT_operator"
387 @classmethod
388 def poll(cls, context):
389 sfile = context.space_data
390 operator = sfile.active_operator
392 return operator.bl_idname == "EXPORT_MESH_OT_stl"
394 def draw(self, context):
395 layout = self.layout
396 layout.use_property_split = True
397 layout.use_property_decorate = False # No animation.
399 sfile = context.space_data
400 operator = sfile.active_operator
402 layout.prop(operator, "use_mesh_modifiers")
405 def menu_import(self, context):
406 self.layout.operator(ImportSTL.bl_idname, text="Stl (.stl) (legacy)")
409 def menu_export(self, context):
410 self.layout.operator(ExportSTL.bl_idname, text="Stl (.stl) (legacy)")
413 classes = (
414 ImportSTL,
415 STL_PT_import_transform,
416 STL_PT_import_geometry,
417 ExportSTL,
418 STL_PT_export_main,
419 STL_PT_export_include,
420 STL_PT_export_transform,
421 STL_PT_export_geometry,
425 def register():
426 for cls in classes:
427 bpy.utils.register_class(cls)
429 bpy.types.TOPBAR_MT_file_import.append(menu_import)
430 bpy.types.TOPBAR_MT_file_export.append(menu_export)
433 def unregister():
434 for cls in classes:
435 bpy.utils.unregister_class(cls)
437 bpy.types.TOPBAR_MT_file_import.remove(menu_import)
438 bpy.types.TOPBAR_MT_file_export.remove(menu_export)
441 if __name__ == "__main__":
442 register()