Skip to content

Commit

Permalink
Merge pull request #3 from lsimic/develop_1.1.0
Browse files Browse the repository at this point in the history
Merge develop_1.1.0 into main
  • Loading branch information
lsimic authored Jul 17, 2024
2 parents 4eeaa0e + 7965e32 commit b3d09ab
Show file tree
Hide file tree
Showing 18 changed files with 941 additions and 34 deletions.
55 changes: 52 additions & 3 deletions FFFGen/UI.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def draw(self, context):
box.operator("fff_gen.create_fibula_screw", text="Create Fibula guide screw")

if properties.is_initialized and (bpy.context.window.workspace.name == constants.WORKSPACE_MANDIBLE_GUIDES):
layout.prop(properties, "positioning_aid_toggle")
box = layout.box()
box.label(text="Mandible Guides:")
if not len(bpy.data.collections[constants.COLLECTION_GUIDE_MANDIBLE].objects):
Expand All @@ -123,7 +124,19 @@ def draw(self, context):
else:
box.operator("fff_gen.create_mandible_start_screw", text="Create Mandible Start Screw")
box.operator("fff_gen.create_mandible_end_screw", text="Create Mandible End Screw")
box.operator("fff_gen.join_mandible_guides", text="Join mandible guides")
if properties.positioning_aid_toggle == "GUIDE":
if not "joined_mandible_guide" in bpy.data.objects.keys():
box.operator("fff_gen.join_mandible_guides", text="Join mandible guides")
else:
# try to find the positioning aid object
# if found - it is initialized
# otherwise - it is not initialized and show the button to do so.
if not "positioning_aid_mesh" in bpy.data.objects.keys():
box.operator("fff_gen.create_mandible_positioning_aid", text="Create positioning aid")
else:
# Property to adjust scale/thickness
box.prop(properties, "positioning_aid_size_x")
box.prop(properties, "positioning_aid_size_z")


class FFFGenDangerPanel(Panel):
Expand All @@ -144,8 +157,12 @@ def draw(self, context):
col.operator("fff_gen.clear_fibula_guides", text="Clear Fibula Guides")

if properties.is_initialized and (bpy.context.window.workspace.name == constants.WORKSPACE_MANDIBLE_GUIDES):
if len(bpy.data.collections[constants.COLLECTION_GUIDE_MANDIBLE].objects):
col.operator("fff_gen.clear_mandible_guides", text="Clear Mandible Guides")
if properties.positioning_aid_toggle == "GUIDE":
if len(bpy.data.collections[constants.COLLECTION_GUIDE_MANDIBLE].objects):
col.operator("fff_gen.clear_mandible_guides", text="Clear Mandible Guides")
else:
if "positioning_aid_mesh" in bpy.data.objects.keys():
col.operator("fff_gen.clear_mandible_positioning_aid", text="Clear positioning aid")

if properties.is_initialized and (bpy.context.window.workspace.name == constants.WORKSPACE_POSITIONING):
if len(bpy.data.collections[constants.COLLECTION_CUTTING_PLANES_FIBULA].objects):
Expand All @@ -171,3 +188,35 @@ def draw(self, context):
row = layout.row()
row.prop(obj.data.materials[0], "diffuse_color")


class FFFGenExportPanel(Panel):
bl_idname = "FFF_GEN_PT_export"
bl_label = "Export"
bl_category = "FFF Gen"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_context = "objectmode"

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

if properties.is_initialized:
sub = layout.row() # fibula guide checkbox
sub.enabled = ("fibula_guide" in bpy.data.objects.keys())
sub.prop(properties, "export_toggle_fibula_guide")

sub = layout.row() # mandible guide checkbox
sub.enabled = ("joined_mandible_guide" in bpy.data.objects.keys())
sub.prop(properties, "export_toggle_mandible_guide")

sub = layout.row() # mandible positioning aid checkbox
sub.enabled = ("positioning_aid_mesh" in bpy.data.objects.keys())
sub.prop(properties, "export_toggle_mandible_aid")

sub = layout.row() # mandible positioning aid checkbox
sub.enabled = len(bpy.data.collections[constants.COLLECTION_FFF_GEN_MANDIBLE].objects) > 0
sub.prop(properties, "export_toggle_reconstructed_mandible")

layout.prop(properties, "export_dir_path") # file path
layout.operator("fff_gen.export_guides", text="Export") # export button
25 changes: 23 additions & 2 deletions FFFGen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@
from . import fibula_guides
from . import mandible_guides
from . import clear
from . import load_handler
from . import export_guides
from . import screenshot
from . import bevel_worldspace


bl_info = {
"name": "FFFGen",
"description": "Fibula free flap guide generation",
"author": "Luka Šimić, Vjekoslav Kopačin",
"version": (1, 0, 0),
"blender": (2, 83, 0),
"version": (1, 1, 0),
"blender": (3, 6, 2),
"location": "View3D > Toolbox > FFF Gen",
"wiki_url": "https://lsimic.github.io/FFFGen/",
"tracker_url": "https://github.com/lsimic/FFFGen/issues",
Expand All @@ -59,17 +63,26 @@ def register():
bpy.utils.register_class(mandible_guides.CreateMandibleStartScrew)
bpy.utils.register_class(mandible_guides.CreateMandibleEndScrew)
bpy.utils.register_class(mandible_guides.JoinMandibleGuides)
bpy.utils.register_class(mandible_guides.CreateMandiblePositioningAid)
bpy.utils.register_class(export_guides.ExportGuides)
bpy.utils.register_class(clear.ClearMandibleGuides)
bpy.utils.register_class(clear.ClearFibulaGuides)
bpy.utils.register_class(clear.ClearCuttingPlanes)
bpy.utils.register_class(clear.ClearMandiblePositioningAid)
bpy.utils.register_class(clear.ClearAll)
bpy.utils.register_class(UI.FFFGenGeneralPanel)
bpy.utils.register_class(UI.FFFGenGuidesPanel)
bpy.utils.register_class(UI.FFFGenDangerPanel)
bpy.utils.register_class(UI.FFFGenColorPanel)
bpy.utils.register_class(UI.FFFGenExportPanel)
bpy.utils.register_class(screenshot.SaveScreenshot)
bpy.utils.register_class(screenshot.FFFGenScreenshotPanel)
bpy.utils.register_class(bevel_worldspace.FFFGenBevelPanel)
bpy.app.handlers.load_post.append(load_handler.on_load_post_handler)


def unregister():
bpy.app.handlers.load_post.remove(load_handler.on_load_post_handler)
del bpy.types.Scene.FFFGenPropertyGroup
bpy.utils.unregister_class(property_group.FFFGenPropertyGroup)
bpy.utils.unregister_class(initialize_addon.InitializeAddon)
Expand All @@ -83,11 +96,19 @@ def unregister():
bpy.utils.unregister_class(mandible_guides.CreateMandibleStartScrew)
bpy.utils.unregister_class(mandible_guides.CreateMandibleEndScrew)
bpy.utils.unregister_class(mandible_guides.JoinMandibleGuides)
bpy.utils.unregister_class(mandible_guides.CreateMandiblePositioningAid)
bpy.utils.unregister_class(export_guides.ExportGuides)
bpy.utils.unregister_class(clear.ClearMandibleGuides)
bpy.utils.unregister_class(clear.ClearFibulaGuides)
bpy.utils.unregister_class(clear.ClearCuttingPlanes)
bpy.utils.unregister_class(clear.ClearMandiblePositioningAid)
bpy.utils.unregister_class(clear.ClearAll)
bpy.utils.unregister_class(UI.FFFGenGeneralPanel)
bpy.utils.unregister_class(UI.FFFGenGuidesPanel)
bpy.utils.unregister_class(UI.FFFGenDangerPanel)
bpy.utils.unregister_class(UI.FFFGenColorPanel)
bpy.utils.unregister_class(UI.FFFGenExportPanel)
bpy.utils.unregister_class(screenshot.SaveScreenshot)
bpy.utils.unregister_class(screenshot.FFFGenScreenshotPanel)
bpy.utils.unregister_class(bevel_worldspace.FFFGenBevelPanel)

91 changes: 91 additions & 0 deletions FFFGen/bevel_worldspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# FFF Gen Add-on
# Copyright (C) 2020 Luka Simic
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####

import bpy
import os
from bpy.types import Panel


# a helper function to create a world-space bevel setup
# uses goemetry modifiers to bring geo to world space, adds the bevel and then another geometry node modifier to bring it back into local
# a bit hacky, but works well enough.
# modifier_name should be passed as the hooks on the property will search for modifiers with the name that follow a specific pattern.
def create_bevel_modifier(obj, modifier_name, segments, width):
nodegroup_objtoworld = None
nodegroup_worldtoobj = None

# no need to load/append from file if the node group is already in the current document...
if "geonode_object_to_world" in bpy.data.node_groups.keys():
nodegroup_objtoworld = bpy.data.node_groups["geonode_object_to_world"]
if "geonode_world_to_object" in bpy.data.node_groups.keys():
nodegroup_worldtoobj = bpy.data.node_groups["geonode_world_to_object"]

# load node groups if not already loaded...
if nodegroup_objtoworld is None:
directory = os.path.dirname(os.path.realpath(__file__))
file_path = os.path.join(directory, "geonodes_world_space_bevel.blend")
with bpy.data.libraries.load(file_path, link=False) as (data_from, data_to):
data_to.node_groups = [name for name in data_from.node_groups if name == "geonode_object_to_world"]
nodegroup_objtoworld = data_to.node_groups[0]
# load node groups if not already loaded...
if nodegroup_worldtoobj is None:
directory = os.path.dirname(os.path.realpath(__file__))
file_path = os.path.join(directory, "geonodes_world_space_bevel.blend")
with bpy.data.libraries.load(file_path, link=False) as (data_from, data_to):
data_to.node_groups = [name for name in data_from.node_groups if name == "geonode_world_to_object"]
nodegroup_worldtoobj = data_to.node_groups[0]

# create the geo node modifier. first we add object to world.
mod_objtoworld = obj.modifiers.new(
name="geo_object_to_world",
type="NODES"
)
mod_objtoworld.node_group = nodegroup_objtoworld
# create bevel modifier (now bevel will be done in world space)
mod_bevel = obj.modifiers.new(
name=modifier_name,
type="BEVEL"
)
mod_bevel.segments = segments
mod_bevel.width = width
# create the other geo node modifier to bring back geo into object space.
mod_worldtoobj = obj.modifiers.new(
name="geo_world_to_object",
type="NODES"
)
mod_worldtoobj.node_group = nodegroup_worldtoobj

return mod_bevel


class FFFGenBevelPanel(Panel):
bl_idname = "FFF_GEN_PT_bevel"
bl_label = "Bevel"
bl_category = "FFF Gen"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_context = "objectmode"

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

layout.prop(properties, "bevel_segmentcount")
layout.prop(properties, "bevel_width")
43 changes: 41 additions & 2 deletions FFFGen/clear.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def execute(self, context):

class ClearMandibleGuides(bpy.types.Operator):
bl_idname = "fff_gen.clear_mandible_guides"
bl_label = "Clear mandible guide objects"
bl_description = "Removes all mandible guide objects"
bl_label = "Clear mandible guide and positioning aid objects"
bl_description = "Removes all mandible and positioning guide objects"

def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
Expand All @@ -50,6 +50,19 @@ def execute(self, context):
return {"FINISHED"}


class ClearMandiblePositioningAid(bpy.types.Operator):
bl_idname = "fff_gen.clear_mandible_positioning_aid"
bl_label = "Clear mandible positioning aid objects"
bl_description = "Removes all mandible positioning aid objects"

def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)

def execute(self, context):
clear_mandible_positioning_aid()
return {"FINISHED"}


class ClearCuttingPlanes(bpy.types.Operator):
bl_idname = "fff_gen.clear_cutting_planes"
bl_label = "Clear cutting plane objects"
Expand Down Expand Up @@ -97,6 +110,32 @@ def clear_mandible_guides():
bpy.data.collections[constants.COLLECTION_CUTTING_PLANES_MANDIBLE].hide_viewport = False


def clear_mandible_positioning_aid():
# deselect all selected objects
for obj in bpy.context.selected_objects:
obj.select_set(False)

# select all positioning aid specific objects.
objects = []
positioning_aid_object_names = [
"positioning_aid_curve",
"positioning_aid_curve_handle_start",
"positioning_aid_curve_handle_end",
"positioning_aid_start",
"positioning_aid_end",
"positioning_aid_mesh"
]
for obj_name in positioning_aid_object_names:
if obj_name in bpy.data.objects.keys():
objects.append(bpy.data.objects[obj_name])
override_context = {
"selected_objects":objects
}

# delete using override context
bpy.ops.object.delete(override_context)


def clear_fibula_guides():
# deselect all selected objects
for obj in bpy.context.selected_objects:
Expand Down
16 changes: 14 additions & 2 deletions FFFGen/cutting_planes.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ def set_cutting_plane_positions(objects_cutting_planes):
if obj.name.startswith("fibula_object"):
objects_fibula[obj.name] = obj

if "fibula_copy" in bpy.data.objects.keys():
fibula_orig = bpy.data.objects["fibula_copy"]
else:
fibula_orig = None


for obj_fibula in objects_fibula.values():
# duplicate the fibula object
for obj in bpy.context.selected_objects:
Expand All @@ -127,8 +133,13 @@ def set_cutting_plane_positions(objects_cutting_planes):
obj_plane_end.select_set(False)
bpy.context.view_layer.objects.active = obj_fibula_dupli
bpy.ops.object.constraints_clear()
obj_fibula_dupli.location = (0.0, 0.0, 0.0)
obj_fibula_dupli.rotation_euler = (0.0, 0.0, 0.0)

if fibula_orig is not None:
obj_fibula_dupli.location = fibula_orig.location
obj_fibula_dupli.rotation_euler = fibula_orig.rotation_euler
else:
obj_fibula_dupli.location = (0.0, 0.0, 0.0)
obj_fibula_dupli.rotation_euler = (0.0, 0.0, 0.0)

# clear the parent relationship, but keep the transformations to keep planes in place
obj_fibula_dupli.select_set(False)
Expand Down Expand Up @@ -183,6 +194,7 @@ def setup_cutting_planes(cutting_plane_start_orig, cutting_plane_end_orig, armat
)
constraint_child_of.target = armature
constraint_child_of.subtarget = bone.name
bpy.ops.constraint.childof_clear_inverse(constraint=constraint_child_of.name, owner="OBJECT")
cutting_plane_dupli.select_set(False)
bpy.context.view_layer.objects.active = None

Expand Down
Loading

0 comments on commit b3d09ab

Please sign in to comment.