1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 "name": "STL format (legacy)",
7 "author": "Guillaume Bouchard (Guillaum)",
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
21 Import-Export STL files (binary or ascii)
23 - Import automatically remove the doubles.
24 - Export can export with/without modifiers applied
29 - Does not handle endian
34 if "stl_utils" in locals():
35 importlib
.reload(stl_utils
)
36 if "blender_utils" in locals():
37 importlib
.reload(blender_utils
)
40 from bpy
.props
import (
48 from bpy_extras
.io_utils
import (
54 from bpy
.types
import (
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"
69 filter_glob
: StringProperty(
73 files
: CollectionProperty(
75 type=OperatorFileListElement
,
77 directory
: StringProperty(
80 global_scale
: FloatProperty(
82 soft_min
=0.001, soft_max
=1000.0,
86 use_scene_unit
: BoolProperty(
88 description
="Apply current scene's unit (as defined by unit scale) to imported data",
91 use_facet_normal
: BoolProperty(
93 description
="Use (import) facet normals (note that this will still give flat shading)",
97 def execute(self
, context
):
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)
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')
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
)
134 def draw(self
, context
):
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"
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
):
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"
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
):
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",
204 global_scale
: FloatProperty(
206 min=0.01, max=1000.0,
209 use_scene_unit
: BoolProperty(
211 description
="Apply current scene's unit (as defined by unit scale) to exported data",
216 description
="Save the file in ASCII file format",
219 use_mesh_modifiers
: BoolProperty(
220 name
="Apply Modifiers",
221 description
="Apply the modifiers before saving",
224 batch_mode
: EnumProperty(
227 ('OFF', "Off", "All data in one file"),
228 ('OBJECT', "Object", "Each object as a file"),
231 global_space
: FloatVectorProperty(
233 description
="Export in this reference space",
239 def check_extension(self
):
240 return self
.batch_mode
== 'OFF'
242 def execute(self
, context
):
245 from mathutils
import Matrix
246 from . import stl_utils
247 from . import blender_utils
249 keywords
= self
.as_keywords(
258 "use_mesh_modifiers",
264 scene
= context
.scene
265 if self
.use_selection
:
266 data_seq
= context
.selected_objects
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
,
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
)
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()
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
)
299 def draw(self
, context
):
303 class STL_PT_export_main(bpy
.types
.Panel
):
304 bl_space_type
= 'FILE_BROWSER'
305 bl_region_type
= 'TOOL_PROPS'
307 bl_parent_id
= "FILE_PT_operator"
308 bl_options
= {'HIDE_HEADER'}
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
):
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'
333 bl_parent_id
= "FILE_PT_operator"
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
):
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"
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
):
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"
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
):
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)")
415 STL_PT_import_transform
,
416 STL_PT_import_geometry
,
419 STL_PT_export_include
,
420 STL_PT_export_transform
,
421 STL_PT_export_geometry
,
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
)
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__":