gp_toolbox/utils.py

955 lines
30 KiB
Python
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

import bpy, os
import numpy as np
import bmesh
import mathutils
from mathutils import Vector
import math
from math import sqrt
from sys import platform
import subprocess
""" def get_gp_parent(layer) :
if layer.parent_type == "BONE" and layer.parent_bone :
return layer.parent.pose.bones.get(layer.parent_bone)
else :
return layer.parent
"""
def get_matrix(ob) :
'''return a copy of the world_matrix, applied object matrix if its a bone'''
if isinstance(ob, bpy.types.PoseBone) :
return ob.id_data.matrix_world @ ob.matrix.copy()# * ?
else :
return ob.matrix_world.copy()
def set_matrix(gp_frame,mat):
for stroke in gp_frame.strokes :
for point in stroke.points :
point.co = mat @ point.co
# get view vector location (the 2 methods work fine)
def get_view_origin_position():
## method 1
# from bpy_extras import view3d_utils
# region = bpy.context.region
# rv3d = bpy.context.region_data
# view_loc = view3d_utils.region_2d_to_origin_3d(region, rv3d, (region.width/2.0, region.height/2.0))
## method 2
r3d = bpy.context.space_data.region_3d
view_loc2 = r3d.view_matrix.inverted().translation
return view_loc2
def location_to_region(worldcoords):
from bpy_extras import view3d_utils
return view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d, worldcoords)
def region_to_location(viewcoords, depthcoords):
from bpy_extras import view3d_utils
return view3d_utils.region_2d_to_location_3d(bpy.context.region, bpy.context.space_data.region_3d, viewcoords, depthcoords)
def vector_len_from_coord(a, b):
'''get either two points or world coordinates and return length'''
from mathutils import Vector
if type(a) is Vector:
return (a - b).length
else:
return (a.co - b.co).length
def transfer_value(Value, OldMin, OldMax, NewMin, NewMax):
'''map a value from a range to another (transfer/translate value)'''
return (((Value - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
def object_derived_get(ob, scene):
if ob.dupli_type != 'NONE' :
ob.dupli_list_create(scene)
ob_matrix_pairs = [(dob.object, dob.matrix.copy()) for dob in ob.dupli_list]
ob.dupli_list_clear()
else:
ob_matrix_pairs = [(ob, ob.matrix_world.copy())]
return ob_matrix_pairs
# -----------------
### Bmesh
# -----------------
def link_vert(v,ordered_vert) :
for e in v.link_edges :
other_vert = e.other_vert(v)
if other_vert not in ordered_vert :
ordered_vert.append(other_vert)
link_vert(other_vert,ordered_vert)
return ordered_vert
def find_loops(bm) :
verts = []
loops = []
print([v for v in bm.verts if len(v.link_edges)==1])
for v in [v for v in bm.verts if len(v.link_edges)==1] :
if v not in verts :
loop = link_vert(v,[v])
loops.append(loop)
for vert in loop :
verts.append(vert)
return loops
def get_perimeter(points) :
perimeter = 0
print('pointlen',len(points))
for i,point in enumerate(points) :
if i != 0 :
perimeter += (Vector(point) -Vector(points[i-1])).length
return perimeter
def points_to_bm_face(points,depth=0) :
bm = bmesh.new()
for point in points :
bm.verts.new((point[0],point[1],depth))
bm.faces.new(bm.verts)
bm.faces.ensure_lookup_table()
return bm
def gp_stroke_to_bmesh(strokes):
strokes_info = []
for stroke in strokes :
info = {'stroke' : stroke ,'color':stroke.colorname,'line_width':stroke.line_width}
bm = bmesh.new()
strength = bm.verts.layers.float.new('strength')
pressure = bm.verts.layers.float.new('pressure')
select = bm.verts.layers.int.new('select')
verts = []
for i,point in enumerate(stroke.points) :
v = bm.verts.new(point.co)
v[strength] = point.strength
v[pressure] = point.pressure
v[select] = point.select
verts.append(v)
if i > 0 :
e = bm.edges.new([verts[-1],verts[-2]])
info['bmesh']= bm
strokes_info.append(info)
return strokes_info
# -----------------
### GP Drawing
# -----------------
def simple_draw_gp_stroke(pts, frame, width = 2, mat_id = 0):
'''
draw basic stroke by passing list of point 3D coordinate
the frame to draw on and optional width parameter (default = 2)
'''
stroke = frame.strokes.new()
stroke.line_width = width
stroke.display_mode = '3DSPACE'
stroke.material_index = mat_id
# readonly -> stroke.is_nofill_stroke# boundary_stroke
stroke.points.add(len(pts))
seq = [i for vec in pts for i in vec]## foreach_set flatlist for speed
stroke.points.foreach_set('co', seq)
## one by one
# for i, pt in enumerate(pts):
# stroke.points.add()
# dest_point = stroke.points[i]
# dest_point.co = pt
return stroke
## OLD - need update
def draw_gp_stroke(loop_info, frame, palette, width = 2) :
stroke = frame.strokes.new(palette)
stroke.line_width = width
stroke.display_mode = '3DSPACE'# old -> draw_mode
for i,info in enumerate(loop_info) :
stroke.points.add()
dest_point = stroke.points[i]
for attr,value in info.items() :
setattr(dest_point,attr,value)
return stroke
def get_camera_frame_info(cam, distance = 1):
'''
return a list with 4 screen corner top-right first rotating CC
4-------1
| |
3-------2
'''
cam_coord = cam.matrix_world.to_translation()
scene = bpy.context.scene
#shift_x = cam.data.shift_x
#shift_y = cam.data.shift_y
#cam.data.shift_x = 0
#cam.data.shift_y = 0
frame = cam.data.view_frame(scene)
frame = [cam.matrix_world * corner for corner in frame]
#frame = [corner+(corner-cam_coord).normalized()*distance for corner in frame]
#cam.data.shift_x = shift_x
#cam.data.shift_y = shift_y
# bpy.context.scene.cursor_location = frame[0]# test by placing cursor
return frame
def id_convert(fimg,id,operation = 'EQUAL',border = True):
new_img = fimg.copy()
width = len(new_img[0])
if operation == 'EQUAL' :
thresh_mask = new_img[...] == id
elif operation == 'GREATER' :
thresh_mask = new_img[...] > id
elif operation == 'LOWER' :
thresh_mask = new_img[...] < id
else :
return
new_img[:] = 1.0
new_img[thresh_mask] = 0.0
if border :
# Adding black around the image
new_img = np.concatenate((new_img,[[1]*width]))
new_img = np.concatenate(([[1]*width],new_img))
new_img = np.insert(new_img,width,1,axis = 1)
new_img = np.insert(new_img,0,1,axis = 1)
return new_img
def remapping(value, leftMin, leftMax, rightMin, rightMax):
# Figure out how 'wide' each range is
leftSpan = leftMax - leftMin
rightSpan = rightMax - rightMin
# Convert the left range into a 0-1 range (float)
valueScaled = float(value - leftMin) / float(leftSpan)
# Convert the 0-1 range into a value in the right range.
return rightMin + (valueScaled * rightSpan)
# -----------------
### GP funcs
# -----------------
def get_gp_draw_plane(context, obj=None):
''' return tuple with plane coordinate and normal
of the curent drawing accordign to geometry'''
settings = context.scene.tool_settings
orient = settings.gpencil_sculpt.lock_axis #'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR'
loc = settings.gpencil_stroke_placement_view3d #'ORIGIN', 'CURSOR', 'SURFACE', 'STROKE'
if obj:
mat = obj.matrix_world
else:
mat = context.object.matrix_world if context.object else None
# -> placement
if loc == "CURSOR":
plane_co = context.scene.cursor.location
else: # ORIGIN (also on origin if set to 'SURFACE', 'STROKE')
if not context.object:
plane_co = None
else:
plane_co = context.object.matrix_world.to_translation()# context.object.location
# -> orientation
if orient == 'VIEW':
#only depth is important, no need to get view vector
plane_no = None
elif orient == 'AXIS_Y':#front (X-Z)
plane_no = Vector((0,1,0))
plane_no.rotate(mat)
elif orient == 'AXIS_X':#side (Y-Z)
plane_no = Vector((1,0,0))
plane_no.rotate(mat)
elif orient == 'AXIS_Z':#top (X-Y)
plane_no = Vector((0,0,1))
plane_no.rotate(mat)
elif orient == 'CURSOR':
plane_no = Vector((0,0,1))
plane_no.rotate(context.scene.cursor.matrix)
return plane_co, plane_no
def check_angle_from_view(obj=None, plane_no=None, context=None):
'''Return angle to obj according to chosen drawing axis'''
import math
from bpy_extras import view3d_utils
if not context:
context = bpy.context
if not plane_no:
_plane_co, plane_no = get_gp_draw_plane(context, obj=obj)
view_direction = view3d_utils.region_2d_to_vector_3d(context.region, context.region_data, (context.region.width/2.0, context.region.height/2.0))
angle = math.degrees(view_direction.angle(plane_no))
# correct angle value when painting from other side (seems a bit off...)
if angle > 90:
angle = abs(90 - (angle - 90))
# over angle warning
if angle > 75:
return angle, 'ERROR', f"Canvas not aligned at all! (angle: {angle:.2f}°), use Realign GP or change draw axis"
if angle > 6:
return angle, 'ERROR', f'Canvas not aligned! angle: {angle:.2f}°, use Realign GP'
if angle > 3: # below 3 is reasonable (camera seem to view induce error !)
return angle, 'INFO', f'Canvas almost aligned (angle: {angle:.2f}°)'
if angle == 0:
return angle, 'INFO', f'Canvas perfectly aligned (angle: {angle:.2f}°)'
# below 1 consider aligned
return angle, 'INFO', f'Canvas alignement seem ok (angle: {angle:.2f}°)'
## need big update
def create_gp_palette(gp_data_block,info) :
palette = gp_data_block.palettes.active
name = info["name"]
if palette.colors.get(name) :
return palette.colors.get(name)
else :
p = palette.colors.new()
for attr,value in info.items() :
setattr(p,attr,value)
return p
""" def get_gp_data_block() :
scene = bpy.context.scene
if scene.tool_settings.grease_pencil_source == 'OBJECT' and ob and ob.grease_pencil:
gp_data_block = ob.grease_pencil
elif scene.grease_pencil :
gp_data_block = scene.grease_pencil
else :
gp_data_block =bpy.data.grease_pencil.new('GPencil')
scene.grease_pencil = gp_data_block
palette = gp_data_block.palettes.active
if not palette :
palette = gp_data_block.palettes.new("palette")
return gp_data_block,palette """
def get_gp_objects(selection=True):
'''return selected objects or only the active one'''
if not bpy.context.active_object or bpy.context.active_object.type != 'GPENCIL':
print('No active GP object')
return []
active = bpy.context.active_object
if selection:
selection = [o for o in bpy.context.selected_objects if o.type == 'GPENCIL']
if not active in selection:
selection += [active]
return selection
if bpy.context.active_object and bpy.context.active_object.type == 'GPENCIL':
return [active]
return []
def get_gp_datas(selection=True):
'''return selected objects or only the active one'''
if not bpy.context.active_object or bpy.context.active_object.type != 'GPENCIL':
print('No active GP object')
return []
active_data = bpy.context.active_object.data
if selection:
selected = []
for o in bpy.context.selected_objects:
if o.type == 'GPENCIL':
if o.data not in selected:
selected.append(o.data)
# selected = [o.data for o in bpy.context.selected_objects if o.type == 'GPENCIL']
if not active_data in selected:
selected += [active_data]
return selected
if bpy.context.active_object and bpy.context.active_object.type == 'GPENCIL':
return [active_data]
print('EOL. No active GP object')
return []
def get_gp_layer(gp_data_block,name) :
gp_layer = gp_data_block.layers.get(name)
if not gp_layer :
gp_layer = gp_data_block.layers.new(name)
return gp_layer
def get_gp_frame(layer,frame_nb = None) :
scene = bpy.context.scene
if not frame_nb :
frame_nb = scene.frame_current
frames={}
for i,f in enumerate(layer.frames) :
frames[f.frame_number]=i
if not scene.frame_current in frames.keys():
dest_frame = layer.frames.new(frame_nb)
else :
dest_frame = layer.frames[frames[frame_nb]]
dest_frame.clear()
return dest_frame
def get_active_frame(layer_name=None):
'''Return active frame of active layer or from layer name passed'''
if layer_name:
lay = bpy.context.scene.grease_pencil.layers.get(layer_name)
if lay:
frame = lay.active_frame
if frame:
return frame
else:
print ('no active frame for layer', layer_name)
else:
print('no layers named', layer_name, 'in scene layers')
else:#active layer
frame = bpy.context.scene.grease_pencil.layers.active.active_frame
if frame:
return frame
else:
print ('no active frame on active layer')
def get_stroke_2D_coords(stroke):
'''return a list containing points 2D coordinates of passed gp stroke object'''
return [location_to_region(p.co) for p in stroke.points]
'''#foreach method for retreiving multiple other attribute quickly and stack them
point_nb = len(stroke.points)
seq = [0]*(point_nb*3)
stroke.points.foreach_get("co",seq)
print("raw_list", seq)#Dbg
import numpy as np
#can use np.stack to add points infos on same index (on different line/dimension)
#https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.stack.html
'''
def get_all_stroke_2D_coords(frame):
'''return a list of lists with all strokes's points 2D location'''
## using modification from get_stroke_2D_coords func'
return [get_stroke_2D_coords(s) for s in frame.strokes]
## direct
#return[[location_to_region(p.co) for p in s.points] for s in frame.strokes]
def selected_strokes(frame):
'''return all stroke having a point selected as a list of strokes objects'''
stlist = []
for i, s in enumerate(frame.strokes):
if any(pt.select for pt in s.points):
stlist.append(s)
return stlist
from math import sqrt
from mathutils import Vector
# -----------------
### Vector utils 3d
# -----------------
def single_vector_length(v):
return sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2]))
def vector_length(A,B):
''''take two Vector3 and return length'''
return sqrt((A[0] - B[0])**2 + (A[1] - B[1])**2 + (A[2] - B[2])**2)
def vector_length_coeff(size, A, B):
'''
Calculate the vector lenght
return the coefficient to multiply this vector
to obtain a vector of the size given in paramerter
'''
Vlength = sqrt((A[0] - B[0])**2 + (A[1] - B[1])**2 + (A[2] - B[2])**2)
if Vlength == 0:
print('problem Vector lenght == 0 !')
return (1)
return (size / Vlength)
def cross_vector_coord(foo, bar, size):
'''Return the coord in space of a cross vector between the two point with specified size'''
between = foo - bar
#create a generic Up vector (on Y or Z)
up = Vector([1.0,0,0])
new = Vector.cross(up, between)#the cross product return a 90 degree Vector
if new == Vector([0.0000, 0.0000, 0.0000]):
#new == 0 if up vector and between are aligned ! (so change up vector)
up = Vector([0,-1.0,0])
new = Vector.cross(up, between)#the cross product return a 90 degree Vector
perpendicular = foo + new
coeff = vector_length_coeff(size, foo, perpendicular)
#position the point in space by adding the new vector multiplied by coeff value to get wanted lenght
return (foo + (new * coeff))
def midpoint(p1, p2):
'''middle location between 2 vector is calculated by adding the two vector and divide by two'''
##mid = (foo + bar) / 2
return (Vector([(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2, (p1[2] + p2[2]) / 2]))
def extrapolate_points_by_length(a,b, length):
'''
Return a third point C from by continuing in AB direction
Length define BC distance. both vector2 and vector3
'''
# return b + ((b - a).normalized() * length)# one shot
ab = b - a
if not ab: return None
return b + (ab.normalized() * length)
# -----------------
### Vector utils 2d
# -----------------
def is_vector_close(a, b, rel_tol=1e-03):
'''compare Vector or sequence of value
by default tolerance is set on 1e-03 (0.001)
'''
return all([math.isclose(i, j, rel_tol=rel_tol) for i, j in zip(a,b)])
def single_vector_length_2d(v):
return sqrt((v[0] * v[0]) + (v[1] * v[1]))
def vector_length_2d(A,B):
''''take two Vector and return length'''
return sqrt((A[0] - B[0])**2 + (A[1] - B[1])**2)
def vector_length_coeff_2d(size, A, B):
'''
Calculate the vector lenght
return the coefficient to multiply this vector
to obtain a vector of the size given in paramerter
'''
Vlength = sqrt((A[0] - B[0])**2 + (A[1] - B[1])**2)
if Vlength == 0:
print('problem Vector lenght == 0 !')
return (1)
return (size / Vlength)
def cross_vector_coord_2d(foo, bar, size):
'''Return the coord in space of a cross vector between the two point with specified size'''
##middle location between 2 vector is calculated by adding the two vector and divide by two
##mid = (foo + bar) / 2
between = foo - bar
#create a generic Up vector (on Y or Z)
up = Vector([0,1.0])
new = Vector.cross(up, between)#the cross product return a 90 degree Vector
if new == Vector([0.0000, 0.0000]):
#new == 0 if up vector and between are aligned ! (so change up vector)
up = Vector([0,-1.0,0])
new = Vector.cross(up, between)#the cross product return a 90 degree Vector
perpendicular = foo + new
coeff = vector_length_coeff(size, foo, perpendicular)
#position the point in space by adding the new vector multiplied by coeff value to get wanted lenght
return (foo + (new * coeff))
def midpoint_2d(p1, p2):
return (Vector([(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2]))
# -----------------
### Collection management
# -----------------
def set_collection(ob, collection, unlink=True) :
''' link an object in a collection and create it if necessary, if unlink object is removed from other collections'''
scn = bpy.context.scene
col = None
visible = False
linked = False
# check if collection exist or create it
for c in bpy.data.collections :
if c.name == collection : col = c
if not col : col = bpy.data.collections.new(name=collection)
# link the collection to the scene's collection if necessary
for c in scn.collection.children :
if c.name == col.name : visible = True
if not visible : scn.collection.children.link(col)
# check if the object is already in the collection and link it if necessary
for o in col.objects :
if o == ob : linked = True
if not linked : col.objects.link(ob)
# remove object from scene's collection
for o in scn.collection.objects :
if o == ob : scn.collection.objects.unlink(ob)
# if unlink flag we remove the object from other collections
if unlink :
for c in ob.users_collection :
if c.name != collection : c.objects.unlink(ob)
# -----------------
### Path utils
# -----------------
def get_addon_prefs():
'''
function to read current addon preferences properties
access a prop like this :
prefs = get_addon_prefs()
option_state = prefs.super_special_option
oneliner : get_addon_prefs().super_special_option
'''
import os
addon_name = os.path.splitext(__name__)[0]
preferences = bpy.context.preferences
addon_prefs = preferences.addons[addon_name].preferences
return (addon_prefs)
def open_addon_prefs():
'''Open addon prefs windows with focus on current addon'''
from .__init__ import bl_info
wm = bpy.context.window_manager
wm.addon_filter = 'All'
if not 'COMMUNITY' in wm.addon_support: # reactivate community
wm.addon_support = set([i for i in wm.addon_support] + ['COMMUNITY'])
wm.addon_search = bl_info['name']
bpy.context.preferences.active_section = 'ADDONS'
bpy.ops.preferences.addon_expand(module=__package__)
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
def open_file(file_path) :
'''Open filepath with default browser'''
if platform.lower() == 'darwin':
subprocess.call(('open', file_path))
elif platform.lower().startswith('win'):
os.startfile(file_path)
# subprocess.call(('start', file_path))
else:#linux
subprocess.call(('xdg-open', file_path))
def open_folder(folderpath):
'''Open the folder at given path with default browser'''
myOS = platform
if myOS.startswith('linux') or myOS.startswith('freebsd'):
cmd = 'xdg-open'
elif myOS.startswith('win'):
cmd = 'explorer'
if not folderpath:
return('/')
else:#elif myOS == "darwin":
cmd = 'open'
if not folderpath:
return('//')
folderpath = os.path.normpath(folderpath)# to prevent bad path string
fullcmd = [cmd, folderpath]
print(fullcmd)
# subprocess.call(fullcmd)
subprocess.Popen(fullcmd)
return ' '.join(fullcmd)#back to string to return and print
def detect_OS():
"""return str of os name : Linux, Windows, Mac (None if undetected)"""
myOS = platform
if myOS.startswith('linux') or myOS.startswith('freebsd'):# linux
# print("operating system : Linux")
return ("Linux")
elif myOS.startswith('win'):# Windows
# print("operating system : Windows")
return ("Windows")
elif myOS == "darwin":# OS X
# print("operating system : Mac")
return ('Mac')
else:# undetected
print("Cannot detect OS, python 'sys.platform' give :", myOS)
return None
def fuzzy_match(s1, s2, tol=0.8, case_sensitive=False):
'''Tell if two strings are similar using a similarity ratio (0 to 1) value passed as third arg'''
from difflib import SequenceMatcher
# can also use difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)
if case_sensitive:
similarity = SequenceMatcher(None, s1, s2)
else:
similarity = SequenceMatcher(None, s1.lower(), s2.lower())
return similarity.ratio() > tol
def fuzzy_match_ratio(s1, s2, case_sensitive=False):
'''Tell how much two passed strings are similar 1.0 being exactly similar'''
from difflib import SequenceMatcher
if case_sensitive:
similarity = SequenceMatcher(None, s1, s2)
else:
similarity = SequenceMatcher(None, s1.lower(), s2.lower())
return similarity.ratio()
def convert_attr(Attr):
'''Convert given value to a Json serializable format'''
if isinstance(Attr, (mathutils.Vector,mathutils.Color)):
return Attr[:]
elif isinstance(Attr, mathutils.Matrix):
return [v[:] for v in Attr]
elif isinstance(Attr,bpy.types.bpy_prop_array):
return [Attr[i] for i in range(0,len(Attr))]
else:
return(Attr)
## confirm pop-up message:
def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
'''Show message box with element passed as string or list
if _message if a list of lists:
if sublist have 2 element:
considered a label [text,icon]
if sublist have 3 element:
considered as an operator [ops_id_name, text, icon]
'''
def draw(self, context):
for l in _message:
if isinstance(l, str):
self.layout.label(text=l)
else:
if len(l) == 2: # label with icon
self.layout.label(text=l[0], icon=l[1])
elif len(l) == 3: # ops
self.layout.operator_context = "INVOKE_DEFAULT"
self.layout.operator(l[0], text=l[1], icon=l[2], emboss=False) # <- highligh the entry
## offset pnale when using row...
# row = self.layout.row()
# row.label(text=l[1])
# row.operator(l[0], icon=l[2])
if isinstance(_message, str):
_message = [_message]
bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon)
# -----------------
### UI utils
# -----------------
## kmi draw for addon without delete button
def draw_kmi(km, kmi, layout):
map_type = kmi.map_type
## col = _indented_layout(layout)
# layout.label(text=f'{km.name} - {km.space_type} - {km.region_type}') # debug
# layout.label(text=f'{km.name}')
col = layout.column()
if kmi.show_expanded:
col = col.column(align=True)
box = col.box()
else:
box = col.column()
split = box.split()
# header bar
row = split.row(align=True)
row.prop(kmi, "show_expanded", text="", emboss=False)
row.prop(kmi, "active", text="", emboss=False)
if km.is_modal:
row.separator()
row.prop(kmi, "propvalue", text="")
else:
row.label(text=kmi.name)
row = split.row()
row.prop(kmi, "map_type", text="")
if map_type == 'KEYBOARD':
row.prop(kmi, "type", text="", full_event=True)
elif map_type == 'MOUSE':
row.prop(kmi, "type", text="", full_event=True)
elif map_type == 'NDOF':
row.prop(kmi, "type", text="", full_event=True)
elif map_type == 'TWEAK':
subrow = row.row()
subrow.prop(kmi, "type", text="")
subrow.prop(kmi, "value", text="")
elif map_type == 'TIMER':
row.prop(kmi, "type", text="")
else:
row.label()
### / Hided delete button
if (not kmi.is_user_defined) and kmi.is_user_modified:
row.operator("preferences.keyitem_restore", text="", icon='BACK').item_id = kmi.id
else:
pass ### NO REMOVE
# row.operator(
# "preferences.keyitem_remove",
# text="",
# # Abusing the tracking icon, but it works pretty well here.
# icon=('TRACKING_CLEAR_BACKWARDS' if kmi.is_user_defined else 'X')
# ).item_id = kmi.id
### Hided delete button /
# Expanded, additional event settings
if kmi.show_expanded:
box = col.box()
split = box.split(factor=0.4)
sub = split.row()
if km.is_modal:
sub.prop(kmi, "propvalue", text="")
else:
# One day...
# sub.prop_search(kmi, "idname", bpy.context.window_manager, "operators_all", text="")
sub.prop(kmi, "idname", text="")
if map_type not in {'TEXTINPUT', 'TIMER'}:
sub = split.column()
subrow = sub.row(align=True)
if map_type == 'KEYBOARD':
subrow.prop(kmi, "type", text="", event=True)
subrow.prop(kmi, "value", text="")
subrow_repeat = subrow.row(align=True)
subrow_repeat.active = kmi.value in {'ANY', 'PRESS'}
subrow_repeat.prop(kmi, "repeat", text="Repeat")
elif map_type in {'MOUSE', 'NDOF'}:
subrow.prop(kmi, "type", text="")
subrow.prop(kmi, "value", text="")
subrow = sub.row()
subrow.scale_x = 0.75
subrow.prop(kmi, "any", toggle=True)
subrow.prop(kmi, "shift", toggle=True)
subrow.prop(kmi, "ctrl", toggle=True)
subrow.prop(kmi, "alt", toggle=True)
subrow.prop(kmi, "oskey", text="Cmd", toggle=True)
subrow.prop(kmi, "key_modifier", text="", event=True)
# Operator properties
box.template_keymap_item_properties(kmi)
## Modal key maps attached to this operator
# if not km.is_modal:
# kmm = kc.keymaps.find_modal(kmi.idname)
# if kmm:
# draw_km(display_keymaps, kc, kmm, None, layout + 1)
# layout.context_pointer_set("keymap", km)
# -----------------
### linking utility
# -----------------
def link_objects_in_blend(filepath, obj_name_list, link=True):
'''Link an object by name from a file, if link is False, append instead of linking'''
if isinstance(obj_name_list, str):
obj_name_list = [obj_name_list]
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 in obj_name_list]
return data_to.objects
def check_materials_in_blend(filepath):
'''Return a list of all material in remote blend file'''
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
l = [m for m in data_from.materials]
return l
def check_objects_in_blend(filepath, avoid_camera=True):
'''Return a list of object name in remote blend 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 any(x in o.lower() for x in ('camera', 'draw_cam', 'obj_cam'))]
else:
l = [o for o in data_from.objects]
return l
# -----------------
### props handling
# -----------------
def iterate_selector(zone, attr, state, info_attr = None, active_access='active'):
'''Iterate with given attribute'''
item_number = len(zone)
if item_number <= 1:
return
if getattr(zone, attr) == None:
print('no', attr, 'in', zone)
return
if state: # swap
info = None
bottom = None
new_index = getattr(zone, attr) + state
setattr(zone, attr, new_index % item_number)
if new_index == item_number:
bottom = 1 # bottom reached, cycle to first
elif new_index < 0:
bottom = -1 # up reached, cycle to last
if info_attr:
active_item = getattr(zone, active_access) # active by default
if active_item:
info = getattr(active_item, info_attr)
return info, bottom