Newer
Older
Dima Glib
committed
variants_enum = {'RAW', 'PREVIEW', 'RENDER'}
variants_normalization = {
'MESH':{},
'CURVE':{},
'SURFACE':{},
'FONT':{},
'META':{'RAW':'PREVIEW'},
'ARMATURE':{'RAW':'PREVIEW', 'RENDER':'PREVIEW'},
'LATTICE':{'RAW':'PREVIEW', 'RENDER':'PREVIEW'},
'EMPTY':{'RAW':'PREVIEW', 'RENDER':'PREVIEW'},
'CAMERA':{'RAW':'PREVIEW', 'RENDER':'PREVIEW'},
'LAMP':{'RAW':'PREVIEW', 'RENDER':'PREVIEW'},
'SPEAKER':{'RAW':'PREVIEW', 'RENDER':'PREVIEW'},
}
conversible_types = {'MESH', 'CURVE', 'SURFACE', 'FONT',
'META', 'ARMATURE', 'LATTICE'}
convert_types = conversible_types
CoDEmanX
committed
def __init__(self, context, convert_types=None):
self.collection = context.collection
self.scene = context.scene
Dima Glib
committed
if convert_types:
self.convert_types = convert_types
self.cached = {}
CoDEmanX
committed
Dima Glib
committed
def __del__(self):
self.clear()
CoDEmanX
committed
Dima Glib
committed
def clear(self, expect_zero_users=False):
for cache_item in self.cached.values():
if cache_item:
try:
cache_item.dispose()
except RuntimeError:
if expect_zero_users:
raise
self.cached.clear()
CoDEmanX
committed
Dima Glib
committed
def __delitem__(self, obj):
cache_item = self.cached.pop(obj, None)
if cache_item:
cache_item.dispose()
CoDEmanX
committed
Dima Glib
committed
def __contains__(self, obj):
return obj in self.cached
CoDEmanX
committed
Dima Glib
committed
def __getitem__(self, obj):
if isinstance(obj, tuple):
return self.get(*obj)
return self.get(obj)
CoDEmanX
committed
Dima Glib
committed
def get(self, obj, variant='PREVIEW', reuse=True):
if variant not in self.variants_enum:
raise ValueError("Mesh variant must be one of %s" %
self.variants_enum)
CoDEmanX
committed
Dima Glib
committed
# Make sure the variant is proper for this type of object
variant = (self.variants_normalization[obj.type].
get(variant, variant))
CoDEmanX
committed
Dima Glib
committed
if obj in self.cached:
cache_item = self.cached[obj]
try:
# cache_item is None if object isn't conversible to mesh
return (None if (cache_item is None)
else cache_item[variant])
except KeyError:
pass
else:
cache_item = None
CoDEmanX
committed
Dima Glib
committed
if obj.type not in self.conversible_types:
self.cached[obj] = None
return None
CoDEmanX
committed
Dima Glib
committed
if not cache_item:
cache_item = MeshCacheItem()
self.cached[obj] = cache_item
CoDEmanX
committed
Dima Glib
committed
conversion = self._convert(obj, variant, reuse)
cache_item[variant] = conversion
CoDEmanX
committed
Dima Glib
committed
return conversion[0]
CoDEmanX
committed
Dima Glib
committed
def _convert(self, obj, variant, reuse=True):
obj_type = obj.type
obj_mode = obj.mode
data = obj.data
CoDEmanX
committed
Dima Glib
committed
if obj_type == 'MESH':
if reuse and ((variant == 'RAW') or (len(obj.modifiers) == 0)):
return (obj, False)
else:
force_objectmode = (obj_mode in {'EDIT', 'SCULPT'})
Dima Glib
committed
return (self._to_mesh(obj, variant, force_objectmode), True)
elif obj_type in {'CURVE', 'SURFACE', 'FONT'}:
Dima Glib
committed
if variant == 'RAW':
bm = bmesh.new()
for spline in data.splines:
for point in spline.bezier_points:
bm.verts.new(point.co)
bm.verts.new(point.handle_left)
bm.verts.new(point.handle_right)
for point in spline.points:
bm.verts.new(point.co[:3])
return (self._make_obj(bm, obj), True)
else:
if variant == 'RENDER':
resolution_u = data.resolution_u
resolution_v = data.resolution_v
if data.render_resolution_u != 0:
data.resolution_u = data.render_resolution_u
if data.render_resolution_v != 0:
data.resolution_v = data.render_resolution_v
CoDEmanX
committed
Dima Glib
committed
result = (self._to_mesh(obj, variant), True)
CoDEmanX
committed
Dima Glib
committed
if variant == 'RENDER':
data.resolution_u = resolution_u
data.resolution_v = resolution_v
CoDEmanX
committed
Dima Glib
committed
return result
elif obj_type == 'META':
if variant == 'RAW':
# To avoid the hassle of snapping metaelements
# to themselves, we just create an empty mesh
bm = bmesh.new()
return (self._make_obj(bm, obj), True)
else:
if variant == 'RENDER':
resolution = data.resolution
data.resolution = data.render_resolution
CoDEmanX
committed
Dima Glib
committed
result = (self._to_mesh(obj, variant), True)
CoDEmanX
committed
Dima Glib
committed
if variant == 'RENDER':
data.resolution = resolution
CoDEmanX
committed
Dima Glib
committed
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
return result
elif obj_type == 'ARMATURE':
bm = bmesh.new()
if obj_mode == 'EDIT':
for bone in data.edit_bones:
head = bm.verts.new(bone.head)
tail = bm.verts.new(bone.tail)
bm.edges.new((head, tail))
elif obj_mode == 'POSE':
for bone in obj.pose.bones:
head = bm.verts.new(bone.head)
tail = bm.verts.new(bone.tail)
bm.edges.new((head, tail))
else:
for bone in data.bones:
head = bm.verts.new(bone.head_local)
tail = bm.verts.new(bone.tail_local)
bm.edges.new((head, tail))
return (self._make_obj(bm, obj), True)
elif obj_type == 'LATTICE':
bm = bmesh.new()
for point in data.points:
bm.verts.new(point.co_deform)
return (self._make_obj(bm, obj), True)
CoDEmanX
committed
Dima Glib
committed
def _to_mesh(self, obj, variant, force_objectmode=False):
tmp_name = chr(0x10ffff) # maximal Unicode value
CoDEmanX
committed
Dima Glib
committed
with ToggleObjectMode(force_objectmode):
if variant == 'RAW':
mesh = obj.to_mesh(self.scene, False, 'PREVIEW')
else:
mesh = obj.to_mesh(self.scene, True, variant)
mesh.name = tmp_name
CoDEmanX
committed
Dima Glib
committed
return self._make_obj(mesh, obj)
CoDEmanX
committed
Dima Glib
committed
def _make_obj(self, mesh, src_obj):
tmp_name = chr(0x10ffff) # maximal Unicode value
CoDEmanX
committed
Dima Glib
committed
if isinstance(mesh, bmesh.types.BMesh):
bm = mesh
mesh = bpy.data.meshes.new(tmp_name)
bm.to_mesh(mesh)
CoDEmanX
committed
Dima Glib
committed
tmp_obj = bpy.data.objects.new(tmp_name, mesh)
CoDEmanX
committed
Dima Glib
committed
if src_obj:
tmp_obj.matrix_world = src_obj.matrix_world
CoDEmanX
committed
Dima Glib
committed
# This is necessary for correct bbox display # TODO
# (though it'd be better to change the logic in the raycasting)
tmp_obj.show_in_front = src_obj.show_in_front
CoDEmanX
committed
tmp_obj.instance_faces_scale = src_obj.instance_faces_scale
tmp_obj.instance_collection = src_obj.instance_collection
Dima Glib
committed
#tmp_obj.dupli_list = src_obj.dupli_list
tmp_obj.instance_type = src_obj.instance_type
CoDEmanX
committed
# Make Blender recognize object as having geometry
# (is there a simpler way to do this?)
self.collection.objects.link(tmp_obj)
self.scene.update()
# We don't need this object in scene
self.collection.objects.unlink(tmp_obj)
CoDEmanX
committed
Dima Glib
committed
return tmp_obj
#============================================================================#
# A base class for emulating ID-datablock behavior
class PseudoIDBlockBase(bpy.types.PropertyGroup):
# TODO: use normal metaprogramming?
@staticmethod
def create_props(type, name, options={'ANIMATABLE'}):
def active_update(self, context):
# necessary to avoid recursive calls
if self._self_update[0]:
return
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
# prepare data for renaming...
old_key = (self.enum if self.enum else self.collection[0].name)
new_key = (self.active if self.active else "Untitled")
CoDEmanX
committed
CoDEmanX
committed
old_item = None
new_item = None
existing_names = []
CoDEmanX
committed
for item in self.collection:
if (item.name == old_key) and (not new_item):
new_item = item
elif (item.name == new_key) and (not old_item):
old_item = item
else:
existing_names.append(item.name)
existing_names.append(new_key)
CoDEmanX
committed
# rename current item
new_item.name = new_key
CoDEmanX
committed
if old_item:
# rename other item if it has that name
name = new_key
i = 1
while name in existing_names:
name = "{}.{:0>3}".format(new_key, i)
i += 1
old_item.name = name
CoDEmanX
committed
# update the enum
self._self_update[0] += 1
self.update_enum()
self._self_update[0] -= 1
# end def
CoDEmanX
committed
def enum_update(self, context):
# necessary to avoid recursive calls
if self._self_update[0]:
return
CoDEmanX
committed
self._dont_rename[0] = True
self.active = self.enum
self._dont_rename[0] = False
CoDEmanX
committed
CoDEmanX
committed
collection = bpy.props.CollectionProperty(
active = bpy.props.StringProperty(
name="Name",
description="Name of the active {}".format(name),
options=options,
update=active_update)
items=[],
name="Choose",
description="Choose {}".format(name),
default=set(),
options={'ENUM_FLAG'},
update=enum_update)
CoDEmanX
committed
return collection, active, enum
# end def
CoDEmanX
committed
def add(self, name="", **kwargs):
if not name:
name = 'Untitled'
_name = name
CoDEmanX
committed
existing_names = [item.name for item in self.collection]
i = 1
while name in existing_names:
name = "{}.{:0>3}".format(_name, i)
i += 1
CoDEmanX
committed
instance = self.collection.add()
instance.name = name
CoDEmanX
committed
for key, value in kwargs.items():
setattr(instance, key, value)
CoDEmanX
committed
self._self_update[0] += 1
self.active = name
self.update_enum()
self._self_update[0] -= 1
CoDEmanX
committed
CoDEmanX
committed
def remove(self, key):
if isinstance(key, int):
i = key
else:
i = self.indexof(key)
CoDEmanX
committed
# Currently remove() ignores non-existing indices...
# In the case this behavior changes, we have the try block.
try:
self.collection.remove(i)
except:
pass
CoDEmanX
committed
self._self_update[0] += 1
if len(self.collection) != 0:
i = min(i, len(self.collection) - 1)
self.active = self.collection[i].name
else:
self.active = ""
self.update_enum()
self._self_update[0] -= 1
CoDEmanX
committed
def get_item(self, key=None):
if key is None:
i = self.indexof(self.active)
elif isinstance(key, int):
i = key
else:
i = self.indexof(key)
CoDEmanX
committed
try:
return self.collection[i]
except:
return None
CoDEmanX
committed
def indexof(self, key):
return next((i for i, v in enumerate(self.collection) \
if v.name == key), -1)
CoDEmanX
committed
CoDEmanX
committed
#for i, item in enumerate(self.collection):
# if item.name == key:
# return i
#return -1 # non-existing index
CoDEmanX
committed
def update_enum(self):
names = []
items = []
for item in self.collection:
names.append(item.name)
items.append((item.name, item.name, ""))
CoDEmanX
committed
prop_class, prop_params = type(self).enum
prop_params["items"] = items
if len(items) == 0:
prop_params["default"] = set()
prop_params["options"] = {'ENUM_FLAG'}
else:
# Somewhy active may be left from previous times,
# I don't want to dig now why that happens.
if self.active not in names:
self.active = items[0][0]
prop_params["default"] = self.active
prop_params["options"] = set()
CoDEmanX
committed
# Can this cause problems? In the near future, shouldn't...
type(self).enum = (prop_class, prop_params)
#type(self).enum = bpy.props.EnumProperty(**prop_params)
CoDEmanX
committed
if len(items) != 0:
self.enum = self.active
CoDEmanX
committed
CoDEmanX
committed
data_name = ""
op_new = ""
op_delete = ""
icon = 'DOT'
CoDEmanX
committed
def draw(self, context, layout):
if len(self.collection) == 0:
if self.op_new:
layout.operator(self.op_new, icon=self.icon)
else:
layout.label(
text="({})".format(self.data_name),
icon=self.icon)
return
CoDEmanX
committed
row = layout.row(align=True)
row.prop_menu_enum(self, "enum", text="", icon=self.icon)
row.prop(self, "active", text="")
if self.op_new:
row.operator(self.op_new, text="", icon='ADD')
if self.op_delete:
row.operator(self.op_delete, text="", icon='X')
# end class
#============================================================================#
# ===== PROPERTY DEFINITIONS ===== #
# ===== TRANSFORM EXTRA OPTIONS ===== #
class TransformExtraOptionsProp(bpy.types.PropertyGroup):
use_relative_coords: bpy.props.BoolProperty(
CoDEmanX
committed
name="Relative coordinates",
description="Consider existing transformation as the starting point",
snap_interpolate_normals_mode: bpy.props.EnumProperty(
items=[('NEVER', "Never", "Don't interpolate normals"),
('ALWAYS', "Always", "Always interpolate normals"),
('SMOOTH', "Smoothness-based", "Interpolate normals only "\
"for faces with smooth shading"),],
CoDEmanX
committed
name="Normal interpolation",
description="Normal interpolation mode for snapping",
snap_only_to_solid: bpy.props.BoolProperty(
CoDEmanX
committed
name="Snap only to solid",
description="Ignore wireframe/non-solid objects during snapping",
snap_element_screen_size: bpy.props.IntProperty(
CoDEmanX
committed
name="Snap distance",
description="Radius in pixels for snapping to edges/vertices",
use_comma_separator: bpy.props.BoolProperty(
Dima Glib
committed
name="Use comma separator",
description="Use comma separator when copying/pasting"\
"coordinate values (instead of Tab character)",
default=True,
options={'HIDDEN'})
# ===== 3D VECTOR LOCATION ===== #
class LocationProp(bpy.types.PropertyGroup):
pos: bpy.props.FloatVectorProperty(
name="xyz", description="xyz coords",
options={'HIDDEN'}, subtype='XYZ')
# ===== HISTORY ===== #
def update_history_max_size(self, context):
settings = find_settings()
CoDEmanX
committed
CoDEmanX
committed
prop_class, prop_params = type(history).current_id
old_max = prop_params["max"]
CoDEmanX
committed
size = history.max_size
try:
int_size = int(size)
int_size = max(int_size, 0)
int_size = min(int_size, history.max_size_limit)
except:
int_size = old_max
CoDEmanX
committed
if old_max != int_size:
prop_params["max"] = int_size
type(history).current_id = (prop_class, prop_params)
CoDEmanX
committed
# also: clear immediately?
for i in range(len(history.entries) - 1, int_size, -1):
history.entries.remove(i)
CoDEmanX
committed
if str(int_size) != size:
# update history.max_size if it's not inside the limits
history.max_size = str(int_size)
def update_history_id(self, context):
scene = bpy.context.scene
CoDEmanX
committed
settings = find_settings()
history = settings.history
CoDEmanX
committed
pos = history.get_pos()
if pos is not None:
# History doesn't depend on view (?)
cursor_pos = get_cursor_location(scene=scene)
CoDEmanX
committed
if CursorHistoryProp.update_cursor_on_id_change:
# Set cursor position anyway (we're changing v3d's
# cursor, which may be separate from scene's)
# This, however, should be done cautiously
# from scripts, since, e.g., CursorMonitor
# can supply wrong context -> cursor will be set
# in a different view than required
set_cursor_location(pos, v3d=context.space_data)
CoDEmanX
committed
if pos != cursor_pos:
if (history.current_id == 0) and (history.last_id <= 1):
history.last_id = 1
else:
history.last_id = history.curr_id
history.curr_id = history.current_id
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
class CursorHistoryBackward(bpy.types.Operator):
bl_idname = "scene.cursor_3d_history_backward"
bl_label = "Cursor History Backward"
bl_description = "Jump to previous position in cursor history"
def execute(self, context):
settings = find_settings()
history = settings.history
history.current_id += 1 # max is oldest
return {'FINISHED'}
class CursorHistoryForward(bpy.types.Operator):
bl_idname = "scene.cursor_3d_history_forward"
bl_label = "Cursor History Forward"
bl_description = "Jump to next position in cursor history"
def execute(self, context):
settings = find_settings()
history = settings.history
history.current_id -= 1 # 0 is newest
return {'FINISHED'}
class CursorHistoryProp(bpy.types.PropertyGroup):
max_size_limit = 500
CoDEmanX
committed
update_cursor_on_id_change = True
CoDEmanX
committed
show_trace: bpy.props.BoolProperty(
name="Trace",
description="Show history trace",
default=False)
max_size: bpy.props.StringProperty(
name="Size",
description="History max size",
default=str(50),
update=update_history_max_size)
current_id: bpy.props.IntProperty(
name="Index",
description="Current position in cursor location history",
default=50,
min=0,
max=50,
update=update_history_id)
entries: bpy.props.CollectionProperty(
CoDEmanX
committed
curr_id: bpy.props.IntProperty(options={'HIDDEN'})
last_id: bpy.props.IntProperty(options={'HIDDEN'})
CoDEmanX
committed
def get_pos(self, id = None):
if id is None:
id = self.current_id
CoDEmanX
committed
id = min(max(id, 0), len(self.entries) - 1)
CoDEmanX
committed
if id < 0:
# history is empty
return None
CoDEmanX
committed
CoDEmanX
committed
# for updating the upper bound on file load
def update_max_size(self):
prop_class, prop_params = type(self).current_id
# self.max_size expected to be always a correct integer
prop_params["max"] = int(self.max_size)
type(self).current_id = (prop_class, prop_params)
CoDEmanX
committed
def draw_trace(self, context):
bgl.glColor4f(0.75, 1.0, 0.75, 1.0)
bgl.glBegin(bgl.GL_LINE_STRIP)
for entry in self.entries:
p = entry.pos
bgl.glVertex3f(p[0], p[1], p[2])
bgl.glEnd()
CoDEmanX
committed
def draw_offset(self, context):
bgl.glShadeModel(bgl.GL_SMOOTH)
CoDEmanX
committed
tfm_operator = CursorDynamicSettings.active_transform_operator
CoDEmanX
committed
CoDEmanX
committed
if tfm_operator:
p = tfm_operator.particles[0]. \
get_initial_matrix().to_translation()
else:
p = self.get_pos(self.last_id)
bgl.glColor4f(1.0, 0.75, 0.5, 1.0)
bgl.glVertex3f(p[0], p[1], p[2])
CoDEmanX
committed
p = get_cursor_location(v3d=context.space_data)
bgl.glColor4f(1.0, 1.0, 0.25, 1.0)
bgl.glVertex3f(p[0], p[1], p[2])
CoDEmanX
committed
bgl.glEnd()
# ===== BOOKMARK ===== #
class BookmarkProp(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(
name="name", description="bookmark name",
options={'HIDDEN'})
pos: bpy.props.FloatVectorProperty(
name="xyz", description="xyz coords",
options={'HIDDEN'}, subtype='XYZ')
class BookmarkIDBlock(PseudoIDBlockBase):
# Somewhy instance members aren't seen in update()
# callbacks... but class members are.
_self_update = [0]
_dont_rename = [False]
CoDEmanX
committed
data_name = "Bookmark"
op_new = "scene.cursor_3d_new_bookmark"
op_delete = "scene.cursor_3d_delete_bookmark"
icon = 'CURSOR'
CoDEmanX
committed
collection, active, enum = PseudoIDBlockBase.create_props(
BookmarkProp, "Bookmark")
class NewCursor3DBookmark(bpy.types.Operator):
bl_idname = "scene.cursor_3d_new_bookmark"
bl_label = "New Bookmark"
bl_description = "Add a new bookmark"
CoDEmanX
committed
name: bpy.props.StringProperty(
name="Name",
description="Name of the new bookmark",
default="Mark")
CoDEmanX
committed
@classmethod
def poll(cls, context):
return context.area.type == 'VIEW_3D'
CoDEmanX
committed
def execute(self, context):
settings = find_settings()
library = settings.libraries.get_item()
if not library:
return {'CANCELLED'}
CoDEmanX
committed
bookmark = library.bookmarks.add(name=self.name)
CoDEmanX
committed
cusor_pos = get_cursor_location(v3d=context.space_data)
CoDEmanX
committed
bookmark.pos = library.convert_from_abs(context.space_data,
cusor_pos, True)
Sebastian Nell
committed
self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
CoDEmanX
committed
return {'FINISHED'}
class DeleteCursor3DBookmark(bpy.types.Operator):
bl_idname = "scene.cursor_3d_delete_bookmark"
bl_label = "Delete Bookmark"
bl_description = "Delete active bookmark"
CoDEmanX
committed
def execute(self, context):
settings = find_settings()
library = settings.libraries.get_item()
if not library:
return {'CANCELLED'}
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
return {'FINISHED'}
class OverwriteCursor3DBookmark(bpy.types.Operator):
bl_idname = "scene.cursor_3d_overwrite_bookmark"
bl_label = "Overwrite"
bl_description = "Overwrite active bookmark "\
"with the current cursor location"
CoDEmanX
committed
@classmethod
def poll(cls, context):
return context.area.type == 'VIEW_3D'
CoDEmanX
committed
def execute(self, context):
settings = find_settings()
library = settings.libraries.get_item()
if not library:
return {'CANCELLED'}
CoDEmanX
committed
bookmark = library.bookmarks.get_item()
if not bookmark:
return {'CANCELLED'}
CoDEmanX
committed
cusor_pos = get_cursor_location(v3d=context.space_data)
CoDEmanX
committed
bookmark.pos = library.convert_from_abs(context.space_data,
cusor_pos, True)
Sebastian Nell
committed
self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
CoDEmanX
committed
CursorDynamicSettings.recalc_csu(context, 'PRESS')
CoDEmanX
committed
return {'FINISHED'}
class RecallCursor3DBookmark(bpy.types.Operator):
bl_idname = "scene.cursor_3d_recall_bookmark"
bl_label = "Recall"
bl_description = "Move cursor to the active bookmark"
CoDEmanX
committed
@classmethod
def poll(cls, context):
return context.area.type == 'VIEW_3D'
CoDEmanX
committed
def execute(self, context):
settings = find_settings()
library = settings.libraries.get_item()
if not library:
return {'CANCELLED'}
CoDEmanX
committed
bookmark = library.bookmarks.get_item()
if not bookmark:
return {'CANCELLED'}
CoDEmanX
committed
bookmark_pos = library.convert_to_abs(context.space_data,
bookmark.pos, True)
set_cursor_location(bookmark_pos, v3d=context.space_data)
Sebastian Nell
committed
self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
CoDEmanX
committed
CoDEmanX
committed
return {'FINISHED'}
class SwapCursor3DBookmark(bpy.types.Operator):
bl_idname = "scene.cursor_3d_swap_bookmark"
bl_label = "Swap"
bl_description = "Swap cursor position with the active bookmark"
CoDEmanX
committed
@classmethod
def poll(cls, context):
return context.area.type == 'VIEW_3D'
CoDEmanX
committed
def execute(self, context):
settings = find_settings()
library = settings.libraries.get_item()
if not library:
return {'CANCELLED'}
CoDEmanX
committed
bookmark = library.bookmarks.get_item()
if not bookmark:
return {'CANCELLED'}
CoDEmanX
committed
cusor_pos = get_cursor_location(v3d=context.space_data)
CoDEmanX
committed
bookmark_pos = library.convert_to_abs(context.space_data,
bookmark.pos, True)
CoDEmanX
committed
set_cursor_location(bookmark_pos, v3d=context.space_data)
CoDEmanX
committed
bookmark.pos = library.convert_from_abs(context.space_data,
cusor_pos, True,
use_history=False)
except Exception as exc:
Sebastian Nell
committed
self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
CoDEmanX
committed
CoDEmanX
committed
return {'FINISHED'}
# Will this be used?
class SnapSelectionToCursor3DBookmark(bpy.types.Operator):
bl_idname = "scene.cursor_3d_snap_selection_to_bookmark"
bl_label = "Snap Selection"
bl_description = "Snap selection to the active bookmark"
# Will this be used?
class AddEmptyAtCursor3DBookmark(bpy.types.Operator):
bl_idname = "scene.cursor_3d_add_empty_at_bookmark"
bl_label = "Add Empty"
bl_description = "Add new Empty at the active bookmark"
CoDEmanX
committed
@classmethod
def poll(cls, context):
return context.area.type == 'VIEW_3D'
CoDEmanX
committed
def execute(self, context):
settings = find_settings()
library = settings.libraries.get_item()
if not library:
return {'CANCELLED'}
CoDEmanX
committed
bookmark = library.bookmarks.get_item()
if not bookmark:
return {'CANCELLED'}
CoDEmanX
committed
matrix = library.get_matrix(use_history=False,
v3d=context.space_data, warn=True)
bookmark_pos = matrix * bookmark.pos
except Exception as exc:
Sebastian Nell
committed
self.report({'ERROR_INVALID_CONTEXT'}, exc.args[0])
CoDEmanX
committed
name = "{}.{}".format(library.name, bookmark.name)
obj = bpy.data.objects.new(name, None)
obj.matrix_world = to_matrix4x4(matrix, bookmark_pos)
context.collection.objects.link(obj)
CoDEmanX
committed
"""
for sel_obj in list(context.selected_objects):
sel_obj.select = False
obj.select = True
context.scene.objects.active = obj
CoDEmanX
committed
# We need this to update bookmark position if
# library's system is local/scaled/normal/etc.
CursorDynamicSettings.recalc_csu(context, "PRESS")
"""
CoDEmanX
committed
# TODO: exit from editmode? It has separate history!
# If we just link object to scene, it will not trigger
# addition of new entry to Undo history
bpy.ops.ed.undo_push(message="Add Object")
CoDEmanX
committed
return {'FINISHED'}
# ===== BOOKMARK LIBRARY ===== #
class BookmarkLibraryProp(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(
name="Name", description="Name of the bookmark library",
options={'HIDDEN'})
bookmarks = bpy.props.PointerProperty(
type=BookmarkIDBlock,
options={'HIDDEN'})
system: bpy.props.EnumProperty(
items=[
('GLOBAL', "Global", "Global (absolute) coordinates"),
('LOCAL', "Local", "Local coordinate system, "\
"relative to the active object"),
('SCALED', "Scaled", "Scaled local coordinate system, "\
"relative to the active object"),
('NORMAL', "Normal", "Normal coordinate system, "\
"relative to the selected elements"),
('CONTEXT', "Context", "Current transform orientation; "\
"origin depends on selection"),
],
default="GLOBAL",
name="System",
description="Coordinate system in which to store/recall "\
"cursor locations",
options={'HIDDEN'})
offset: bpy.props.BoolProperty(
name="Offset",
description="Store/recall relative to the last cursor position",
default=False,
options={'HIDDEN'})
CoDEmanX
committed
# Returned None means "operation is not aplicable"
def get_matrix(self, use_history, v3d, warn=True, **kwargs):
#particles, csu = gather_particles(**kwargs)
CoDEmanX
committed
# Ensure we have relevant CSU (Blender will crash
# if we use the old one after Undo/Redo)
CursorDynamicSettings.recalc_csu(bpy.context)
CoDEmanX
committed
CoDEmanX
committed
if self.offset:
# history? or keep separate for each scene?
if not use_history:
csu.source_pos = get_cursor_location(v3d=v3d)
else:
settings = find_settings()
history = settings.history
csu.source_pos = history.get_pos(history.last_id)
else:
csu.source_pos = Vector()
CoDEmanX
committed
active_obj = csu.tou.view_layer.objects.active
CoDEmanX
committed
if self.system == 'GLOBAL':
sys_name = 'GLOBAL'
pivot = 'WORLD'
elif self.system == 'LOCAL':
if not active_obj:
if warn:
raise Exception("There is no active object")
return None
sys_name = 'LOCAL'
pivot = 'ACTIVE'
elif self.system == 'SCALED':
if not active_obj:
if warn:
raise Exception("There is no active object")
return None
sys_name = 'Scaled'
pivot = 'ACTIVE'
elif self.system == 'NORMAL':
Sebastian Nell
committed
if not active_obj or active_obj.mode != 'EDIT':
if warn:
raise Exception("Active object must be in Edit mode")
return None
sys_name = 'NORMAL'
pivot = 'MEDIAN' # ?
elif self.system == 'CONTEXT':
sys_name = None # use current orientation
pivot = None
CoDEmanX
committed
if active_obj and (active_obj.mode != 'OBJECT'):
if len(particles) == 0:
pivot = active_obj.matrix_world.to_translation()
CoDEmanX
committed
return csu.get_matrix(sys_name, self.offset, pivot)
CoDEmanX
committed
def convert_to_abs(self, v3d, pos, warn=False, **kwargs):
kwargs.pop("use_history", None)
matrix = self.get_matrix(False, v3d, warn, **kwargs)
if not matrix:
return None
return matrix * pos
CoDEmanX
committed
def convert_from_abs(self, v3d, pos, warn=False, **kwargs):
use_history = kwargs.pop("use_history", True)
matrix = self.get_matrix(use_history, v3d, warn, **kwargs)
CoDEmanX
committed
try:
return matrix.inverted() * pos
except:
# this is some degenerate object
return Vector()
CoDEmanX
committed
def draw_bookmark(self, context):
r = context.region
rv3d = context.region_data
CoDEmanX
committed
bookmark = self.bookmarks.get_item()
if not bookmark:
return
CoDEmanX
committed
pos = self.convert_to_abs(context.space_data, bookmark.pos)
CoDEmanX
committed
projected = location_3d_to_region_2d(r, rv3d, pos)
CoDEmanX
committed
if projected:
# Store previous OpenGL settings
smooth_prev = gl_get(bgl.GL_SMOOTH)
CoDEmanX
committed
dpi = context.preferences.system.dpi
widget_unit = (pixelsize * dpi * 20.0 + 36.0) / 72.0
CoDEmanX
committed
bgl.glShadeModel(bgl.GL_SMOOTH)
bgl.glLineWidth(2)
bgl.glColor4f(0.0, 1.0, 0.0, 1.0)
bgl.glBegin(bgl.GL_LINE_STRIP)
radius = widget_unit * 0.3 #6
n = 8
da = 2 * math.pi / n
x, y = projected
x, y = int(x), int(y)
for i in range(n + 1):
a = i * da
dx = math.sin(a) * radius
dy = math.cos(a) * radius
if (i % 2) == 0:
bgl.glColor4f(0.0, 1.0, 0.0, 1.0)
else:
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
bgl.glVertex2i(x + int(dx), y + int(dy))
bgl.glEnd()
CoDEmanX
committed
# Restore previous OpenGL settings
gl_enable(bgl.GL_SMOOTH, smooth_prev)
class BookmarkLibraryIDBlock(PseudoIDBlockBase):
# Somewhy instance members aren't seen in update()
# callbacks... but class members are.
_self_update = [0]
_dont_rename = [False]
CoDEmanX
committed