Newer
Older
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ***** END GPL LICENSE BLOCK *****
# <pep8-80 compliant>
bl_info = {
"name": "Enhanced 3D Cursor",
"description": "Cursor history and bookmarks; drag/snap cursor.",
"author": "dairin0d",
"version": (2, 9, 2),
Sebastian Nell
committed
"blender": (2, 65, 4),
"location": "View3D > Action mouse; F10; Properties panel",
"warning": "",
CoDEmanX
committed
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
CoDEmanX
committed
"tracker_url": "https://developer.blender.org/T28451",
ATTENTION:
somewhere around 45447 revision object.ray_cast() starts conflicting with
mesh.update(calc_tessface=True) -- at least when invoked within one
operator cycle, object.ray_cast() crashes if object's tessfaces were
update()d earlier in the code. However, not update()ing the meshes
seems to work fine -- ray_cast() does its job, and it's possible to
access tessfaces afterwards.
mesh.calc_tessface() -- ? crashes too
Seems like now axes are stored in columns instead of rows.
Perhaps it's better to write utility functions to create/decompose
matrices from/to 3D-vector axes and a translation component
Dima Glib
committed
Breakdown:
Addon registration
Keymap utils
Various utils (e.g. find_region)
OpenGL; drawing utils
Non-undoable data storage
Cursor utils
Stick-object
Cursor monitor
Addon's GUI
Addon's properties
Addon's operators
ID Block emulator
Mesh cache
Snap utils
View3D utils
Transform orientation / coordinate system utils
Generic transform utils
Main operator
...
.
First step is to re-make the cursor addon (make something usable first).
CAD tools should be done without the hassle.
Dima Glib
committed
strip trailing space? (one of campbellbarton's commits did that)
CoDEmanX
committed
Dima Glib
committed
- implement 'GIMBAL' orientation (euler axes)
- mini-Z-buffer in the vicinity of mouse coords (using raycasts)
- an orientation that points towards cursor
(from current selection to cursor)
- user coordinate systems (using e.g. empties to store different
systems; when user switches to such UCS, origin will be set to
"cursor", cursor will be sticked to the empty, and a custom
transform orientation will be aligned with the empty)
- "Stick" transform orientation that is always aligned with the
object cursor is "sticked" to?
Dima Glib
committed
- user preferences? (stored in a file)
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
- create spline/edge_mesh from history?
- API to access history/bookmarks/operators from other scripts?
- Snap selection to bookmark?
- Optimize
- Clean up code, move to several files?
LATER:
ISSUES:
Limitations:
- I need to emulate in Python some things that Blender doesn't
currently expose through API:
- obtaining matrix of predefined transform orientation
- obtaining position of pivot
For some kinds of information (e.g. active vertex/edge,
selected meta-elements), there is simply no workaround.
- Snapping to vertices/edges works differently than in Blender.
First of all, iteration over all vertices/edges of all
objects along the ray is likely to be very slow.
Second, it's more human-friendly to snap to visible
elements (or at least with approximately known position).
- In editmode I have to exit-and-enter it to get relevant
information about current selection. Thus any operator
would automatically get applied when you click on 3D View.
Mites:
QUESTIONS:
==============================================================================
Borrowed code/logic:
- space_view3d_panel_measure.py (Buerbaum Martin "Pontiac"):
- OpenGL state storing/restoring; working with projection matrices.
"""
import bpy
import bgl
import blf
Dima Glib
committed
import bmesh
from mathutils import Vector, Matrix, Quaternion, Euler
from mathutils.geometry import (intersect_line_sphere,
intersect_ray_tri,
barycentric_transform,
Dima Glib
committed
tessellate_polygon,
intersect_line_line,
intersect_line_plane,
)
from bpy_extras.view3d_utils import (region_2d_to_location_3d,
location_3d_to_region_2d,
)
import math
import time
# ====== MODULE GLOBALS / CONSTANTS ====== #
tmp_name = chr(0x10ffff) # maximal Unicode value
epsilon = 0.000001
# ====== SET CURSOR OPERATOR ====== #
class EnhancedSetCursor(bpy.types.Operator):
"""Cursor history and bookmarks; drag/snap cursor."""
bl_idname = "view3d.cursor3d_enhanced"
bl_label = "Enhanced Set Cursor"
CoDEmanX
committed
key_char_map = {
'PERIOD':".", 'NUMPAD_PERIOD':".",
'MINUS':"-", 'NUMPAD_MINUS':"-",
'EQUAL':"+", 'NUMPAD_PLUS':"+",
#'E':"e", # such big/small numbers aren't useful
'ONE':"1", 'NUMPAD_1':"1",
'TWO':"2", 'NUMPAD_2':"2",
'THREE':"3", 'NUMPAD_3':"3",
'FOUR':"4", 'NUMPAD_4':"4",
'FIVE':"5", 'NUMPAD_5':"5",
'SIX':"6", 'NUMPAD_6':"6",
'SEVEN':"7", 'NUMPAD_7':"7",
'EIGHT':"8", 'NUMPAD_8':"8",
'NINE':"9", 'NUMPAD_9':"9",
'ZERO':"0", 'NUMPAD_0':"0",
'SPACE':" ",
'SLASH':"/", 'NUMPAD_SLASH':"/",
'NUMPAD_ASTERIX':"*",
}
CoDEmanX
committed
key_coordsys_map = {
'LEFT_BRACKET':-1,
'RIGHT_BRACKET':1,
'J':'VIEW',
'K':"Surface",
'L':'LOCAL',
'B':'GLOBAL',
'N':'NORMAL',
'M':"Scaled",
}
CoDEmanX
committed
key_pivot_map = {
'H':'ACTIVE',
'U':'CURSOR',
'I':'INDIVIDUAL',
'O':'CENTER',
'P':'MEDIAN',
}
CoDEmanX
committed
key_snap_map = {
'C':'INCREMENT',
'V':'VERTEX',
'E':'EDGE',
'F':'FACE',
}
CoDEmanX
committed
key_tfm_mode_map = {
'G':'MOVE',
'R':'ROTATE',
'S':'SCALE',
}
CoDEmanX
committed
key_map = {
"confirm":{'ACTIONMOUSE'}, # also 'RET' ?
"cancel":{'SELECTMOUSE', 'ESC'},
"free_mouse":{'F10'},
"make_normal_snapshot":{'W'},
"make_tangential_snapshot":{'Q'},
"use_absolute_coords":{'A'},
"snap_to_raw_mesh":{'D'},
"use_object_centers":{'T'},
"precision_up":{'PAGE_UP'},
"precision_down":{'PAGE_DOWN'},
"move_caret_prev":{'LEFT_ARROW'},
"move_caret_next":{'RIGHT_ARROW'},
"move_caret_home":{'HOME'},
"move_caret_end":{'END'},
"change_current_axis":{'TAB', 'RET', 'NUMPAD_ENTER'},
"prev_axis":{'UP_ARROW'},
"next_axis":{'DOWN_ARROW'},
"remove_next_character":{'DEL'},
"remove_last_character":{'BACK_SPACE'},
"copy_axes":{'C'},
"paste_axes":{'V'},
"cut_axes":{'X'},
}
CoDEmanX
committed
CoDEmanX
committed
angle_grid_steps = {True:1.0, False:5.0}
scale_grid_steps = {True:0.01, False:0.1}
CoDEmanX
committed
# ====== OPERATOR METHOD OVERLOADS ====== #
@classmethod
def poll(cls, context):
area_types = {'VIEW_3D',} # also: IMAGE_EDITOR ?
return (context.area.type in area_types) and \
(context.region.type == "WINDOW")
CoDEmanX
committed
def modal(self, context, event):
context.area.tag_redraw()
return self.try_process_input(context, event)
CoDEmanX
committed
def invoke(self, context, event):
# Attempt to launch the monitor
if bpy.ops.view3d.cursor3d_monitor.poll():
bpy.ops.view3d.cursor3d_monitor()
CoDEmanX
committed
# Don't interfere with these modes when only mouse is pressed
if ('SCULPT' in context.mode) or ('PAINT' in context.mode):
if "MOUSE" in event.type:
return {'CANCELLED'}
CoDEmanX
committed
CursorDynamicSettings.active_transform_operator = self
CoDEmanX
committed
CoDEmanX
committed
settings = find_settings()
tfm_opts = settings.transform_options
CoDEmanX
committed
settings_scene = context.scene.cursor_3d_tools_settings
CoDEmanX
committed
Dima Glib
committed
self.setup_keymaps(context, event)
CoDEmanX
committed
# Coordinate System Utility
self.particles, self.csu = gather_particles(context=context)
self.particles = [View3D_Cursor(context)]
CoDEmanX
committed
self.csu.source_pos = self.particles[0].get_location()
self.csu.source_rot = self.particles[0].get_rotation()
self.csu.source_scale = self.particles[0].get_scale()
CoDEmanX
committed
# View3D Utility
self.vu = ViewUtility(context.region, context.space_data,
context.region_data)
CoDEmanX
committed
# Snap Utility
self.su = SnapUtility(context)
CoDEmanX
committed
# turn off view locking for the duration of the operator
self.view_pos = self.vu.get_position(True)
self.vu.set_position(self.vu.get_position(), True)
self.view_locks = self.vu.get_locks()
self.vu.set_locks({})
CoDEmanX
committed
# Initialize runtime states
self.initiated_by_mouse = ("MOUSE" in event.type)
self.free_mouse = not self.initiated_by_mouse
self.use_object_centers = False
self.axes_values = ["", "", ""]
self.axes_coords = [None, None, None]
self.axes_eval_success = [True, True, True]
self.allowed_axes = [True, True, True]
self.current_axis = 0
self.caret_pos = 0
self.coord_format = "{:." + str(settings.free_coord_precision) + "f}"
self.transform_mode = 'MOVE'
self.init_xy_angle_distance(context, event)
CoDEmanX
committed
self.click_start = time.time()
if not self.initiated_by_mouse:
self.click_start -= self.click_period
CoDEmanX
committed
self.stick_obj_name = settings_scene.stick_obj_name
self.stick_obj_pos = settings_scene.stick_obj_pos
CoDEmanX
committed
# Initial run
self.try_process_input(context, event, True)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
CoDEmanX
committed
def cancel(self, context):
for particle in self.particles:
particle.revert()
CoDEmanX
committed
set_stick_obj(context.scene, self.stick_obj_name, self.stick_obj_pos)
CoDEmanX
committed
CoDEmanX
committed
# ====== CLEANUP/FINALIZE ====== #
def finalize(self, context):
# restore view locking
self.vu.set_locks(self.view_locks)
self.vu.set_position(self.view_pos, True)
CoDEmanX
committed
CoDEmanX
committed
# This is to avoid "blinking" of
# between-history-positions line
settings = find_settings()
history = settings.history
# make sure the most recent history entry is displayed
history.curr_id = 0
history.last_id = 0
CoDEmanX
committed
# Ensure there are no leftovers from draw_callback
context.area.tag_redraw()
CoDEmanX
committed
CoDEmanX
committed
def cleanup(self, context):
self.particles = None
self.csu = None
self.vu = None
if self.su is not None:
self.su.dispose()
self.su = None
CoDEmanX
committed
CursorDynamicSettings.active_transform_operator = None
CoDEmanX
committed
Dima Glib
committed
def setup_keymaps(self, context, event=None):
CoDEmanX
committed
# There is no such event as 'ACTIONMOUSE',
# it's always 'LEFTMOUSE' or 'RIGHTMOUSE'
Dima Glib
committed
if event:
if event.type == 'LEFTMOUSE':
self.key_map["confirm"] = {'LEFTMOUSE'}
self.key_map["cancel"] = {'RIGHTMOUSE', 'ESC'}
elif event.type == 'RIGHTMOUSE':
self.key_map["confirm"] = {'RIGHTMOUSE'}
self.key_map["cancel"] = {'LEFTMOUSE', 'ESC'}
else:
event = None
if event is None:
select_mouse = context.user_preferences.inputs.select_mouse
if select_mouse == 'RIGHT':
self.key_map["confirm"] = {'LEFTMOUSE'}
self.key_map["cancel"] = {'RIGHTMOUSE', 'ESC'}
else:
self.key_map["confirm"] = {'RIGHTMOUSE'}
self.key_map["cancel"] = {'LEFTMOUSE', 'ESC'}
CoDEmanX
committed
# Use user-defined "free mouse" key, if it exists
wm = context.window_manager
if '3D View' in wm.keyconfigs.user.keymaps:
km = wm.keyconfigs.user.keymaps['3D View']
for kmi in km.keymap_items:
if kmi.idname == 'view3d.cursor3d_enhanced':
if kmi.map_type == 'KEYBOARD':
self.key_map["free_mouse"] = {kmi.type,}
break
CoDEmanX
committed
def try_process_input(self, context, event, initial_run=False):
try:
return self.process_input(context, event, initial_run)
except:
# If anything fails, at least dispose the resources
self.cleanup(context)
raise
CoDEmanX
committed
def process_input(self, context, event, initial_run=False):
wm = context.window_manager
v3d = context.space_data
CoDEmanX
committed
if event.type in self.key_map["confirm"]:
if self.free_mouse:
finished = (event.value == 'PRESS')
else:
finished = (event.value == 'RELEASE')
CoDEmanX
committed
if finished:
return self.finalize(context)
CoDEmanX
committed
if event.type in self.key_map["cancel"]:
return self.cancel(context)
CoDEmanX
committed
CoDEmanX
committed
settings = find_settings()
tfm_opts = settings.transform_options
CoDEmanX
committed
make_snapshot = False
tangential_snapshot = False
CoDEmanX
committed
if event.value == 'PRESS':
if event.type in self.key_map["free_mouse"]:
Sebastian Nell
committed
if self.free_mouse and not initial_run:
# confirm if pressed second time
return self.finalize(context)
else:
self.free_mouse = True
CoDEmanX
committed
if event.type in self.key_tfm_mode_map:
new_mode = self.key_tfm_mode_map[event.type]
CoDEmanX
committed
if self.transform_mode != new_mode:
# snap cursor to its initial state
if new_mode != 'MOVE':
for particle in self.particles:
initial_matrix = particle.get_initial_matrix()
particle.set_matrix(initial_matrix)
# reset intial mouse position
self.init_xy_angle_distance(context, event)
CoDEmanX
committed
CoDEmanX
committed
if event.type in self.key_map["make_normal_snapshot"]:
make_snapshot = True
tangential_snapshot = False
CoDEmanX
committed
if event.type in self.key_map["make_tangential_snapshot"]:
make_snapshot = True
tangential_snapshot = True
CoDEmanX
committed
if event.type in self.key_map["snap_to_raw_mesh"]:
tool_settings.use_snap_self = \
not tool_settings.use_snap_self
CoDEmanX
committed
if (not event.alt) and (event.type in {'X', 'Y', 'Z'}):
axis_lock = [(event.type == 'X') != event.shift,
(event.type == 'Y') != event.shift,
(event.type == 'Z') != event.shift]
CoDEmanX
committed
if self.allowed_axes != axis_lock:
self.allowed_axes = axis_lock
else:
self.allowed_axes = [True, True, True]
CoDEmanX
committed
if event.type in self.key_map["use_absolute_coords"]:
tfm_opts.use_relative_coords = \
not tfm_opts.use_relative_coords
CoDEmanX
committed
CoDEmanX
committed
incr = 0
if event.type in self.key_map["change_current_axis"]:
incr = (-1 if event.shift else 1)
elif event.type in self.key_map["next_axis"]:
incr = 1
elif event.type in self.key_map["prev_axis"]:
incr = -1
CoDEmanX
committed
if incr != 0:
self.current_axis = (self.current_axis + incr) % 3
self.caret_pos = len(self.axes_values[self.current_axis])
CoDEmanX
committed
incr = 0
if event.type in self.key_map["precision_up"]:
incr = 1
elif event.type in self.key_map["precision_down"]:
incr = -1
CoDEmanX
committed
if incr != 0:
settings.free_coord_precision += incr
self.coord_format = "{:." + \
str(settings.free_coord_precision) + "f}"
CoDEmanX
committed
if (event.type == 'ZERO') and event.ctrl:
self.snap_to_system_origin()
else:
self.process_axis_input(event)
CoDEmanX
committed
Dima Glib
committed
jc = (", " if tfm_opts.use_comma_separator else "\t")
Dima Glib
committed
wm.clipboard = jc.join(self.get_axes_text(True))
elif event.type in self.key_map["cut_axes"]:
Dima Glib
committed
wm.clipboard = jc.join(self.get_axes_text(True))
self.set_axes_text("\t\t\t")
elif event.type in self.key_map["paste_axes"]:
Dima Glib
committed
if jc == "\t":
self.set_axes_text(wm.clipboard, True)
else:
jc = jc.strip()
ttext = ""
brackets = 0
for c in wm.clipboard:
if c in "[{(":
brackets += 1
elif c in "]})":
brackets -= 1
if (brackets == 0) and (c == jc):
c = "\t"
ttext += c
self.set_axes_text(ttext, True)
CoDEmanX
committed
if event.type in self.key_coordsys_map:
new_orientation = self.key_coordsys_map[event.type]
self.csu.set_orientation(new_orientation)
CoDEmanX
committed
CoDEmanX
committed
if event.ctrl:
self.snap_to_system_origin()
CoDEmanX
committed
if event.type in self.key_map["use_object_centers"]:
v3d.use_pivot_point_align = not v3d.use_pivot_point_align
CoDEmanX
committed
if event.type in self.key_pivot_map:
self.csu.set_pivot(self.key_pivot_map[event.type])
CoDEmanX
committed
CoDEmanX
committed
if event.ctrl:
self.snap_to_system_origin(force_pivot=True)
CoDEmanX
committed
if (not event.alt) and (event.type in self.key_snap_map):
snap_element = self.key_snap_map[event.type]
if tool_settings.snap_element == snap_element:
if snap_element == 'VERTEX':
snap_element = 'VOLUME'
elif snap_element == 'VOLUME':
snap_element = 'VERTEX'
tool_settings.snap_element = snap_element
# end if
CoDEmanX
committed
use_snap = (tool_settings.use_snap != event.ctrl)
if use_snap:
snap_type = tool_settings.snap_element
else:
userprefs_view = context.user_preferences.view
if userprefs_view.use_mouse_depth_cursor:
# Suggested by Lissanro in the forum
use_snap = True
snap_type = 'FACE'
CoDEmanX
committed
axes_coords = [None, None, None]
if self.transform_mode == 'MOVE':
for i in range(3):
if self.axes_coords[i] is not None:
axes_coords[i] = self.axes_coords[i]
elif not self.allowed_axes[i]:
axes_coords[i] = 0.0
CoDEmanX
committed
self.su.set_modes(
interpolation=tfm_opts.snap_interpolate_normals_mode,
use_relative_coords=tfm_opts.use_relative_coords,
editmode=tool_settings.use_snap_self,
snap_type=snap_type,
snap_align=tool_settings.use_snap_align_rotation,
axes_coords=axes_coords,
)
CoDEmanX
committed
self.do_raycast = ("MOUSE" in event.type)
self.grid_substep = event.shift
self.modify_surface_orientation = (len(self.particles) == 1)
self.xy = Vector((event.mouse_region_x, event.mouse_region_y))
CoDEmanX
committed
self.use_object_centers = v3d.use_pivot_point_align
CoDEmanX
committed
if event.type == 'MOUSEMOVE':
self.update_transform_mousemove()
CoDEmanX
committed
if self.transform_mode == 'MOVE':
transform_func = self.transform_move
elif self.transform_mode == 'ROTATE':
transform_func = self.transform_rotate
elif self.transform_mode == 'SCALE':
transform_func = self.transform_scale
CoDEmanX
committed
for particle in self.particles:
transform_func(particle)
CoDEmanX
committed
if make_snapshot:
self.make_normal_snapshot(context.scene, tangential_snapshot)
CoDEmanX
committed
CoDEmanX
committed
def update_origin_projection(self, context):
r = context.region
rv3d = context.region_data
CoDEmanX
committed
origin = self.csu.get_origin()
# prehaps not projection, but intersection with plane?
self.origin_xy = location_3d_to_region_2d(r, rv3d, origin)
if self.origin_xy is None:
self.origin_xy = Vector((r.width / 2, r.height / 2))
CoDEmanX
committed
self.delta_xy = (self.start_xy - self.origin_xy).to_3d()
self.prev_delta_xy = self.delta_xy
CoDEmanX
committed
def init_xy_angle_distance(self, context, event):
self.start_xy = Vector((event.mouse_region_x, event.mouse_region_y))
CoDEmanX
committed
CoDEmanX
committed
# Distinction between angles has to be made because
# angles can go beyond 360 degrees (we cannot snap
# to increment the original ones).
self.raw_angles = [0.0, 0.0, 0.0]
self.angles = [0.0, 0.0, 0.0]
self.scales = [1.0, 1.0, 1.0]
CoDEmanX
committed
def update_transform_mousemove(self):
delta_xy = (self.xy - self.origin_xy).to_3d()
CoDEmanX
committed
n_axes = sum(int(v) for v in self.allowed_axes)
if n_axes == 1:
# rotate using angle as value
rd = self.prev_delta_xy.rotation_difference(delta_xy)
offset = -rd.angle * round(rd.axis[2])
CoDEmanX
committed
CoDEmanX
committed
i_allowed = 0
for i in range(3):
if self.allowed_axes[i]:
i_allowed = i
CoDEmanX
committed
view_dir = self.vu.get_direction()
if view_dir.dot(sys_matrix[i_allowed][:3]) < 0:
offset = -offset
CoDEmanX
committed
for i in range(3):
if self.allowed_axes[i]:
self.raw_angles[i] += offset
elif n_axes == 2:
# rotate using XY coords as two values
offset = (delta_xy - self.prev_delta_xy) * (math.pi / 180.0)
CoDEmanX
committed
if self.grid_substep:
offset *= 0.1
else:
offset *= 0.5
CoDEmanX
committed
j = 0
for i in range(3):
if self.allowed_axes[i]:
self.raw_angles[i] += offset[1 - j]
j += 1
elif n_axes == 3:
# rotate around view direction
rd = self.prev_delta_xy.rotation_difference(delta_xy)
offset = -rd.angle * round(rd.axis[2])
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
try:
view_dir = sys_matrix.inverted().to_3x3() * view_dir
except:
# this is some degenerate system
pass
CoDEmanX
committed
CoDEmanX
committed
matrix = Euler(self.raw_angles, 'XYZ').to_matrix()
matrix.rotate(rot)
CoDEmanX
committed
euler = matrix.to_euler('XYZ')
self.raw_angles[0] += clamp_angle(euler.x - self.raw_angles[0])
self.raw_angles[1] += clamp_angle(euler.y - self.raw_angles[1])
self.raw_angles[2] += clamp_angle(euler.z - self.raw_angles[2])
CoDEmanX
committed
scale = delta_xy.length / self.delta_xy.length
if self.delta_xy.dot(delta_xy) < 0:
scale *= -1
for i in range(3):
if self.allowed_axes[i]:
self.scales[i] = scale
CoDEmanX
committed
CoDEmanX
committed
global set_cursor_location__reset_stick
CoDEmanX
committed
src_matrix = particle.get_matrix()
initial_matrix = particle.get_initial_matrix()
CoDEmanX
committed
matrix = self.su.snap(
self.xy, src_matrix, initial_matrix,
self.do_raycast, self.grid_substep,
self.vu, self.csu,
self.modify_surface_orientation,
self.use_object_centers)
CoDEmanX
committed
set_cursor_location__reset_stick = False
set_cursor_location__reset_stick = True
CoDEmanX
committed
def rotate_matrix(self, matrix):
sys_matrix = self.csu.get_matrix()
CoDEmanX
committed
try:
matrix = sys_matrix.inverted() * matrix
except:
# this is some degenerate system
pass
CoDEmanX
committed
# Blender's order of rotation [in local axes]
rotation_order = [2, 1, 0]
CoDEmanX
committed
# Seems that 4x4 matrix cannot be rotated using rotate() ?
sys_matrix3 = sys_matrix.to_3x3()
CoDEmanX
committed
for i in range(3):
j = rotation_order[i]
axis = sys_matrix3[j]
angle = self.angles[j]
CoDEmanX
committed
rot = angle_axis_to_quat(angle, axis)
# this seems to be buggy too
#rot = Matrix.Rotation(angle, 3, axis)
CoDEmanX
committed
sys_matrix3 = rot.to_matrix() * sys_matrix3
# sys_matrix3.rotate has a bug? or I don't understand how it works?
#sys_matrix3.rotate(rot)
CoDEmanX
committed
for i in range(3):
sys_matrix[i][:3] = sys_matrix3[i]
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
def transform_rotate(self, particle):
grid_step = self.angle_grid_steps[self.grid_substep]
grid_step *= (math.pi / 180.0)
CoDEmanX
committed
for i in range(3):
if self.axes_values[i] and self.axes_eval_success[i]:
self.raw_angles[i] = self.axes_coords[i] * (math.pi / 180.0)
CoDEmanX
committed
CoDEmanX
committed
if self.su.implementation.snap_type == 'INCREMENT':
for i in range(3):
self.angles[i] = round_step(self.angles[i], grid_step)
CoDEmanX
committed
initial_matrix = particle.get_initial_matrix()
matrix = self.rotate_matrix(initial_matrix)
CoDEmanX
committed
CoDEmanX
committed
def scale_matrix(self, matrix):
sys_matrix = self.csu.get_matrix()
CoDEmanX
committed
try:
matrix = sys_matrix.inverted() * matrix
except:
# this is some degenerate system
pass
CoDEmanX
committed
for i in range(3):
sys_matrix[i] *= self.scales[i]
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
def transform_scale(self, particle):
grid_step = self.scale_grid_steps[self.grid_substep]
CoDEmanX
committed
for i in range(3):
if self.axes_values[i] and self.axes_eval_success[i]:
self.scales[i] = self.axes_coords[i]
CoDEmanX
committed
if self.su.implementation.snap_type == 'INCREMENT':
for i in range(3):
self.scales[i] = round_step(self.scales[i], grid_step)
CoDEmanX
committed
initial_matrix = particle.get_initial_matrix()
matrix = self.scale_matrix(initial_matrix)
CoDEmanX
committed
CoDEmanX
committed
def set_axis_input(self, axis_id, axis_val):
if axis_val == self.axes_values[axis_id]:
return
CoDEmanX
committed
CoDEmanX
committed
if len(axis_val) == 0:
self.axes_coords[axis_id] = None
self.axes_eval_success[axis_id] = True
else:
try:
#self.axes_coords[axis_id] = float(eval(axis_val, {}, {}))
self.axes_coords[axis_id] = \
float(eval(axis_val, math.__dict__))
self.axes_eval_success[axis_id] = True
except:
self.axes_eval_success[axis_id] = False
CoDEmanX
committed
def snap_to_system_origin(self, force_pivot=False):
if self.transform_mode == 'MOVE':
pivot = self.csu.get_pivot_name(raw=force_pivot)
p = self.csu.get_origin(relative=False, pivot=pivot)
m = self.csu.get_matrix()
try:
p = m.inverted() * p
except:
# this is some degenerate system
pass
for i in range(3):
self.set_axis_input(i, str(p[i]))
elif self.transform_mode == 'ROTATE':
for i in range(3):
self.set_axis_input(i, "0")
elif self.transform_mode == 'SCALE':
for i in range(3):
self.set_axis_input(i, "1")
CoDEmanX
committed
def get_axes_values(self, as_string=False):
if self.transform_mode == 'MOVE':
localmat = CursorDynamicSettings.local_matrix
raw_axes = localmat.translation
elif self.transform_mode == 'ROTATE':
raw_axes = Vector(self.angles) * (180.0 / math.pi)
elif self.transform_mode == 'SCALE':
raw_axes = Vector(self.scales)
CoDEmanX
committed
axes_values = []
for i in range(3):
if as_string and self.axes_values[i]:
value = self.axes_values[i]
elif self.axes_eval_success[i] and \
(self.axes_coords[i] is not None):
value = self.axes_coords[i]
else:
value = raw_axes[i]
if as_string:
value = self.coord_format.format(value)
axes_values.append(value)
CoDEmanX
committed
CoDEmanX
committed
def get_axes_text(self, offset=False):
axes_values = self.get_axes_values(as_string=True)
CoDEmanX
committed
axes_text = []
for i in range(3):
j = i
if offset:
j = (i + self.current_axis) % 3
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
def set_axes_text(self, text, offset=False):
if "\n" in text:
text = text.replace("\r", "")
else:
text = text.replace("\r", "\n")
text = text.replace("\n", "\t")
#text = text.replace(",", ".") # ???
CoDEmanX
committed
axes_text = text.split("\t")
for i in range(min(len(axes_text), 3)):
j = i
if offset:
j = (i + self.current_axis) % 3
self.set_axis_input(j, axes_text[i])
CoDEmanX
committed
def process_axis_input(self, event):
axis_id = self.current_axis
axis_val = self.axes_values[axis_id]
CoDEmanX
committed
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
if event.type in self.key_map["remove_next_character"]:
if event.ctrl:
# clear all
for i in range(3):
self.set_axis_input(i, "")
self.caret_pos = 0
return
else:
axis_val = axis_val[0:self.caret_pos] + \
axis_val[self.caret_pos + 1:len(axis_val)]
elif event.type in self.key_map["remove_last_character"]:
if event.ctrl:
# clear current
axis_val = ""
else:
axis_val = axis_val[0:self.caret_pos - 1] + \
axis_val[self.caret_pos:len(axis_val)]
self.caret_pos -= 1
elif event.type in self.key_map["move_caret_next"]:
self.caret_pos += 1
if event.ctrl:
snap_chars = ".-+*/%()"
i = self.caret_pos
while axis_val[i:i + 1] not in snap_chars:
i += 1
self.caret_pos = i
elif event.type in self.key_map["move_caret_prev"]:
self.caret_pos -= 1
if event.ctrl:
snap_chars = ".-+*/%()"
i = self.caret_pos
while axis_val[i - 1:i] not in snap_chars:
i -= 1
self.caret_pos = i
elif event.type in self.key_map["move_caret_home"]:
self.caret_pos = 0
elif event.type in self.key_map["move_caret_end"]:
self.caret_pos = len(axis_val)
elif event.type in self.key_char_map:
# Currently accessing event.ascii seems to crash Blender
c = self.key_char_map[event.type]
if event.shift:
if c == "8":
c = "*"
elif c == "5":
c = "%"
elif c == "9":
c = "("
elif c == "0":
c = ")"
axis_val = axis_val[0:self.caret_pos] + c + \
axis_val[self.caret_pos:len(axis_val)]
self.caret_pos += 1
CoDEmanX
committed
self.caret_pos = min(max(self.caret_pos, 0), len(axis_val))
CoDEmanX
committed
CoDEmanX
committed
# ====== DRAWING ====== #
def gizmo_distance(self, pos):
rv3d = self.vu.region_data
if rv3d.view_perspective == 'ORTHO':
dist = rv3d.view_distance
else:
view_pos = self.vu.get_viewpoint()
view_dir = self.vu.get_direction()
dist = (pos - view_pos).dot(view_dir)
return dist
CoDEmanX
committed
def gizmo_scale(self, pos):
return self.gizmo_distance(pos) * self.gizmo_factor
CoDEmanX
committed
def check_v3d_local(self, context):
csu_v3d = self.csu.space_data
v3d = context.space_data
if csu_v3d.local_view:
return csu_v3d != v3d
return v3d.local_view
CoDEmanX
committed
def draw_3d(self, context):
if self.check_v3d_local(context):
return
CoDEmanX
committed
if time.time() < (self.click_start + self.click_period):
return
CoDEmanX
committed
settings = find_settings()
tfm_opts = settings.transform_options
CoDEmanX
committed
initial_matrix = self.particles[0].get_initial_matrix()
CoDEmanX
committed
sys_matrix = self.csu.get_matrix()
if tfm_opts.use_relative_coords:
sys_matrix.translation = initial_matrix.translation.copy()
sys_origin = sys_matrix.to_translation()
dest_point = self.particles[0].get_location()
CoDEmanX
committed
if self.is_normal_visible():
p0, x, y, z, _x, _z = \
self.get_normal_params(tfm_opts, dest_point)
CoDEmanX
committed
# use theme colors?
#ThemeView3D.normal
#ThemeView3D.vertex_normal