Files
blender-addons/io_mesh_stl/__init__.py
2023-12-23 23:08:30 +02:00

443 lines
12 KiB
Python

# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
bl_info = {
"name": "STL format (legacy)",
"author": "Guillaume Bouchard (Guillaum)",
"version": (1, 1, 3),
"blender": (2, 81, 6),
"location": "File > Import-Export",
"description": "Import-Export STL files",
"doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_stl.html",
"support": 'OFFICIAL',
"category": "Import-Export",
}
# @todo write the wiki page
"""
Import-Export STL files (binary or ascii)
- Import automatically remove the doubles.
- Export can export with/without modifiers applied
Issues:
Import:
- Does not handle endian
"""
if "bpy" in locals():
import importlib
if "stl_utils" in locals():
importlib.reload(stl_utils)
if "blender_utils" in locals():
importlib.reload(blender_utils)
import bpy
from bpy.props import (
StringProperty,
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
)
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
orientation_helper,
axis_conversion,
)
from bpy.types import (
Operator,
OperatorFileListElement,
)
@orientation_helper(axis_forward='Y', axis_up='Z')
class ImportSTL(Operator, ImportHelper):
bl_idname = "import_mesh.stl"
bl_label = "Import STL (legacy)"
bl_description = "Load STL triangle mesh data"
bl_options = {'UNDO'}
filename_ext = ".stl"
filter_glob: StringProperty(
default="*.stl",
options={'HIDDEN'},
)
files: CollectionProperty(
name="File Path",
type=OperatorFileListElement,
)
directory: StringProperty(
subtype='DIR_PATH',
)
global_scale: FloatProperty(
name="Scale",
soft_min=0.001, soft_max=1000.0,
min=1e-6, max=1e6,
default=1.0,
)
use_scene_unit: BoolProperty(
name="Scene Unit",
description="Apply current scene's unit (as defined by unit scale) to imported data",
default=False,
)
use_facet_normal: BoolProperty(
name="Facet Normals",
description="Use (import) facet normals (note that this will still give flat shading)",
default=False,
)
def execute(self, context):
import os
from mathutils import Matrix
from . import stl_utils
from . import blender_utils
paths = [os.path.join(self.directory, name.name) for name in self.files]
scene = context.scene
# Take into account scene's unit scale, so that 1 inch in Blender gives 1 inch elsewhere! See T42000.
global_scale = self.global_scale
if scene.unit_settings.system != 'NONE' and self.use_scene_unit:
global_scale /= scene.unit_settings.scale_length
global_matrix = axis_conversion(
from_forward=self.axis_forward,
from_up=self.axis_up,
).to_4x4() @ Matrix.Scale(global_scale, 4)
if not paths:
paths.append(self.filepath)
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
if bpy.ops.object.select_all.poll():
bpy.ops.object.select_all(action='DESELECT')
for path in paths:
objName = bpy.path.display_name_from_filepath(path)
tris, tri_nors, pts = stl_utils.read_stl(path)
tri_nors = tri_nors if self.use_facet_normal else None
blender_utils.create_and_link_mesh(objName, tris, tri_nors, pts, global_matrix)
return {'FINISHED'}
def draw(self, context):
pass
class STL_PT_import_transform(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
bl_label = "Transform"
bl_parent_id = "FILE_PT_operator"
@classmethod
def poll(cls, context):
sfile = context.space_data
operator = sfile.active_operator
return operator.bl_idname == "IMPORT_MESH_OT_stl"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
sfile = context.space_data
operator = sfile.active_operator
layout.prop(operator, "global_scale")
layout.prop(operator, "use_scene_unit")
layout.prop(operator, "axis_forward")
layout.prop(operator, "axis_up")
class STL_PT_import_geometry(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
bl_label = "Geometry"
bl_parent_id = "FILE_PT_operator"
@classmethod
def poll(cls, context):
sfile = context.space_data
operator = sfile.active_operator
return operator.bl_idname == "IMPORT_MESH_OT_stl"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
sfile = context.space_data
operator = sfile.active_operator
layout.prop(operator, "use_facet_normal")
@orientation_helper(axis_forward='Y', axis_up='Z')
class ExportSTL(Operator, ExportHelper):
bl_idname = "export_mesh.stl"
bl_label = "Export STL (legacy)"
bl_description = """Save STL triangle mesh data"""
filename_ext = ".stl"
filter_glob: StringProperty(default="*.stl", options={'HIDDEN'})
use_selection: BoolProperty(
name="Selection Only",
description="Export selected objects only",
default=False,
)
global_scale: FloatProperty(
name="Scale",
min=0.01, max=1000.0,
default=1.0,
)
use_scene_unit: BoolProperty(
name="Scene Unit",
description="Apply current scene's unit (as defined by unit scale) to exported data",
default=False,
)
ascii: BoolProperty(
name="Ascii",
description="Save the file in ASCII file format",
default=False,
)
use_mesh_modifiers: BoolProperty(
name="Apply Modifiers",
description="Apply the modifiers before saving",
default=True,
)
batch_mode: EnumProperty(
name="Batch Mode",
items=(
('OFF', "Off", "All data in one file"),
('OBJECT', "Object", "Each object as a file"),
),
)
global_space: FloatVectorProperty(
name="Global Space",
description="Export in this reference space",
subtype='MATRIX',
size=(4, 4),
)
@property
def check_extension(self):
return self.batch_mode == 'OFF'
def execute(self, context):
import os
import itertools
from mathutils import Matrix
from . import stl_utils
from . import blender_utils
keywords = self.as_keywords(
ignore=(
"axis_forward",
"axis_up",
"use_selection",
"global_scale",
"check_existing",
"filter_glob",
"use_scene_unit",
"use_mesh_modifiers",
"batch_mode",
"global_space",
),
)
scene = context.scene
if self.use_selection:
data_seq = context.selected_objects
else:
data_seq = scene.objects
# Take into account scene's unit scale, so that 1 inch in Blender gives 1 inch elsewhere! See T42000.
global_scale = self.global_scale
if scene.unit_settings.system != 'NONE' and self.use_scene_unit:
global_scale *= scene.unit_settings.scale_length
global_matrix = axis_conversion(
to_forward=self.axis_forward,
to_up=self.axis_up,
).to_4x4() @ Matrix.Scale(global_scale, 4)
if self.properties.is_property_set("global_space"):
global_matrix = global_matrix @ self.global_space.inverted()
if self.batch_mode == 'OFF':
faces = itertools.chain.from_iterable(
blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers)
for ob in data_seq)
stl_utils.write_stl(faces=faces, **keywords)
elif self.batch_mode == 'OBJECT':
prefix = os.path.splitext(self.filepath)[0]
keywords_temp = keywords.copy()
for ob in data_seq:
faces = blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers)
keywords_temp["filepath"] = prefix + bpy.path.clean_name(ob.name) + ".stl"
stl_utils.write_stl(faces=faces, **keywords_temp)
return {'FINISHED'}
def draw(self, context):
pass
class STL_PT_export_main(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
bl_label = ""
bl_parent_id = "FILE_PT_operator"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
sfile = context.space_data
operator = sfile.active_operator
return operator.bl_idname == "EXPORT_MESH_OT_stl"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
sfile = context.space_data
operator = sfile.active_operator
layout.prop(operator, "ascii")
layout.prop(operator, "batch_mode")
class STL_PT_export_include(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
bl_label = "Include"
bl_parent_id = "FILE_PT_operator"
@classmethod
def poll(cls, context):
sfile = context.space_data
operator = sfile.active_operator
return operator.bl_idname == "EXPORT_MESH_OT_stl"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
sfile = context.space_data
operator = sfile.active_operator
layout.prop(operator, "use_selection")
class STL_PT_export_transform(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
bl_label = "Transform"
bl_parent_id = "FILE_PT_operator"
@classmethod
def poll(cls, context):
sfile = context.space_data
operator = sfile.active_operator
return operator.bl_idname == "EXPORT_MESH_OT_stl"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
sfile = context.space_data
operator = sfile.active_operator
layout.prop(operator, "global_scale")
layout.prop(operator, "use_scene_unit")
layout.prop(operator, "axis_forward")
layout.prop(operator, "axis_up")
class STL_PT_export_geometry(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
bl_label = "Geometry"
bl_parent_id = "FILE_PT_operator"
@classmethod
def poll(cls, context):
sfile = context.space_data
operator = sfile.active_operator
return operator.bl_idname == "EXPORT_MESH_OT_stl"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
sfile = context.space_data
operator = sfile.active_operator
layout.prop(operator, "use_mesh_modifiers")
def menu_import(self, context):
self.layout.operator(ImportSTL.bl_idname, text="Stl (.stl) (legacy)")
def menu_export(self, context):
self.layout.operator(ExportSTL.bl_idname, text="Stl (.stl) (legacy)")
classes = (
ImportSTL,
STL_PT_import_transform,
STL_PT_import_geometry,
ExportSTL,
STL_PT_export_main,
STL_PT_export_include,
STL_PT_export_transform,
STL_PT_export_geometry,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.TOPBAR_MT_file_import.append(menu_import)
bpy.types.TOPBAR_MT_file_export.append(menu_export)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
bpy.types.TOPBAR_MT_file_import.remove(menu_import)
bpy.types.TOPBAR_MT_file_export.remove(menu_export)
if __name__ == "__main__":
register()