1
$\begingroup$

My stl_directory field is working properly but if I save the .blend file it doesn't. It looks like this:"//....\Downloads\stl\upper2.stl" when it should look like this: "C:\Users\migue\Downloads\stl\upper2.stl".

Could somebody help me?

enter image description here


import bpy
import os 

bl_info = {
    "name": "Guard Generator",
    "description": "Import an STL file and add a Geometry Nodes modifier to it to generate a guard.",
    "author": "BRON",
    "version": (1, 0, 0),
    "blender": (3, 6, 0),
    "location": "View3D > Tools",
    "category": "Import-Export"
}

class OBJECT_PG_custom_properties(bpy.types.PropertyGroup):

    stl_directory : bpy.props.StringProperty(
        name="STL Directory",
        subtype='FILE_PATH'
    )

    yaw_type : bpy.props.EnumProperty(
        name="Yaw Type",
        items=(
            ("MAXILLARY", "Maxillary", ""),
            ("MANDIBLE", "Mandible", "")
        ),
        default="MAXILLARY"
    )

    num_pieces : bpy.props.EnumProperty(
        name="Number of Pieces",
        items=(
            ("12", "12 Pieces", ""),
            ("14", "14 Pieces", ""),
            ("16", "16 Pieces", "")
        ),
        default="14"
    )


class OBJECT_PT_custom_panel(bpy.types.Panel):
    bl_label = "Guard Generator"
    bl_idname = "OBJECT_PT_custom_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Guard Generator'

    def draw(self, context):
        layout = self.layout
        
        custom_properties = context.scene.custom_properties

        box = layout.box()
        box.prop(custom_properties, "stl_directory")
        row = box.row()
        row.prop(custom_properties, "yaw_type", expand=True)
        row = box.row()
        row.prop(custom_properties, "num_pieces", expand=True)
        row = box.row()
        row.operator("import_mesh.stl", text="Import", icon="IMPORT")
        row = box.row()
        row.operator("export_mesh.stl", text="Export", icon="EXPORT").filepath = os.path.join(bpy.path.abspath(custom_properties.stl_directory), f"{context.active_object.name}.stl")


class ImportSTL(bpy.types.Operator):
    bl_idname = "import_mesh.my_stl_importer"
    bl_label = "Import STL"

    def execute(self, context):
        if context.scene.custom_properties.stl_directory:


            # Check if there is an object called "MiddlePlane" in the scene
            middle_plane_exists = False
            for obj in bpy.data.objects:
                if obj.name == "MiddlePlane":
                    middle_plane_exists = True

            # If there isn't an object called "MiddlePlane" in the scene, create one and add the geometry nodes modifier called "GN_MiddlePlane" to it
            if not middle_plane_exists:
                bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0))
                bpy.context.active_object.name = "MiddlePlane"
                middle_plane = bpy.context.active_object
                middle_plane.modifiers.new(name="GN_MiddlePlane", type="NODES")
                middle_plane.modifiers["GN_MiddlePlane"].node_group = bpy.data.node_groups["GN_MiddlePlane"]

                # Add a wireframe modifier for better visualization
                wireframe_modifier = middle_plane.modifiers.new(name="Wireframe", type='WIREFRAME')
                wireframe_modifier.thickness = 0.004
                wireframe_modifier.use_even_offset = True
                wireframe_modifier.use_replace = False
                
                
            # Load the STL file
            bpy.ops.import_mesh.stl(filepath=context.scene.custom_properties.stl_directory)
            imported_object = bpy.context.selected_objects[0]

            # Change the imported object's scale to 0.01 and apply the scale
            imported_object.scale = (0.01, 0.01, 0.01)
            bpy.ops.object.transform_apply(scale=True)

            # Add a weld modifier and a decimate modifier to the object and apply both of them
            bpy.ops.object.modifier_add(type='WELD')
            bpy.context.object.modifiers["Weld"].merge_threshold = 0.00015
            bpy.ops.object.modifier_add(type='REMESH')
            bpy.context.object.modifiers["Remesh"].mode = 'SHARP'
            bpy.context.object.modifiers["Remesh"].octree_depth = 8
            bpy.ops.object.modifier_add(type='DECIMATE')
            bpy.context.object.modifiers["Decimate"].ratio = 0.3
            bpy.ops.object.modifier_apply(modifier="Weld")
            bpy.ops.object.modifier_apply(modifier="Remesh")
            bpy.ops.object.modifier_apply(modifier="Decimate")

            # Add the Geometry Nodes modifier based on the user selection
            if context.scene.custom_properties.yaw_type == "MAXILLARY":
                obj = context.view_layer.objects.active
                obj.modifiers.new(name="Maxillary", type="NODES")
                if context.scene.custom_properties.num_pieces == "12":
                    obj.modifiers["Maxillary"].node_group = bpy.data.node_groups["GN_Maxillar_12"]
                elif context.scene.custom_properties.num_pieces == "14":
                    obj.modifiers["Maxillary"].node_group = bpy.data.node_groups["GN_Maxillar_14"]
                elif context.scene.custom_properties.num_pieces == "16":
                    obj.modifiers["Maxillary"].node_group = bpy.data.node_groups["GN_Maxillar_16"]

            elif context.scene.custom_properties.yaw_type == "MANDIBLE":
                obj = context.view_layer.objects.active
                obj.modifiers.new(name="Mandible", type="NODES")
                if context.scene.custom_properties.num_pieces == "12":
                    obj.modifiers["Mandible"].node_group = bpy.data.node_groups["GN_Mandible_12"]
                elif context.scene.custom_properties.num_pieces == "14":
                    obj.modifiers["Mandible"].node_group = bpy.data.node_groups["GN_Mandible_14"]
                elif context.scene.custom_properties.num_pieces == "16":
                    obj.modifiers["Mandible"].node_group = bpy.data.node_groups["GN_Mandible_16"]

        else:
            self.report({"WARNING"}, "Please, select an .stl first")

        return {'FINISHED'}
    
class ExportSTL(bpy.types.Operator):
    bl_idname = "export_mesh.my_stl_exporter"
    bl_label = "Export STL"

    filepath: bpy.props.StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        # Export the STL file with the default options
        bpy.ops.export_mesh.stl(filepath=self.filepath, check_existing=False, use_selection=True)
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(OBJECT_PG_custom_properties)
    bpy.utils.register_class(OBJECT_PT_custom_panel)
    bpy.utils.register_class(ImportSTL)
    bpy.utils.register_class(ExportSTL)
    
    bpy.types.Scene.custom_properties = bpy.props.PointerProperty(type=OBJECT_PG_custom_properties)
    
    
def unregister():
    bpy.utils.unregister_class(OBJECT_PG_custom_properties)
    bpy.utils.unregister_class(OBJECT_PT_custom_panel)
    bpy.utils.register_class(ImportSTL)
    bpy.utils.register_class(ExportSTL)
    
    del bpy.types.Scene.custom_properties
    
if __name__ == "__main__":
    register()

$\endgroup$
0

1 Answer 1

2
$\begingroup$

Use update() to update the relative path to the absolute path.

API: https://docs.blender.org/api/current/bpy.props.html#bpy.props.StringProperty

import bpy
import os 


bl_info = {
    "name": "Guard Generator",
    "description": "Import an STL file and add a Geometry Nodes modifier to it to generate a guard.",
    "author": "BRON",
    "version": (1, 0, 0),
    "blender": (3, 6, 0),
    "location": "View3D > Tools",
    "category": "Import-Export"
}


class OBJECT_PG_custom_properties(bpy.types.PropertyGroup):
    
    def update_path(self, context):
        if self.stl_directory:
            self.stl_directory = os.path.abspath(bpy.path.abspath(self.stl_directory))
        
    stl_directory : bpy.props.StringProperty(
        name = "STL Directory",
        subtype = 'FILE_PATH',
        update = update_path
    )

    yaw_type : bpy.props.EnumProperty(
        name="Yaw Type",
        items=(
            ("MAXILLARY", "Maxillary", ""),
            ("MANDIBLE", "Mandible", "")
        ),
        default="MAXILLARY"
    )

    num_pieces : bpy.props.EnumProperty(
        name="Number of Pieces",
        items=(
            ("12", "12 Pieces", ""),
            ("14", "14 Pieces", ""),
            ("16", "16 Pieces", "")
        ),
        default="14"
    )


class OBJECT_PT_custom_panel(bpy.types.Panel):
    bl_label = "Guard Generator"
    bl_idname = "OBJECT_PT_custom_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Guard Generator'

    def draw(self, context):
        layout = self.layout
        
        custom_properties = context.scene.custom_properties

        box = layout.box()
        box.prop(custom_properties, "stl_directory")
        row = box.row()
        row.prop(custom_properties, "yaw_type", expand=True)
        row = box.row()
        row.prop(custom_properties, "num_pieces", expand=True)
        row = box.row()
        row.operator("import_mesh.my_stl_importer", text="Import", icon="IMPORT")
        row = box.row()
        row.operator("export_mesh.my_stl_exporter", text="Export", icon="EXPORT").filepath = os.path.join(bpy.path.abspath(custom_properties.stl_directory), f"{context.active_object.name}.stl")


class OBJECT_OT_import_STL(bpy.types.Operator):
    bl_idname = "import_mesh.my_stl_importer"
    bl_label = "Import STL"

    def execute(self, context):
        if context.scene.custom_properties.stl_directory:

            # Check if there is an object called "MiddlePlane" in the scene
            middle_plane_exists = False
            for obj in bpy.data.objects:
                if obj.name == "MiddlePlane":
                    middle_plane_exists = True

            # If there isn't an object called "MiddlePlane" in the scene, create one and add the geometry nodes modifier called "GN_MiddlePlane" to it
            if not middle_plane_exists:
                bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0))
                bpy.context.active_object.name = "MiddlePlane"
                middle_plane = bpy.context.active_object
                middle_plane.modifiers.new(name="GN_MiddlePlane", type="NODES")
                middle_plane.modifiers["GN_MiddlePlane"].node_group = bpy.data.node_groups["GN_MiddlePlane"]

                # Add a wireframe modifier for better visualization
                wireframe_modifier = middle_plane.modifiers.new(name="Wireframe", type='WIREFRAME')
                wireframe_modifier.thickness = 0.004
                wireframe_modifier.use_even_offset = True
                wireframe_modifier.use_replace = False
                
                
            # Load the STL file
            bpy.ops.import_mesh.stl(filepath=context.scene.custom_properties.stl_directory)
            imported_object = bpy.context.selected_objects[0]

            # Change the imported object's scale to 0.01 and apply the scale
            imported_object.scale = (0.01, 0.01, 0.01)
            bpy.ops.object.transform_apply(scale=True)

            # Add a weld modifier and a decimate modifier to the object and apply both of them
            bpy.ops.object.modifier_add(type='WELD')
            bpy.context.object.modifiers["Weld"].merge_threshold = 0.00015
            bpy.ops.object.modifier_add(type='REMESH')
            bpy.context.object.modifiers["Remesh"].mode = 'SHARP'
            bpy.context.object.modifiers["Remesh"].octree_depth = 8
            bpy.ops.object.modifier_add(type='DECIMATE')
            bpy.context.object.modifiers["Decimate"].ratio = 0.3
            bpy.ops.object.modifier_apply(modifier="Weld")
            bpy.ops.object.modifier_apply(modifier="Remesh")
            bpy.ops.object.modifier_apply(modifier="Decimate")

            # Add the Geometry Nodes modifier based on the user selection
            if context.scene.custom_properties.yaw_type == "MAXILLARY":
                obj = context.view_layer.objects.active
                obj.modifiers.new(name="Maxillary", type="NODES")
                if context.scene.custom_properties.num_pieces == "12":
                    obj.modifiers["Maxillary"].node_group = bpy.data.node_groups["GN_Maxillar_12"]
                elif context.scene.custom_properties.num_pieces == "14":
                    obj.modifiers["Maxillary"].node_group = bpy.data.node_groups["GN_Maxillar_14"]
                elif context.scene.custom_properties.num_pieces == "16":
                    obj.modifiers["Maxillary"].node_group = bpy.data.node_groups["GN_Maxillar_16"]

            elif context.scene.custom_properties.yaw_type == "MANDIBLE":
                obj = context.view_layer.objects.active
                obj.modifiers.new(name="Mandible", type="NODES")
                if context.scene.custom_properties.num_pieces == "12":
                    obj.modifiers["Mandible"].node_group = bpy.data.node_groups["GN_Mandible_12"]
                elif context.scene.custom_properties.num_pieces == "14":
                    obj.modifiers["Mandible"].node_group = bpy.data.node_groups["GN_Mandible_14"]
                elif context.scene.custom_properties.num_pieces == "16":
                    obj.modifiers["Mandible"].node_group = bpy.data.node_groups["GN_Mandible_16"]

        else:
            self.report({"WARNING"}, "Please, select an .stl first")

        return {'FINISHED'}


class OBJECT_OT_export_STL(bpy.types.Operator):
    bl_idname = "export_mesh.my_stl_exporter"
    bl_label = "Export STL"

    filepath: bpy.props.StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        # Export the STL file with the default options
        bpy.ops.export_mesh.stl(filepath=self.filepath, check_existing=False, use_selection=True)
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


classes = (
    OBJECT_PG_custom_properties,
    OBJECT_PT_custom_panel,
    OBJECT_OT_import_STL,
    OBJECT_OT_export_STL,
    )


def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    
    bpy.types.Scene.custom_properties = bpy.props.PointerProperty(type=OBJECT_PG_custom_properties)
    
    
def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    
    del bpy.types.Scene.custom_properties


if __name__ == "__main__":
    register()
$\endgroup$
8
  • $\begingroup$ thank you very much for your help but still not working... I update the main post with a pic and with the complete code. $\endgroup$
    – Miguel
    Jun 29, 2023 at 19:36
  • 1
    $\begingroup$ OK, I updated my answer. $\endgroup$
    – Karan
    Jun 29, 2023 at 20:58
  • $\begingroup$ thanks Karan. It's almost perfect.... but (if .blend is saved) first time you select an stl file it looks like this "C:\Users\migue\Documents\BLENDER PROJECTS\..\..\Downloads\stl\upper.stl" .... it's adding the .blend file path at the beginning..... however, next time you select a file looks correct like this: "C:\Users\migue\Downloads\stl\upper.stl" $\endgroup$
    – Miguel
    Jun 30, 2023 at 8:02
  • $\begingroup$ It doesn't matter, you should always use bpy.path.abspath() so that it export in absolute path. here filepath = os.path.join(bpy.path.abspath(custom_properties.stl_directory), f"{context.active_object.name}.stl") $\endgroup$
    – Karan
    Jun 30, 2023 at 14:50
  • $\begingroup$ sorry Karan, but if I use DIR_PATH instead of FILE_PATH then you can't select a file, just the folder.....and still happens the same.... if the .blend(the .BLEND not the STL) is saved and the STL Directory field is empty, then you select a file and it looks like this "C:\Users\migue\Documents\BLENDER PROJECTS\..\..\Downloads\stl\upper.stl" and it should look like this: "C:\Users\migue\Downloads\stl\upper.stl" $\endgroup$
    – Miguel
    Jun 30, 2023 at 16:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .