1 # SPDX-License-Identifier: GPL-2.0-or-later
5 "author": "Guillaume Bouchard (Guillaum)",
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
19 Import-Export STL files (binary or ascii)
21 - Import automatically remove the doubles.
22 - Export can export with/without modifiers applied
27 - Does not handle endian
32 if "stl_utils" in locals():
33 importlib
.reload(stl_utils
)
34 if "blender_utils" in locals():
35 importlib
.reload(blender_utils
)
38 from bpy
.props
import (
46 from bpy_extras
.io_utils
import (
52 from bpy
.types
import (
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"
67 filter_glob
: StringProperty(
71 files
: CollectionProperty(
73 type=OperatorFileListElement
,
75 directory
: StringProperty(
78 global_scale
: FloatProperty(
80 soft_min
=0.001, soft_max
=1000.0,
84 use_scene_unit
: BoolProperty(
86 description
="Apply current scene's unit (as defined by unit scale) to imported data",
89 use_facet_normal
: BoolProperty(
91 description
="Use (import) facet normals (note that this will still give flat shading)",
95 def execute(self
, context
):
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)
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')
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
)
132 def draw(self
, context
):
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"
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
):
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"
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
):
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",
202 global_scale
: FloatProperty(
204 min=0.01, max=1000.0,
207 use_scene_unit
: BoolProperty(
209 description
="Apply current scene's unit (as defined by unit scale) to exported data",
214 description
="Save the file in ASCII file format",
217 use_mesh_modifiers
: BoolProperty(
218 name
="Apply Modifiers",
219 description
="Apply the modifiers before saving",
222 batch_mode
: EnumProperty(
225 ('OFF', "Off", "All data in one file"),
226 ('OBJECT', "Object", "Each object as a file"),
229 global_space
: FloatVectorProperty(
231 description
="Export in this reference space",
237 def check_extension(self
):
238 return self
.batch_mode
== 'OFF'
240 def execute(self
, context
):
243 from mathutils
import Matrix
244 from . import stl_utils
245 from . import blender_utils
247 keywords
= self
.as_keywords(
256 "use_mesh_modifiers",
262 scene
= context
.scene
263 if self
.use_selection
:
264 data_seq
= context
.selected_objects
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
,
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
)
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()
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
)
297 def draw(self
, context
):
301 class STL_PT_export_main(bpy
.types
.Panel
):
302 bl_space_type
= 'FILE_BROWSER'
303 bl_region_type
= 'TOOL_PROPS'
305 bl_parent_id
= "FILE_PT_operator"
306 bl_options
= {'HIDE_HEADER'}
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
):
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'
331 bl_parent_id
= "FILE_PT_operator"
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
):
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"
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
):
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"
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
):
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)")
413 STL_PT_import_transform
,
414 STL_PT_import_geometry
,
417 STL_PT_export_include
,
418 STL_PT_export_transform
,
419 STL_PT_export_geometry
,
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
)
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__":