fix: reset draw cam rotation
1.7.8 - fix: reset rotation in draw cam mode keep view in the same place (counter camera rotation) - code: initial enhancement for palette linkinggpv2
parent
9389faed22
commit
78b70c8fca
|
@ -1,5 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
1.7.8
|
||||||
|
|
||||||
|
- fix: reset rotation in draw cam mode keep view in the same place (counter camera rotation)
|
||||||
|
- code: initial enhancement for palette linking
|
||||||
|
|
||||||
1.7.7
|
1.7.7
|
||||||
|
|
||||||
- feat: add copy path to `check link` ops with multiple path representation choices
|
- feat: add copy path to `check link` ops with multiple path representation choices
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
from time import ctime
|
from time import ctime
|
||||||
import bpy
|
import bpy
|
||||||
|
import mathutils
|
||||||
from mathutils import Vector#, Matrix
|
from mathutils import Vector#, Matrix
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import math
|
||||||
from math import radians
|
from math import radians
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
class GPTB_OT_copy_text(bpy.types.Operator):
|
class GPTB_OT_copy_text(bpy.types.Operator):
|
||||||
bl_idname = "wm.copytext"
|
bl_idname = "wm.copytext"
|
||||||
bl_label = "Copy to clipboard"
|
bl_label = "Copy to clipboard"
|
||||||
|
@ -280,6 +284,89 @@ class GPTB_OT_set_view_as_cam(bpy.types.Operator):
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_OT_reset_cam_rot(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.reset_cam_rot"
|
||||||
|
bl_label = "Reset rotation"
|
||||||
|
bl_description = "Reset rotation of the draw manipulation camera"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.scene.camera and not context.scene.camera.name.startswith('Cam')
|
||||||
|
# return context.region_data.view_perspective == 'CAMERA'# check if in camera
|
||||||
|
|
||||||
|
def get_center_view(self, context, cam):
|
||||||
|
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||||
|
frame = cam.data.view_frame()
|
||||||
|
mat = cam.matrix_world
|
||||||
|
frame = [mat @ v for v in frame]
|
||||||
|
frame_px = [location_3d_to_region_2d(context.region, context.space_data.region_3d, v) for v in frame]
|
||||||
|
center_x = frame_px[2].x + (frame_px[0].x - frame_px[2].x)/2
|
||||||
|
center_y = frame_px[1].y + (frame_px[0].y - frame_px[1].y)/2
|
||||||
|
return mathutils.Vector((center_x, center_y))
|
||||||
|
|
||||||
|
def get_ui_ratio(self, context):
|
||||||
|
'''correct ui overlap from header/toolbars'''
|
||||||
|
regs = context.area.regions
|
||||||
|
if context.preferences.system.use_region_overlap:
|
||||||
|
w = context.area.width
|
||||||
|
# minus tool header
|
||||||
|
h = context.area.height - regs[0].height
|
||||||
|
else:
|
||||||
|
# minus tool leftbar + sidebar right
|
||||||
|
w = context.area.width - regs[2].width - regs[3].width
|
||||||
|
# minus tool header + header
|
||||||
|
h = context.area.height - regs[0].height - regs[1].height
|
||||||
|
|
||||||
|
self.ratio = h / w
|
||||||
|
self.ratio_inv = w / h
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
cam = context.scene.camera
|
||||||
|
|
||||||
|
# self.cam_init_euler = self.cam.rotation_euler.copy()
|
||||||
|
if not cam.parent or cam.parent.type != 'CAMERA':
|
||||||
|
self.report({'ERROR'}, "No parents to refer to for rotation reset")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
# store original rotation mode
|
||||||
|
org_rotation_mode = cam.rotation_mode
|
||||||
|
|
||||||
|
# set to euler to works with quaternions, restored at finish
|
||||||
|
cam.rotation_mode = 'XYZ'
|
||||||
|
# store camera matrix world
|
||||||
|
org_cam_matrix = cam.matrix_world.copy()
|
||||||
|
|
||||||
|
org_cam_z = cam.rotation_euler.z
|
||||||
|
|
||||||
|
## initialize current view_offset in camera
|
||||||
|
view_cam_offset = mathutils.Vector(context.space_data.region_3d.view_camera_offset)
|
||||||
|
|
||||||
|
# Do the reset to parent transforms
|
||||||
|
cam.matrix_world = cam.parent.matrix_world # wrong, get the parent rotation offset
|
||||||
|
|
||||||
|
# Get diff angle
|
||||||
|
angle = cam.rotation_euler.z - org_cam_z
|
||||||
|
# create rotation matrix with negative angle (we want to counter the move)
|
||||||
|
neg = -angle
|
||||||
|
rot_mat2d = mathutils.Matrix([[math.cos(neg), -math.sin(neg)], [math.sin(neg), math.cos(neg)]])
|
||||||
|
|
||||||
|
# restore original rotation mode
|
||||||
|
cam.rotation_mode = org_rotation_mode
|
||||||
|
|
||||||
|
self.get_ui_ratio(context)
|
||||||
|
# apply rotation matrix
|
||||||
|
new_cam_offset = view_cam_offset.copy()
|
||||||
|
new_cam_offset = mathutils.Vector((new_cam_offset[0], new_cam_offset[1] * self.ratio)) # apply screen ratio
|
||||||
|
new_cam_offset.rotate(rot_mat2d)
|
||||||
|
new_cam_offset = mathutils.Vector((new_cam_offset[0], new_cam_offset[1] * self.ratio_inv)) # restore screen ratio
|
||||||
|
|
||||||
|
context.space_data.region_3d.view_camera_offset = new_cam_offset
|
||||||
|
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
""" # old simple cam draw fit to cam
|
||||||
class GPTB_OT_reset_cam_rot(bpy.types.Operator):
|
class GPTB_OT_reset_cam_rot(bpy.types.Operator):
|
||||||
bl_idname = "gp.reset_cam_rot"
|
bl_idname = "gp.reset_cam_rot"
|
||||||
bl_label = "Reset rotation"
|
bl_label = "Reset rotation"
|
||||||
|
@ -306,9 +393,8 @@ class GPTB_OT_reset_cam_rot(bpy.types.Operator):
|
||||||
else:
|
else:
|
||||||
self.report({'ERROR'}, "No parents to refer to for rotation reset")
|
self.report({'ERROR'}, "No parents to refer to for rotation reset")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
"""
|
||||||
|
|
||||||
class GPTB_OT_toggle_mute_animation(bpy.types.Operator):
|
class GPTB_OT_toggle_mute_animation(bpy.types.Operator):
|
||||||
bl_idname = "gp.toggle_mute_animation"
|
bl_idname = "gp.toggle_mute_animation"
|
||||||
|
|
|
@ -5,7 +5,6 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from .utils import convert_attr, get_addon_prefs
|
from .utils import convert_attr, get_addon_prefs
|
||||||
|
|
||||||
|
|
||||||
### --- Json serialized material load/save
|
### --- Json serialized material load/save
|
||||||
|
|
||||||
def load_palette(context, filepath):
|
def load_palette(context, filepath):
|
||||||
|
@ -58,7 +57,7 @@ class GPTB_OT_load_default_palette(bpy.types.Operator):
|
||||||
line.name = 'line'
|
line.name = 'line'
|
||||||
|
|
||||||
# load json
|
# load json
|
||||||
pfp = Path(bpy.path.abspath(get_addon_prefs().palette_path))
|
pfp = Path(bpy.path.abspath(get_addon_prefs().palettes_path))
|
||||||
if not pfp.exists():
|
if not pfp.exists():
|
||||||
self.report({'ERROR'}, f'Palette path not found')
|
self.report({'ERROR'}, f'Palette path not found')
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
@ -166,7 +165,7 @@ class GPTB_OT_save_palette(bpy.types.Operator, ExportHelper):
|
||||||
def load_blend_palette(context, filepath):
|
def load_blend_palette(context, filepath):
|
||||||
'''Load materials on current active object from current chosen blend'''
|
'''Load materials on current active object from current chosen blend'''
|
||||||
#from pathlib import Path
|
#from pathlib import Path
|
||||||
#palette_fp = C.preferences.addons['GP_toolbox'].preferences['palette_path']
|
#palette_fp = C.preferences.addons['GP_toolbox'].preferences['palettes_path']
|
||||||
#fp = Path(palette_fp) / 'christina.blend'
|
#fp = Path(palette_fp) / 'christina.blend'
|
||||||
print(f'-- import palette from : {filepath} --')
|
print(f'-- import palette from : {filepath} --')
|
||||||
for ob in context.selected_objects:
|
for ob in context.selected_objects:
|
||||||
|
@ -460,7 +459,6 @@ class GPTB_OT_clean_material_stack(bpy.types.Operator):
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPTB_OT_load_palette,
|
GPTB_OT_load_palette,
|
||||||
GPTB_OT_save_palette,
|
GPTB_OT_save_palette,
|
||||||
|
@ -468,6 +466,7 @@ GPTB_OT_load_default_palette,
|
||||||
GPTB_OT_load_blend_palette,
|
GPTB_OT_load_blend_palette,
|
||||||
GPTB_OT_copy_active_to_selected_palette,
|
GPTB_OT_copy_active_to_selected_palette,
|
||||||
GPTB_OT_clean_material_stack,
|
GPTB_OT_clean_material_stack,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
|
|
@ -0,0 +1,338 @@
|
||||||
|
import bpy
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||||
|
from pathlib import Path
|
||||||
|
from . import utils
|
||||||
|
# from . import blendfile
|
||||||
|
|
||||||
|
from bpy.types import (
|
||||||
|
Panel,
|
||||||
|
Operator,
|
||||||
|
PropertyGroup,
|
||||||
|
UIList,
|
||||||
|
)
|
||||||
|
|
||||||
|
from bpy.props import (
|
||||||
|
IntProperty,
|
||||||
|
BoolProperty,
|
||||||
|
StringProperty,
|
||||||
|
FloatProperty,
|
||||||
|
EnumProperty,
|
||||||
|
PointerProperty,
|
||||||
|
)
|
||||||
|
|
||||||
|
class GPTB_UL_blend_list(UIList):
|
||||||
|
# order_by_distance : BoolProperty(default=True)
|
||||||
|
|
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||||
|
layout.label(text=item.blend_name)
|
||||||
|
|
||||||
|
def draw_filter(self, context, layout):
|
||||||
|
row = layout.row()
|
||||||
|
subrow = row.row(align=True)
|
||||||
|
subrow.prop(self, "filter_name", text="") # Only show items matching this name (use ‘*’ as wildcard)
|
||||||
|
|
||||||
|
# reverse order
|
||||||
|
icon = 'SORT_DESC' if self.use_filter_sort_reverse else 'SORT_ASC'
|
||||||
|
subrow.prop(self, "use_filter_sort_reverse", text="", icon=icon) # built-in reverse
|
||||||
|
|
||||||
|
def filter_items(self, context, data, propname):
|
||||||
|
# example : https://docs.blender.org/api/blender_python_api_current/bpy.types.UIList.html
|
||||||
|
# This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
|
||||||
|
# * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
|
||||||
|
# matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the
|
||||||
|
# * The second one is for reordering, it must return a list containing the new indices of the items (which
|
||||||
|
# gives us a mapping org_idx -> new_idx).
|
||||||
|
# Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
|
||||||
|
# If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
|
||||||
|
# returning full lists doing nothing!).
|
||||||
|
|
||||||
|
collec = getattr(data, propname)
|
||||||
|
helper_funcs = bpy.types.UI_UL_list
|
||||||
|
|
||||||
|
# Default return values.
|
||||||
|
flt_flags = []
|
||||||
|
flt_neworder = []
|
||||||
|
|
||||||
|
|
||||||
|
# Filtering by name #not working damn !
|
||||||
|
if self.filter_name:
|
||||||
|
flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, collec, "name",
|
||||||
|
reverse=self.use_filter_sort_reverse)#self.use_filter_name_reverse)
|
||||||
|
|
||||||
|
return flt_flags, flt_neworder
|
||||||
|
|
||||||
|
class GPTB_UL_object_list(UIList):
|
||||||
|
# order_by_distance : BoolProperty(default=True)
|
||||||
|
|
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||||
|
layout.label(text=item.name)
|
||||||
|
|
||||||
|
def draw_filter(self, context, layout):
|
||||||
|
row = layout.row()
|
||||||
|
subrow = row.row(align=True)
|
||||||
|
subrow.prop(self, "filter_name", text="") # Only show items matching this name (use ‘*’ as wildcard)
|
||||||
|
# reverse order
|
||||||
|
icon = 'SORT_DESC' if self.use_filter_sort_reverse else 'SORT_ASC'
|
||||||
|
subrow.prop(self, "use_filter_sort_reverse", text="", icon=icon) # built-in reverse
|
||||||
|
|
||||||
|
def filter_items(self, context, data, propname):
|
||||||
|
collec = getattr(data, propname)
|
||||||
|
helper_funcs = bpy.types.UI_UL_list
|
||||||
|
# Default return values.
|
||||||
|
flt_flags = []
|
||||||
|
flt_neworder = []
|
||||||
|
if self.filter_name:
|
||||||
|
flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, collec, "name",
|
||||||
|
reverse=self.use_filter_sort_reverse)
|
||||||
|
return flt_flags, flt_neworder
|
||||||
|
|
||||||
|
|
||||||
|
def reload_blends(self, context):
|
||||||
|
scn = context.scene
|
||||||
|
pl_prop = scn.bl_palettes_props
|
||||||
|
uilist = scn.bl_palettes_props.blends
|
||||||
|
uilist.clear()
|
||||||
|
pl_prop['bl_idx'] = 0
|
||||||
|
|
||||||
|
prefs = utils.get_addon_prefs()
|
||||||
|
|
||||||
|
if pl_prop.use_project_path:
|
||||||
|
palette_fp = prefs.palette_path
|
||||||
|
else:
|
||||||
|
palette_fp = pl_prop.custom_dir
|
||||||
|
|
||||||
|
if not palette_fp: # singular
|
||||||
|
item = uilist.add()
|
||||||
|
item.blend_name = 'No Palette Path Specified'
|
||||||
|
reload_objects(self, context)
|
||||||
|
return
|
||||||
|
|
||||||
|
palettes_dir = Path(palette_fp)
|
||||||
|
if not palettes_dir.exists():
|
||||||
|
item = uilist.add()
|
||||||
|
item.blend_name = 'Palette Path not found'
|
||||||
|
reload_objects(self, context)
|
||||||
|
return
|
||||||
|
|
||||||
|
blends = [(o.path, Path(o).stem, "") for o in os.scandir(palettes_dir)
|
||||||
|
if o.is_file()
|
||||||
|
and o.name.endswith('.blend')
|
||||||
|
and re.search(r'v\d{3}', o.name, re.I)]
|
||||||
|
|
||||||
|
blends.sort(key=lambda x: x[1], reverse=False)
|
||||||
|
# print('blends found', len(blends))
|
||||||
|
|
||||||
|
for bl in blends: # populate list
|
||||||
|
item = uilist.add()
|
||||||
|
|
||||||
|
scn.bl_palettes_props['bl_idx'] = len(uilist) - 1 # don't trigger updates
|
||||||
|
item.blend_path = bl[0]
|
||||||
|
item.blend_name = bl[1]
|
||||||
|
|
||||||
|
scn.bl_palettes_props.bl_idx = len(uilist) - 1 # trigger update ()
|
||||||
|
# reload_objects(self, context) # triggered by above assignation
|
||||||
|
|
||||||
|
# return len(blends) # return value must be None
|
||||||
|
|
||||||
|
class GPTB_OT_palettes_reload_blends(Operator):
|
||||||
|
bl_idname = "gp.palettes_reload_blends"
|
||||||
|
bl_label = "Reload Palette Blends"
|
||||||
|
bl_description = "Reload the blends in UI list of palettes linker"
|
||||||
|
bl_options = {"REGISTER"} # , "INTERNAL"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
reload_blends(self, context)
|
||||||
|
# ret = reload_blends(self, context)
|
||||||
|
# if ret is None:
|
||||||
|
# self.report({'ERROR'}, 'No blend scanned, check palette path')
|
||||||
|
# else:
|
||||||
|
# self.report({'INFO'}, f'{ret} blends found')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def reload_objects(self, context):
|
||||||
|
scn = context.scene
|
||||||
|
prefs = utils.get_addon_prefs()
|
||||||
|
pal_prop = scn.bl_palettes_props
|
||||||
|
blend_uil = pal_prop.blends
|
||||||
|
obj_uil = pal_prop.objects
|
||||||
|
obj_uil.clear()
|
||||||
|
pal_prop['ob_idx'] = 0
|
||||||
|
|
||||||
|
if not len(blend_uil) or (len(blend_uil) == 1 and not bool(blend_uil[0].blend_path)):
|
||||||
|
item = obj_uil.add()
|
||||||
|
item.name = 'No blend to list object'
|
||||||
|
return
|
||||||
|
|
||||||
|
if not blend_uil[pal_prop.bl_idx].blend_path:
|
||||||
|
item = obj_uil.add()
|
||||||
|
item.name = 'Selected blend has no path'
|
||||||
|
return
|
||||||
|
|
||||||
|
path_to_blend = Path(blend_uil[pal_prop.bl_idx].blend_path)
|
||||||
|
|
||||||
|
ob_list = utils.check_objects_in_blend(str(path_to_blend)) # get list of string of all object except camera
|
||||||
|
|
||||||
|
ob_list.sort(reverse=False) # filter object by name ?
|
||||||
|
# remove camera
|
||||||
|
# print('blends found', len(blends))
|
||||||
|
|
||||||
|
for ob_name in ob_list: # populate list
|
||||||
|
item = obj_uil.add()
|
||||||
|
item.name = ob_name
|
||||||
|
item.path = str(path_to_blend / 'Object' / ob_name)
|
||||||
|
|
||||||
|
pal_prop.ob_idx = len(obj_uil) - 1
|
||||||
|
# return len(ob_list) # must return None if used in update
|
||||||
|
|
||||||
|
|
||||||
|
## PROPS
|
||||||
|
|
||||||
|
class GPTB_PG_blend_prop(PropertyGroup):
|
||||||
|
blend_name : StringProperty() # stem of the path
|
||||||
|
blend_path : StringProperty() # ful path
|
||||||
|
# select: BoolProperty(update=update_select) # use and update to set the plane selection
|
||||||
|
|
||||||
|
class GPTB_PG_object_prop(PropertyGroup):
|
||||||
|
name : StringProperty() # stem of the path
|
||||||
|
path : StringProperty() # Object / Material ?
|
||||||
|
|
||||||
|
class GPTB_PG_palette_settings(PropertyGroup):
|
||||||
|
bl_idx : IntProperty(update=reload_objects) # update_on_index_change to reload object
|
||||||
|
blends : bpy.props.CollectionProperty(type=GPTB_PG_blend_prop)
|
||||||
|
|
||||||
|
ob_idx : IntProperty()
|
||||||
|
objects : bpy.props.CollectionProperty(type=GPTB_PG_object_prop)
|
||||||
|
|
||||||
|
use_project_path : BoolProperty(name='Use Project Palettes',
|
||||||
|
default=True, description='Use palettes directory specified in gp toolbox addon preferences',
|
||||||
|
update=reload_blends)
|
||||||
|
|
||||||
|
show_path : BoolProperty(name='Show path',
|
||||||
|
default=True, description='Show Palette directoty filepath')
|
||||||
|
|
||||||
|
custom_dir : StringProperty(name='Custom Palettes Directory', subtype='DIR_PATH',
|
||||||
|
description='Use choosen directory to load blend palettes',
|
||||||
|
update=reload_blends)
|
||||||
|
|
||||||
|
import_type : EnumProperty(
|
||||||
|
name="Import Type", description="Choose inmport type: link, append, append reuse (keep existing materials)",
|
||||||
|
default='LINK', options={'ANIMATABLE'}, update=None, get=None, set=None,
|
||||||
|
items=(
|
||||||
|
('LINK', 'Link', 'Link materials to selected object', 0),
|
||||||
|
('APPEND', 'Append', 'Append materials to selected objects', 1),
|
||||||
|
('APPEND_REUSE', 'Append (Reuse)', 'Append materials to selected objects\nkeep those already there', 2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# fav_blend: StringProperty() ## mark a blend as prefered ? (need to be stored in prefereneces to restore in other blend...)
|
||||||
|
|
||||||
|
class GPTB_OT_import_obj_palette(Operator):
|
||||||
|
bl_idname = "gp.import_obj_palette"
|
||||||
|
bl_label = "Import Object Palette"
|
||||||
|
bl_description = "Import object palette from blend"
|
||||||
|
bl_options = {"REGISTER", "INTERNAL"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
pl_prop = context.scene.bl_palettes_props
|
||||||
|
path = pl_prop.objects[pl_prop.ob_idx].path
|
||||||
|
self.report({'INFO'}, f'Path to object: {path}')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
## PANEL
|
||||||
|
|
||||||
|
class GPTB_PT_palettes_linker_ui(Panel):
|
||||||
|
bl_idname = 'GPTB_PT_palettes_linker_ui'
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Gpencil"#Dev
|
||||||
|
bl_label = "Palettes Mat Linker"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
scn = bpy.context.scene
|
||||||
|
pl_prop = scn.bl_palettes_props
|
||||||
|
col= layout.column()
|
||||||
|
prefs = utils.get_addon_prefs()
|
||||||
|
## Here put the path thing (only to use a non-library)
|
||||||
|
|
||||||
|
# maybe in submenu...
|
||||||
|
row = col.row()
|
||||||
|
expand_icon = 'TRIA_DOWN' if pl_prop.show_path else 'TRIA_RIGHT'
|
||||||
|
row.prop(pl_prop, 'show_path', text='', icon=expand_icon, emboss=False)
|
||||||
|
row.prop(pl_prop, 'use_project_path', text='Use Project Palettes')
|
||||||
|
row.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
||||||
|
|
||||||
|
if pl_prop.use_project_path:
|
||||||
|
## gp toolbox addon prefs path
|
||||||
|
if not prefs.palette_path:
|
||||||
|
col.label(text='Gp Toolbox Palette Directory Needed')
|
||||||
|
col.label(text='(saved with preferences)')
|
||||||
|
if pl_prop.show_path or not prefs.palette_path:
|
||||||
|
col.prop(prefs, 'palette_path', text='Project Dir')
|
||||||
|
else:
|
||||||
|
## local path
|
||||||
|
if not pl_prop.custom_dir:
|
||||||
|
col.label(text='Need to specify directory')
|
||||||
|
if pl_prop.show_path or not pl_prop.custom_dir:
|
||||||
|
col.prop(pl_prop, 'custom_dir', text='Custom Dir')
|
||||||
|
|
||||||
|
|
||||||
|
row = col.row()
|
||||||
|
row.template_list("GPTB_UL_blend_list", "", pl_prop, "blends", pl_prop, "bl_idx",
|
||||||
|
rows=2)
|
||||||
|
# side panel
|
||||||
|
# subcol = row.column(align=True)
|
||||||
|
# subcol.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
||||||
|
|
||||||
|
## Show object UI list only once blend Uilist is filled ?
|
||||||
|
if not len(pl_prop.blends) or (len(pl_prop.blends) == 1 and not bool(pl_prop.blends[0].blend_path)):
|
||||||
|
col.label(text='Select a blend to list available object')
|
||||||
|
|
||||||
|
row = col.row()
|
||||||
|
row.template_list("GPTB_UL_object_list", "", pl_prop, "objects", pl_prop, "ob_idx",
|
||||||
|
rows=4)
|
||||||
|
|
||||||
|
## Show link button in the border of the UI list ?
|
||||||
|
# col.prop(pl_prop, 'import_type')
|
||||||
|
split = col.split(align=True, factor=0.4 )
|
||||||
|
split.prop(pl_prop, 'import_type', text='')
|
||||||
|
|
||||||
|
split.enabled = len(pl_prop.objects) and bool(pl_prop.objects[pl_prop.ob_idx].path)
|
||||||
|
split.operator('gp.import_obj_palette', text='Palette')
|
||||||
|
|
||||||
|
# button to launch link with combined props (active only if the two items are valids)
|
||||||
|
# str(Path(self.blends) / 'Object' / self.objects
|
||||||
|
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
# blend list
|
||||||
|
GPTB_PG_blend_prop,
|
||||||
|
GPTB_UL_blend_list,
|
||||||
|
GPTB_OT_palettes_reload_blends,
|
||||||
|
|
||||||
|
# object in blend list
|
||||||
|
GPTB_PG_object_prop,
|
||||||
|
GPTB_UL_object_list,
|
||||||
|
|
||||||
|
# prop containing two above
|
||||||
|
GPTB_PG_palette_settings,
|
||||||
|
|
||||||
|
GPTB_OT_import_obj_palette,
|
||||||
|
GPTB_PT_palettes_linker_ui,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
bpy.types.Scene.bl_palettes_props = bpy.props.PointerProperty(type=GPTB_PG_palette_settings)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
|
del bpy.types.Scene.bl_palettes_props
|
53
UI_tools.py
53
UI_tools.py
|
@ -308,8 +308,10 @@ class GPTB_PT_color(bpy.types.Panel):
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
col = layout.column()
|
||||||
## Create empty frame on layer
|
## Create empty frame on layer
|
||||||
layout.operator('gp.create_empty_frames', icon='DECORATE_KEYFRAME')
|
col.operator('gp.palette_linker', text=f'Link Materials Palette TO Object', icon='COLOR') ## ops
|
||||||
|
col.operator('gp.create_empty_frames', icon='DECORATE_KEYFRAME')
|
||||||
|
|
||||||
""" # unused : added in Animation Manager
|
""" # unused : added in Animation Manager
|
||||||
class GPTB_PT_extra(bpy.types.Panel):
|
class GPTB_PT_extra(bpy.types.Panel):
|
||||||
|
@ -378,6 +380,48 @@ def expose_use_channel_color_pref(self, context):
|
||||||
layout.label(text='Use Channel Colors (User preferences):')
|
layout.label(text='Use Channel Colors (User preferences):')
|
||||||
layout.prop(context.preferences.edit, 'use_anim_channel_group_colors')
|
layout.prop(context.preferences.edit, 'use_anim_channel_group_colors')
|
||||||
|
|
||||||
|
def asset_browser_ui(self, context):
|
||||||
|
'''Only shows in blender >= 3.0.0'''
|
||||||
|
|
||||||
|
layout = self.layout
|
||||||
|
asset_file_handle = context.asset_file_handle
|
||||||
|
if asset_file_handle is None:
|
||||||
|
# layout.label(text="No asset selected", icon='INFO')
|
||||||
|
layout.label(text='No object/material selected', icon='INFO')
|
||||||
|
return
|
||||||
|
if asset_file_handle.id_type not in ('OBJECT', 'MATERIAL'):
|
||||||
|
layout.label(text='No object/material selected', icon='INFO')
|
||||||
|
return
|
||||||
|
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False
|
||||||
|
|
||||||
|
asset_library_ref = context.asset_library_ref
|
||||||
|
## Path to blend
|
||||||
|
asset_lib_path = bpy.types.AssetHandle.get_full_library_path(asset_file_handle, asset_library_ref)
|
||||||
|
path_to_obj = Path(asset_lib_path) / 'Objects' / asset_file_handle.name
|
||||||
|
|
||||||
|
## respect header choice ?
|
||||||
|
## import_type in (LINK, APPEND, APPEND_REUSE)
|
||||||
|
imp_type = context.space_data.params.import_type
|
||||||
|
if imp_type == 'APPEND':
|
||||||
|
imp_txt = 'Append'
|
||||||
|
elif imp_type == 'APPEND_REUSE':
|
||||||
|
imp_txt = 'Append (Reuse)'
|
||||||
|
else:
|
||||||
|
imp_txt = 'Link'
|
||||||
|
|
||||||
|
if asset_file_handle.id_type == 'MATERIAL':
|
||||||
|
layout.label(text=f'From Mat: {asset_file_handle.name}')
|
||||||
|
if asset_file_handle.id_type == 'OBJECT':
|
||||||
|
layout.label(text=f'From Obj: {asset_file_handle.name}')
|
||||||
|
layout.label(text=f'{imp_txt} Materials To GP Object')
|
||||||
|
layout.operator('gp.palette_linker', text=f'{imp_txt} Materials To GP Object') ## ops
|
||||||
|
|
||||||
|
# layout.label(text='Link Materials to GP Object')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPTB_PT_sidebar_panel,
|
GPTB_PT_sidebar_panel,
|
||||||
GPTB_PT_checker,
|
GPTB_PT_checker,
|
||||||
|
@ -394,9 +438,16 @@ def register():
|
||||||
bpy.types.GPENCIL_MT_material_context_menu.append(palette_manager_menu)
|
bpy.types.GPENCIL_MT_material_context_menu.append(palette_manager_menu)
|
||||||
bpy.types.DOPESHEET_PT_gpencil_layer_display.append(expose_use_channel_color_pref)
|
bpy.types.DOPESHEET_PT_gpencil_layer_display.append(expose_use_channel_color_pref)
|
||||||
|
|
||||||
|
# if bpy.app.version >= (3,0,0):
|
||||||
|
# bpy.types.ASSETBROWSER_PT_metadata.append(asset_browser_ui)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
bpy.types.DOPESHEET_PT_gpencil_layer_display.remove(expose_use_channel_color_pref)
|
bpy.types.DOPESHEET_PT_gpencil_layer_display.remove(expose_use_channel_color_pref)
|
||||||
bpy.types.GPENCIL_MT_material_context_menu.remove(palette_manager_menu)
|
bpy.types.GPENCIL_MT_material_context_menu.remove(palette_manager_menu)
|
||||||
|
# if bpy.app.version >= (3,0,0):
|
||||||
|
# bpy.types.ASSETBROWSER_PT_metadata.remove(asset_browser_ui)
|
||||||
|
|
||||||
for cls in reversed(classes):
|
for cls in reversed(classes):
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ bl_info = {
|
||||||
"name": "GP toolbox",
|
"name": "GP toolbox",
|
||||||
"description": "Set of tools for Grease Pencil in animation production",
|
"description": "Set of tools for Grease Pencil in animation production",
|
||||||
"author": "Samuel Bernou, Christophe Seux",
|
"author": "Samuel Bernou, Christophe Seux",
|
||||||
"version": (1, 7, 7),
|
"version": (1, 7, 8),
|
||||||
"blender": (2, 91, 0),
|
"blender": (2, 91, 0),
|
||||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
@ -42,6 +42,7 @@ from . import OP_helpers
|
||||||
from . import OP_keyframe_jump
|
from . import OP_keyframe_jump
|
||||||
from . import OP_cursor_snap_canvas
|
from . import OP_cursor_snap_canvas
|
||||||
from . import OP_palettes
|
from . import OP_palettes
|
||||||
|
from . import OP_palettes_linker
|
||||||
from . import OP_brushes
|
from . import OP_brushes
|
||||||
from . import OP_file_checker
|
from . import OP_file_checker
|
||||||
from . import OP_copy_paste
|
from . import OP_copy_paste
|
||||||
|
@ -632,6 +633,7 @@ def register():
|
||||||
OP_playblast_bg.register()
|
OP_playblast_bg.register()
|
||||||
OP_playblast.register()
|
OP_playblast.register()
|
||||||
OP_palettes.register()
|
OP_palettes.register()
|
||||||
|
# OP_palettes_linker.register()
|
||||||
OP_brushes.register()
|
OP_brushes.register()
|
||||||
OP_cursor_snap_canvas.register()
|
OP_cursor_snap_canvas.register()
|
||||||
OP_copy_paste.register()
|
OP_copy_paste.register()
|
||||||
|
@ -678,6 +680,7 @@ def unregister():
|
||||||
OP_copy_paste.unregister()
|
OP_copy_paste.unregister()
|
||||||
OP_cursor_snap_canvas.unregister()
|
OP_cursor_snap_canvas.unregister()
|
||||||
OP_brushes.unregister()
|
OP_brushes.unregister()
|
||||||
|
# OP_palettes_linker.unregister()
|
||||||
OP_palettes.unregister()
|
OP_palettes.unregister()
|
||||||
OP_file_checker.unregister()
|
OP_file_checker.unregister()
|
||||||
OP_helpers.unregister()
|
OP_helpers.unregister()
|
||||||
|
|
17
utils.py
17
utils.py
|
@ -855,4 +855,19 @@ def draw_kmi(km, kmi, layout):
|
||||||
# kmm = kc.keymaps.find_modal(kmi.idname)
|
# kmm = kc.keymaps.find_modal(kmi.idname)
|
||||||
# if kmm:
|
# if kmm:
|
||||||
# draw_km(display_keymaps, kc, kmm, None, layout + 1)
|
# draw_km(display_keymaps, kc, kmm, None, layout + 1)
|
||||||
# layout.context_pointer_set("keymap", km)
|
# layout.context_pointer_set("keymap", km)
|
||||||
|
|
||||||
|
def link_objects_in_blend(filepath, obj_name, link=True):
|
||||||
|
'''Link an object by name from a file, if link is False, append instead of linking'''
|
||||||
|
with bpy.data.libraries.load(filepath, link=link) as (data_from, data_to):
|
||||||
|
data_to.objects = [o for o in data_from.objects if o == obj_name] # c.startswith(obj_name)
|
||||||
|
return data_to.objects
|
||||||
|
|
||||||
|
def check_objects_in_blend(filepath, avoid_camera=True):
|
||||||
|
'''return a list of object name in file'''
|
||||||
|
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
|
||||||
|
if avoid_camera:
|
||||||
|
l = [o for o in data_from.objects if not 'camera' in o.lower()]
|
||||||
|
else:
|
||||||
|
l = [o for o in data_from.objects]
|
||||||
|
return l
|
Loading…
Reference in New Issue