Newer
Older
op_new = "scene.cursor_3d_new_bookmark_library"
op_delete = "scene.cursor_3d_delete_bookmark_library"
icon = 'BOOKMARKS'
CoDEmanX
committed
collection, active, enum = PseudoIDBlockBase.create_props(
BookmarkLibraryProp, "Bookmark Library")
CoDEmanX
committed
def on_item_select(self):
library = self.get_item()
library.bookmarks.update_enum()
class NewCursor3DBookmarkLibrary(bpy.types.Operator):
bl_idname = "scene.cursor_3d_new_bookmark_library"
bl_label = "New Library"
bl_description = "Add a new bookmark library"
CoDEmanX
committed
name: bpy.props.StringProperty(
name="Name",
description="Name of the new library",
default="Lib")
CoDEmanX
committed
def execute(self, context):
settings = find_settings()
CoDEmanX
committed
CoDEmanX
committed
return {'FINISHED'}
class DeleteCursor3DBookmarkLibrary(bpy.types.Operator):
bl_idname = "scene.cursor_3d_delete_bookmark_library"
bl_label = "Delete Library"
bl_description = "Delete active bookmark library"
CoDEmanX
committed
def execute(self, context):
settings = find_settings()
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
return {'FINISHED'}
# ===== MAIN PROPERTIES ===== #
# TODO: ~a bug? Somewhy tooltip shows "Cursor3DToolsSettings.foo"
# instead of "bpy.types.Screen.cursor_3d_tools_settings.foo"
class Cursor3DToolsSettings(bpy.types.PropertyGroup):
transform_options = bpy.props.PointerProperty(
type=TransformExtraOptionsProp,
options={'HIDDEN'})
CoDEmanX
committed
cursor_visible: bpy.props.BoolProperty(
name="Cursor visibility",
description="Show/hide cursor. When hidden, "\
"Blender continuously redraws itself (eats CPU like crazy, "\
"and becomes the less responsive the more complex scene you have)!",
default=True)
cursor_lock = bpy.props.BoolProperty(
name="Lock cursor location",
description="Prevent accidental cursor movement",
default=False)
draw_guides = bpy.props.BoolProperty(
name="Guides",
description="Display guides",
default=True)
CoDEmanX
committed
draw_snap_elements = bpy.props.BoolProperty(
name="Snap elements",
description="Display snap elements",
default=True)
CoDEmanX
committed
draw_N = bpy.props.BoolProperty(
name="Surface normal",
description="Display surface normal",
default=True)
CoDEmanX
committed
draw_T1 = bpy.props.BoolProperty(
name="Surface 1st tangential",
description="Display 1st surface tangential",
default=True)
CoDEmanX
committed
draw_T2 = bpy.props.BoolProperty(
name="Surface 2nd tangential",
description="Display 2nd surface tangential",
default=True)
CoDEmanX
committed
stick_to_obj = bpy.props.BoolProperty(
name="Stick to objects",
description="Move cursor along with object it was snapped to",
default=True)
CoDEmanX
committed
# HISTORY-RELATED
history = bpy.props.PointerProperty(
type=CursorHistoryProp,
options={'HIDDEN'})
CoDEmanX
committed
# BOOKMARK-RELATED
libraries = bpy.props.PointerProperty(
type=BookmarkLibraryIDBlock,
options={'HIDDEN'})
CoDEmanX
committed
show_bookmarks = bpy.props.BoolProperty(
name="Show bookmarks",
description="Show active bookmark in 3D view",
default=True,
options={'HIDDEN'})
CoDEmanX
committed
free_coord_precision = bpy.props.IntProperty(
name="Coord precision",
description="Numer of digits afer comma "\
"for displayed coordinate values",
default=4,
min=0,
max=10,
options={'HIDDEN'})
auto_register_keymaps = bpy.props.BoolProperty(
name="Auto Register Keymaps",
default=True)
class Cursor3DToolsSceneSettings(bpy.types.PropertyGroup):
stick_obj_name: bpy.props.StringProperty(
name="Stick-to-object name",
description="Name of the object to stick cursor to",
options={'HIDDEN'})
stick_obj_pos: bpy.props.FloatVectorProperty(
default=(0.0, 0.0, 0.0),
options={'HIDDEN'},
subtype='XYZ')
# ===== CURSOR RUNTIME PROPERTIES ===== #
class CursorRuntimeSettings(bpy.types.PropertyGroup):
current_monitor_id: bpy.props.IntProperty(
CoDEmanX
committed
surface_pos: bpy.props.FloatVectorProperty(
default=(0.0, 0.0, 0.0),
options={'HIDDEN'},
subtype='XYZ')
use_cursor_monitor: bpy.props.BoolProperty(
name="Enable Cursor Monitor",
description="Record 3D cursor history "\
"(uses a background modal operator)",
default=True)
class CursorDynamicSettings:
local_matrix = Matrix()
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
@classmethod
def recalc_csu(cls, context, event_value=None):
scene_hash_changed = (cls.active_scene_hash != hash(context.scene))
cls.active_scene_hash = hash(context.scene)
CoDEmanX
committed
# Don't recalc if mouse is over some UI panel!
# (otherwise, this may lead to applying operator
# (e.g. Subdivide) in Edit Mode, even if user
# just wants to change some operator setting)
clicked = (event_value in {'PRESS', 'RELEASE'}) and \
(context.region.type == 'WINDOW')
CoDEmanX
committed
if clicked or scene_hash_changed:
particles, cls.csu = gather_particles()
#============================================================================#
# ===== PANELS AND DIALOGS ===== #
class TransformExtraOptions(bpy.types.Panel):
bl_label = "Transform Extra Options"
bl_idname = "OBJECT_PT_transform_extra_options"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
#bl_context = "object"
bl_options = {'DEFAULT_CLOSED'}
CoDEmanX
committed
def draw(self, context):
layout = self.layout
CoDEmanX
committed
settings = find_settings()
tfm_opts = settings.transform_options
CoDEmanX
committed
layout.prop(tfm_opts, "use_relative_coords")
layout.prop(tfm_opts, "snap_only_to_solid")
layout.prop(tfm_opts, "snap_interpolate_normals_mode", text="")
Dima Glib
committed
layout.prop(tfm_opts, "use_comma_separator")
#layout.prop(tfm_opts, "snap_element_screen_size")
class Cursor3DTools(bpy.types.Panel):
bl_label = "3D Cursor Tools"
bl_idname = "OBJECT_PT_cursor_3d_tools"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
CoDEmanX
committed
def draw(self, context):
layout = self.layout
CoDEmanX
committed
# Attempt to launch the monitor
if bpy.ops.view3d.cursor3d_monitor.poll():
bpy.ops.view3d.cursor3d_monitor()
#=============================================#
CoDEmanX
committed
CoDEmanX
committed
row = layout.split(0.5)
#row = layout.row()
row.operator("view3d.set_cursor3d_dialog",
"Set", 'CURSOR')
row = row.split(1 / 3, align=True)
#row = row.row(align=True)
row.prop(settings, "draw_guides",
text="", icon='MANIPUL', toggle=True)
row.prop(settings, "draw_snap_elements",
text="", icon='EDITMODE_HLT', toggle=True)
row.prop(settings, "stick_to_obj",
text="", icon='SNAP_ON', toggle=True)
CoDEmanX
committed
row = layout.split(0.5)
subrow.prop(settings, "cursor_lock", text="", toggle=True,
icon=('LOCKED' if settings.cursor_lock else 'UNLOCKED'))
subrow = subrow.split(1)
subrow.alert = True
subrow.prop(settings, "cursor_visible", text="", toggle=True,
icon=('RESTRICT_VIEW_OFF' if settings.cursor_visible
else 'RESTRICT_VIEW_ON'))
row = row.split(1 / 3, align=True)
row.prop(settings, "draw_N",
text="N", toggle=True, index=0)
row.prop(settings, "draw_T1",
text="T1", toggle=True, index=1)
row.prop(settings, "draw_T2",
text="T2", toggle=True, index=2)
CoDEmanX
committed
# === HISTORY === #
history = settings.history
row = layout.row(align=True)
row.prop(wm.cursor_3d_runtime_settings, "use_cursor_monitor",
text="", toggle=True, icon='REC')
row.prop(history, "show_trace", text="", icon='SORTTIME')
row = row.split(0.35, True)
row.prop(history, "max_size", text="")
row.prop(history, "current_id", text="")
CoDEmanX
committed
# === BOOKMARK LIBRARIES === #
settings.libraries.draw(context, layout)
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
row = layout.row()
row.prop(settings, "show_bookmarks",
text="", icon='RESTRICT_VIEW_OFF')
row = row.row(align=True)
row.prop(library, "system", text="")
row.prop(library, "offset", text="",
icon='ARROW_LEFTRIGHT')
CoDEmanX
committed
# === BOOKMARKS === #
library.bookmarks.draw(context, layout)
CoDEmanX
committed
if len(library.bookmarks.collection) == 0:
return
CoDEmanX
committed
row = layout.row()
row = row.split(align=True)
# PASTEDOWN
# COPYDOWN
row.operator("scene.cursor_3d_overwrite_bookmark",
text="", icon='REC')
row.operator("scene.cursor_3d_swap_bookmark",
text="", icon='FILE_REFRESH')
row.operator("scene.cursor_3d_recall_bookmark",
text="", icon='FILE_TICK')
row.operator("scene.cursor_3d_add_empty_at_bookmark",
text="", icon='EMPTY_DATA')
# Not implemented (and maybe shouldn't)
#row.operator("scene.cursor_3d_snap_selection_to_bookmark",
# text="", icon='SNAP_ON')
class SetCursorDialog(bpy.types.Operator):
bl_idname = "view3d.set_cursor3d_dialog"
bl_label = "Set 3D Cursor"
bl_description = "Set 3D Cursor XYZ values"
CoDEmanX
committed
pos: bpy.props.FloatVectorProperty(
name="Location",
description="3D Cursor location in current coordinate system",
subtype='XYZ',
)
CoDEmanX
committed
@classmethod
def poll(cls, context):
return context.area.type == 'VIEW_3D'
CoDEmanX
committed
def execute(self, context):
scene = context.scene
CoDEmanX
committed
# "current system" / "relative" could have changed
self.matrix = self.csu.get_matrix()
CoDEmanX
committed
set_cursor_location(pos, v3d=context.space_data)
CoDEmanX
committed
return {'FINISHED'}
def invoke(self, context, event):
scene = context.scene
CoDEmanX
committed
cursor_pos = get_cursor_location(v3d=context.space_data)
CoDEmanX
committed
particles, self.csu = gather_particles(context=context)
self.csu.source_pos = cursor_pos
CoDEmanX
committed
CoDEmanX
committed
try:
self.pos = self.matrix.inverted() * cursor_pos
except:
# this is some degenerate system
self.pos = Vector()
CoDEmanX
committed
wm = context.window_manager
return wm.invoke_props_dialog(self, width=160)
CoDEmanX
committed
def draw(self, context):
layout = self.layout
CoDEmanX
committed
settings = find_settings()
tfm_opts = settings.transform_options
CoDEmanX
committed
CoDEmanX
committed
col = layout.column()
col.prop(self, "pos", text="")
CoDEmanX
committed
row = layout.row()
row.prop(tfm_opts, "use_relative_coords", text="Relative")
row.prop(v3d, "transform_orientation", text="")
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
# Adapted from Chromoly's lock_cursor3d
def selection_global_positions(context):
if context.mode == 'EDIT_MESH':
ob = context.active_object
mat = ob.matrix_world
bm = bmesh.from_edit_mesh(ob.data)
verts = [v for v in bm.verts if v.select]
return [mat * v.co for v in verts]
elif context.mode == 'OBJECT':
return [ob.matrix_world.to_translation()
for ob in context.selected_objects]
# Adapted from Chromoly's lock_cursor3d
def center_of_circumscribed_circle(vecs):
if len(vecs) == 1:
return vecs[0]
elif len(vecs) == 2:
return (vecs[0] + vecs[1]) / 2
elif len(vecs) == 3:
v1, v2, v3 = vecs
if v1 != v2 and v2 != v3 and v3 != v1:
v12 = v2 - v1
v13 = v3 - v1
med12 = (v1 + v2) / 2
med13 = (v1 + v3) / 2
per12 = v13 - v13.project(v12)
per13 = v12 - v12.project(v13)
inter = intersect_line_line(med12, med12 + per12,
med13, med13 + per13)
if inter:
return (inter[0] + inter[1]) / 2
return (v1 + v2 + v3) / 3
return None
def center_of_inscribed_circle(vecs):
if len(vecs) == 1:
return vecs[0]
elif len(vecs) == 2:
return (vecs[0] + vecs[1]) / 2
elif len(vecs) == 3:
v1, v2, v3 = vecs
L1 = (v3 - v2).magnitude
L2 = (v3 - v1).magnitude
L3 = (v2 - v1).magnitude
return (L1*v1 + L2*v2 + L3*v3) / (L1 + L2 + L3)
return None
# Adapted from Chromoly's lock_cursor3d
class SnapCursor_Circumscribed(bpy.types.Operator):
bl_idname = "view3d.snap_cursor_to_circumscribed"
bl_label = "Cursor to Circumscribed"
bl_description = "Snap cursor to the center of the circumscribed circle"
def execute(self, context):
vecs = selection_global_positions(context)
if vecs is None:
self.report({'WARNING'}, 'Not implemented \
for %s mode' % context.mode)
return {'CANCELLED'}
pos = center_of_circumscribed_circle(vecs)
if pos is None:
self.report({'WARNING'}, 'Select 3 objects/elements')
return {'CANCELLED'}
set_cursor_location(pos, v3d=context.space_data)
return {'FINISHED'}
class SnapCursor_Inscribed(bpy.types.Operator):
bl_idname = "view3d.snap_cursor_to_inscribed"
bl_label = "Cursor to Inscribed"
bl_description = "Snap cursor to the center of the inscribed circle"
def execute(self, context):
vecs = selection_global_positions(context)
if vecs is None:
self.report({'WARNING'}, 'Not implemented \
for %s mode' % context.mode)
return {'CANCELLED'}
pos = center_of_inscribed_circle(vecs)
if pos is None:
self.report({'WARNING'}, 'Select 3 objects/elements')
return {'CANCELLED'}
set_cursor_location(pos, v3d=context.space_data)
return {'FINISHED'}
class AlignOrientationProperties(bpy.types.PropertyGroup):
axes_items = [
('X', 'X', 'X axis'),
('Y', 'Y', 'Y axis'),
('Z', 'Z', 'Z axis'),
('-X', '-X', '-X axis'),
('-Y', '-Y', '-Y axis'),
('-Z', '-Z', '-Z axis'),
]
CoDEmanX
committed
axes_items_ = [
('X', 'X', 'X axis'),
('Y', 'Y', 'Y axis'),
('Z', 'Z', 'Z axis'),
(' ', ' ', 'Same as source axis'),
]
CoDEmanX
committed
def get_orients(self, context):
orients = []
orients.append(('GLOBAL', "Global", ""))
orients.append(('LOCAL', "Local", ""))
orients.append(('GIMBAL', "Gimbal", ""))
orients.append(('NORMAL', "Normal", ""))
orients.append(('VIEW', "View", ""))
CoDEmanX
committed
if context is not None:
for orientation in context.scene.orientations:
name = orientation.name
orients.append((name, name, ""))
CoDEmanX
committed
CoDEmanX
committed
src_axis: bpy.props.EnumProperty(default='Z', items=axes_items,
name="Initial axis")
#src_orient = bpy.props.EnumProperty(default='GLOBAL', items=get_orients)
CoDEmanX
committed
dest_axis: bpy.props.EnumProperty(default=' ', items=axes_items_,
dest_orient: bpy.props.EnumProperty(items=get_orients,
class AlignOrientation(bpy.types.Operator):
bl_idname = "view3d.align_orientation"
bl_label = "Align Orientation"
bl_description = "Rotates active object to match axis of current "\
"orientation to axis of another orientation"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
axes_items = [
('X', 'X', 'X axis'),
('Y', 'Y', 'Y axis'),
('Z', 'Z', 'Z axis'),
('-X', '-X', '-X axis'),
('-Y', '-Y', '-Y axis'),
('-Z', '-Z', '-Z axis'),
]
CoDEmanX
committed
axes_items_ = [
('X', 'X', 'X axis'),
('Y', 'Y', 'Y axis'),
('Z', 'Z', 'Z axis'),
(' ', ' ', 'Same as source axis'),
]
CoDEmanX
committed
CoDEmanX
committed
def get_orients(self, context):
orients = []
orients.append(('GLOBAL', "Global", ""))
orients.append(('LOCAL', "Local", ""))
orients.append(('GIMBAL', "Gimbal", ""))
orients.append(('NORMAL', "Normal", ""))
orients.append(('VIEW', "View", ""))
CoDEmanX
committed
if context is not None:
for orientation in context.scene.orientations:
name = orientation.name
orients.append((name, name, ""))
CoDEmanX
committed
CoDEmanX
committed
src_axis: bpy.props.EnumProperty(default='Z', items=axes_items,
name="Initial axis")
#src_orient = bpy.props.EnumProperty(default='GLOBAL', items=get_orients)
CoDEmanX
committed
dest_axis: bpy.props.EnumProperty(default=' ', items=axes_items_,
dest_orient: bpy.props.EnumProperty(items=get_orients,
CoDEmanX
committed
@classmethod
def poll(cls, context):
return (context.area.type == 'VIEW_3D') and context.object
CoDEmanX
committed
obj = context.object
scene = context.scene
v3d = context.space_data
rv3d = context.region_data
CoDEmanX
committed
particles, csu = gather_particles(context=context)
tou = csu.tou
#tou = TransformOrientationUtility(scene, v3d, rv3d)
CoDEmanX
committed
aop = wm.align_orientation_properties # self
CoDEmanX
committed
src_matrix = tou.get_matrix()
src_axes = MatrixDecompose(src_matrix)
if src_axis_name.startswith("-"):
src_axis_name = src_axis_name[1:]
src_axis = -src_axes[self.axes_ids[src_axis_name]]
else:
src_axis = src_axes[self.axes_ids[src_axis_name]]
CoDEmanX
committed
dest_matrix = tou.get_matrix()
dest_axes = MatrixDecompose(dest_matrix)
if self.dest_axis != ' ':
else:
dest_axis_name = src_axis_name
dest_axis = dest_axes[self.axes_ids[dest_axis_name]]
CoDEmanX
committed
q = src_axis.rotation_difference(dest_axis)
CoDEmanX
committed
m = obj.matrix_world.to_3x3()
m.rotate(q)
m.resize_4x4()
m.translation = obj.matrix_world.translation.copy()
CoDEmanX
committed
CoDEmanX
committed
#bpy.ops.ed.undo_push(message="Align Orientation")
CoDEmanX
committed
CoDEmanX
committed
Dima Glib
committed
# ATTENTION!
# This _must_ be a dialog, because with 'UNDO' option
# the last selected orientation may revert to the previous state
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=200)
CoDEmanX
committed
def draw(self, context):
layout = self.layout
wm = context.window_manager
aop = wm.align_orientation_properties # self
layout.prop(aop, "src_axis")
layout.prop(aop, "dest_axis")
layout.prop(aop, "dest_orient")
Dima Glib
committed
class CopyOrientation(bpy.types.Operator):
bl_idname = "view3d.copy_orientation"
bl_label = "Copy Orientation"
bl_description = "Makes a copy of current orientation"
CoDEmanX
committed
Dima Glib
committed
def execute(self, context):
scene = context.scene
v3d = context.space_data
rv3d = context.region_data
CoDEmanX
committed
particles, csu = gather_particles(context=context)
tou = csu.tou
#tou = TransformOrientationUtility(scene, v3d, rv3d)
CoDEmanX
committed
Dima Glib
committed
name=tou.get()+".copy", matrix=tou.get_matrix())
CoDEmanX
committed
CoDEmanX
committed
Dima Glib
committed
return {'FINISHED'}
def transform_orientations_panel_extension(self, context):
row = self.layout.row()
row.operator("view3d.align_orientation", text="Align")
row.operator("view3d.copy_orientation", text="Copy")
Dima Glib
committed
# ===== CURSOR MONITOR ===== #
class CursorMonitor(bpy.types.Operator):
"""Monitor changes in cursor location and write to history"""
bl_idname = "view3d.cursor3d_monitor"
bl_label = "Cursor Monitor"
CoDEmanX
committed
# A class-level variable (it must be accessed from poll())
is_running = False
CoDEmanX
committed
CoDEmanX
committed
Sebastian Nell
committed
_handle_view = None
_handle_px = None
_handle_header_px = None
CoDEmanX
committed
script_reload_kmis = []
Sebastian Nell
committed
@staticmethod
def handle_add(self, context):
CursorMonitor._handle_view = bpy.types.SpaceView3D.draw_handler_add(
draw_callback_view, (self, context), 'WINDOW', 'POST_VIEW')
CursorMonitor._handle_px = bpy.types.SpaceView3D.draw_handler_add(
draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
CursorMonitor._handle_header_px = bpy.types.SpaceView3D.draw_handler_add(
draw_callback_header_px, (self, context), 'HEADER', 'POST_PIXEL')
@staticmethod
def handle_remove(context):
if CursorMonitor._handle_view is not None:
bpy.types.SpaceView3D.draw_handler_remove(CursorMonitor._handle_view, 'WINDOW')
if CursorMonitor._handle_px is not None:
bpy.types.SpaceView3D.draw_handler_remove(CursorMonitor._handle_px, 'WINDOW')
if CursorMonitor._handle_header_px is not None:
bpy.types.SpaceView3D.draw_handler_remove(CursorMonitor._handle_header_px, 'HEADER')
CursorMonitor._handle_view = None
CursorMonitor._handle_px = None
CursorMonitor._handle_header_px = None
CoDEmanX
committed
@classmethod
def poll(cls, context):
try:
wm = context.window_manager
if not wm.cursor_3d_runtime_settings.use_cursor_monitor:
return False
runtime_settings = find_runtime_settings()
if not runtime_settings:
return False
CoDEmanX
committed
# When addon is enabled by default and
# user started another new scene, is_running
# would still be True
return (not CursorMonitor.is_running) or \
(runtime_settings.current_monitor_id == 0)
except Exception as e:
print("Cursor monitor exeption in poll:\n" + repr(e))
CoDEmanX
committed
wm = context.window_manager
if not wm.cursor_3d_runtime_settings.use_cursor_monitor:
self.cancel(context)
return {'CANCELLED'}
# Scripts cannot be reloaded while modal operators are running
# Intercept the corresponding event and shut down CursorMonitor
# (it would be relaunched automatically afterwards)
for kmi in CursorMonitor.script_reload_kmis:
if IsKeyMapItemEvent(kmi, event):
return {'CANCELLED'}
try:
return self._modal(context, event)
except Exception as e:
print("Cursor monitor exeption in modal:\n" + repr(e))
# Remove callbacks at any cost
self.cancel(context)
#raise
return {'CANCELLED'}
CoDEmanX
committed
def _modal(self, context, event):
runtime_settings = find_runtime_settings()
CoDEmanX
committed
# ATTENTION: will this work correctly when another
# blend is loaded? (it should, since all scripts
# seem to be reloaded in such case)
if (runtime_settings is None) or \
(self.id != runtime_settings.current_monitor_id):
# Another (newer) monitor was launched;
# this one should stop.
# (OR addon was disabled)
dairin0d
committed
self.cancel(context)
return {'CANCELLED'}
CoDEmanX
committed
Dima Glib
committed
# Somewhy after addon re-registration
# this permanently becomes False
CoDEmanX
committed
if self.update_storage(runtime_settings):
# hmm... can this cause flickering of menus?
context.area.tag_redraw()
CoDEmanX
committed
propagate_settings_to_all_screens(settings)
CoDEmanX
committed
# ================== #
# Update bookmark enums when addon is initialized.
# Since CursorMonitor operator can be called from draw(),
# we have to postpone all re-registration-related tasks
# (such as redefining the enums).
if self.just_initialized:
# update the relevant enums, bounds and other options
# (is_running becomes False once another scene is loaded,
# so this operator gets restarted)
settings.history.update_max_size()
settings.libraries.update_enum()
library = settings.libraries.get_item()
if library:
library.bookmarks.update_enum()
CoDEmanX
committed
self.just_initialized = False
# ================== #
CoDEmanX
committed
# Seems like recalc_csu() in this place causes trouble
# if space type is switched from 3D to e.g. UV
'''
tfm_operator = CursorDynamicSettings.active_transform_operator
if tfm_operator:
CursorDynamicSettings.csu = tfm_operator.csu
else:
CursorDynamicSettings.recalc_csu(context, event.value)
'''
CoDEmanX
committed
CoDEmanX
committed
def update_storage(self, runtime_settings):
if CursorDynamicSettings.active_transform_operator:
# Don't add to history while operator is running
return False
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
# History doesn't depend on view (?)
curr_pos = get_cursor_location(scene=scene)
CoDEmanX
committed
CoDEmanX
committed
# Ignore newly-created or some renamed scenes
if scene.name in self.last_locations:
if curr_pos != self.last_locations[scene.name]:
new_pos = curr_pos
elif runtime_settings.current_monitor_id == 0:
# startup location should be added
new_pos = curr_pos
CoDEmanX
committed
# Seems like scene.cursor_location is fast enough here
# -> no need to resort to v3d.cursor_location.
"""
screen = bpy.context.screen
scene = screen.scene
v3d = None
for area in screen.areas:
for space in area.spaces:
if space.type == 'VIEW_3D':
v3d = space
break
CoDEmanX
committed
curr_pos = get_cursor_location(v3d=v3d)
CoDEmanX
committed
CoDEmanX
committed
# Ignore newly-created or some renamed scenes
if scene.name in self.last_locations:
if curr_pos != self.last_locations[scene.name]:
new_pos = curr_pos
"""
CoDEmanX
committed
CoDEmanX
committed
if new_pos is not None:
settings = find_settings()
history = settings.history
CoDEmanX
committed
pos = history.get_pos()
if (pos is not None):# and (history.current_id != 0): # ?
if pos == new_pos:
return False # self.just_initialized ?
CoDEmanX
committed
entry = history.entries.add()
entry.pos = new_pos
CoDEmanX
committed
last_id = len(history.entries) - 1
history.entries.move(last_id, 0)
CoDEmanX
committed
if last_id > int(history.max_size):
history.entries.remove(last_id)
CoDEmanX
committed
# make sure the most recent history entry is displayed
CoDEmanX
committed
CursorHistoryProp.update_cursor_on_id_change = False
CursorHistoryProp.update_cursor_on_id_change = True
CoDEmanX
committed
history.curr_id = history.current_id
history.last_id = 1
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
def execute(self, context):
print("Cursor monitor: launched")
CoDEmanX
committed
CursorMonitor.script_reload_kmis = list(KeyMapItemSearch('script.reload'))
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
# Important! Call update_storage() before assigning
# current_monitor_id (used to add startup cursor location)
self.update_storage(runtime_settings)
CoDEmanX
committed
# Indicate that this is the most recent monitor.
# All others should shut down.
self.id = runtime_settings.current_monitor_id + 1
runtime_settings.current_monitor_id = self.id
CoDEmanX
committed
CoDEmanX
committed
CursorDynamicSettings.recalc_csu(context, 'PRESS')
# I suppose that cursor position would change
# only with user interaction.
#self._timer = context.window_manager. \
# event_timer_add(0.1, context.window)
CoDEmanX
committed
Sebastian Nell
committed
CursorMonitor.handle_add(self, context)
CoDEmanX
committed
# Here we cannot return 'PASS_THROUGH',
# or Blender will crash!
# Currently there seems to be only one window
context.window_manager.modal_handler_add(self)
CoDEmanX
committed
def cancel(self, context):
CursorMonitor.is_running = False
#type(self).is_running = False
CoDEmanX
committed
Sebastian Nell
committed
CursorMonitor.handle_remove(context)
# ===== MATH / GEOMETRY UTILITIES ===== #
def to_matrix4x4(orient, pos):
if not isinstance(orient, Matrix):
orient = orient.to_matrix()
m = orient.to_4x4()
m.translation = pos.to_3d()
def MatrixCompose(*args):
size = len(args)
m = Matrix.Identity(size)
axes = m.col # m.row
CoDEmanX
committed
if size == 2:
for i in (0, 1):
c = args[i]
if isinstance(c, Vector):
axes[i] = c.to_2d()
elif hasattr(c, "__iter__"):
axes[i] = Vector(c).to_2d()
else:
axes[i][i] = c
else:
for i in (0, 1, 2):
c = args[i]
if isinstance(c, Vector):
axes[i][:3] = c.to_3d()
elif hasattr(c, "__iter__"):
axes[i][:3] = Vector(c).to_3d()
else:
axes[i][i] = c
CoDEmanX
committed
if size == 4:
c = args[3]
if isinstance(c, Vector):
m.translation = c.to_3d()
elif hasattr(c, "__iter__"):
m.translation = Vector(c).to_3d()
CoDEmanX
committed
return m
def MatrixDecompose(m, res_size=None):
size = len(m)
axes = m.col # m.row
if res_size is None:
res_size = size
CoDEmanX
committed
if res_size == 2:
return (axes[0].to_2d(), axes[1].to_2d())
else:
x = axes[0].to_3d()
y = axes[1].to_3d()
z = (axes[2].to_3d() if size > 2 else Vector())
if res_size == 3:
return (x, y, z)
CoDEmanX
committed
t = (m.translation.to_3d() if size == 4 else Vector())
if res_size == 4:
return (x, y, z, t)
def angle_axis_to_quat(angle, axis):
w = math.cos(angle / 2.0)
xyz = axis.normalized() * math.sin(angle / 2.0)
return Quaternion((w, xyz.x, xyz.y, xyz.z))
def round_step(x, s=1.0):
#return math.floor(x * s + 0.5) / s
return math.floor(x / s + 0.5) * s
twoPi = 2.0 * math.pi
def clamp_angle(ang):
# Attention! In Python the behaviour is:
# -359.0 % 180.0 == 1.0
# -359.0 % -180.0 == -179.0
ang = (ang % twoPi)
return ((ang - twoPi) if (ang > math.pi) else ang)
Dima Glib
committed
def prepare_grid_mesh(bm, nx=1, ny=1, sx=1.0, sy=1.0,
z=0.0, xyz_indices=(0,1,2)):
vertices = []
for i in range(nx + 1):
x = 2 * (i / nx) - 1
x *= sx
for j in range(ny + 1):
y = 2 * (j / ny) - 1
y *= sy
pos = (x, y, z)
Dima Glib
committed
vert = bm.verts.new((pos[xyz_indices[0]],
pos[xyz_indices[1]],
pos[xyz_indices[2]]))
vertices.append(vert)
CoDEmanX
committed
nxmax = nx + 1
for i in range(nx):
i1 = i + 1
for j in range(ny):
j1 = j + 1
Dima Glib
committed
verts = [vertices[j + i * nxmax],
vertices[j1 + i * nxmax],
vertices[j1 + i1 * nxmax],
vertices[j + i1 * nxmax]]
bm.faces.new(verts)
#return
Dima Glib
committed
bm = bmesh.new()
CoDEmanX
committed
sides = [
(-1, (0,1,2)), # -Z
(1, (1,0,2)), # +Z
(-1, (1,2,0)), # -Y
(1, (0,2,1)), # +Y
(-1, (2,0,1)), # -X
(1, (2,1,0)), # +X
]
CoDEmanX
committed