mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-20 13:22:58 +00:00
302 lines
10 KiB
Python
302 lines
10 KiB
Python
### 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 #####
|
|
|
|
#python tip: from-imports don't save memory.
|
|
#They execute and cache the entire module just like a regular import.
|
|
|
|
import bpy
|
|
import bmesh
|
|
|
|
from mathutils import Vector
|
|
from mathutils.geometry import (
|
|
intersect_point_line,
|
|
intersect_line_line,
|
|
intersect_ray_tri,
|
|
)
|
|
|
|
from .snap_context_l import SnapContext
|
|
|
|
|
|
def get_units_info(scale, unit_system, separate_units):
|
|
if unit_system == 'METRIC':
|
|
scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
|
|
(1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
|
|
elif unit_system == 'IMPERIAL':
|
|
scale_steps = ((5280, 'mi'), (1, '\''),
|
|
(1 / 12, '"'), (1 / 12000, 'thou'))
|
|
scale /= 0.3048 # BU to feet
|
|
else:
|
|
scale_steps = ((1, ' BU'),)
|
|
separate_units = False
|
|
|
|
return (scale, scale_steps, separate_units)
|
|
|
|
|
|
def convert_distance(val, units_info, precision=5):
|
|
scale, scale_steps, separate_units = units_info
|
|
sval = val * scale
|
|
idx = 0
|
|
while idx < len(scale_steps) - 1:
|
|
if sval >= scale_steps[idx][0]:
|
|
break
|
|
idx += 1
|
|
factor, suffix = scale_steps[idx]
|
|
sval /= factor
|
|
if not separate_units or idx == len(scale_steps) - 1:
|
|
dval = str(round(sval, precision)) + suffix
|
|
else:
|
|
ival = int(sval)
|
|
dval = str(round(ival, precision)) + suffix
|
|
fval = sval - ival
|
|
idx += 1
|
|
while idx < len(scale_steps):
|
|
fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
|
|
if fval >= 1:
|
|
dval += ' ' \
|
|
+ ("%.1f" % fval) \
|
|
+ scale_steps[idx][1]
|
|
break
|
|
idx += 1
|
|
|
|
return dval
|
|
|
|
|
|
def location_3d_to_region_2d(region, rv3d, coord):
|
|
prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0))
|
|
width_half = region.width / 2.0
|
|
height_half = region.height / 2.0
|
|
return Vector((width_half + width_half * (prj.x / prj.w),
|
|
height_half + height_half * (prj.y / prj.w)
|
|
))
|
|
|
|
|
|
def out_Location(rv3d, orig, vector):
|
|
view_matrix = rv3d.view_matrix
|
|
v1 = (int(view_matrix[0][0]*1.5), int(view_matrix[0][1]*1.5), int(view_matrix[0][2]*1.5))
|
|
v2 = (int(view_matrix[1][0]*1.5), int(view_matrix[1][1]*1.5), int(view_matrix[1][2]*1.5))
|
|
|
|
hit = intersect_ray_tri((1,0,0), (0,1,0), (0,0,0), (vector), (orig), False)
|
|
if hit is None:
|
|
hit = intersect_ray_tri(v1, v2, (0,0,0), (vector), (orig), False)
|
|
if hit is None:
|
|
hit = intersect_ray_tri(v1, v2, (0,0,0), (-vector), (orig), False)
|
|
if hit is None:
|
|
hit = Vector()
|
|
return hit
|
|
|
|
|
|
def get_snap_bm_geom(sctx, main_snap_obj, mcursor):
|
|
|
|
r_snp_obj, r_loc, r_elem, r_elem_co = sctx.snap_get(mcursor, main_snap_obj)
|
|
r_view_vector, r_orig = sctx.last_ray
|
|
r_bm = None
|
|
r_bm_geom = None
|
|
|
|
if r_snp_obj is not None:
|
|
obj = r_snp_obj.data[0]
|
|
|
|
if obj.type == 'MESH' and obj.data.is_editmode:
|
|
r_bm = bmesh.from_edit_mesh(obj.data)
|
|
if len(r_elem) == 1:
|
|
r_bm_geom = r_bm.verts[r_elem[0]]
|
|
|
|
elif len(r_elem) == 2:
|
|
try:
|
|
v1 = r_bm.verts[r_elem[0]]
|
|
v2 = r_bm.verts[r_elem[1]]
|
|
r_bm_geom = r_bm.edges.get([v1, v2])
|
|
except IndexError:
|
|
r_bm.verts.ensure_lookup_table()
|
|
|
|
elif len(r_elem) == 3:
|
|
tri = [
|
|
r_bm.verts[r_elem[0]],
|
|
r_bm.verts[r_elem[1]],
|
|
r_bm.verts[r_elem[2]],
|
|
]
|
|
|
|
faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces)
|
|
if len(faces) == 1:
|
|
r_bm_geom = faces.pop()
|
|
else:
|
|
i = -2
|
|
edge = None
|
|
while not edge and i != 1:
|
|
edge = r_bm.edges.get([tri[i], tri[i + 1]])
|
|
i += 1
|
|
if edge:
|
|
for l in edge.link_loops:
|
|
if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]:
|
|
r_bm_geom = l.face
|
|
break
|
|
if r_loc is None:
|
|
r_loc = r_elem_co[0]
|
|
|
|
return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom
|
|
|
|
|
|
class SnapCache(object):
|
|
__slots__ = 'edge', 'face'
|
|
|
|
class Edge:
|
|
__slots__ = 'snp_obj', 'elem', 'vmid', 'vperp', 'v2dmid', 'v2dperp', 'is_increment'
|
|
|
|
def __init__(self):
|
|
self.snp_obj = None
|
|
self.elem = None
|
|
self.vmid = None
|
|
self.vperp = None
|
|
self.v2dmid = None
|
|
self.v2dperp = None
|
|
self.is_increment = False
|
|
|
|
class Face:
|
|
__slots__ = 'bm_face', 'vmid', 'v2dmid'
|
|
|
|
def __init__(self):
|
|
self.bm_face = None
|
|
|
|
def __init__(self):
|
|
self.edge = self.Edge()
|
|
self.face = self.Face()
|
|
|
|
def clear(self):
|
|
self.edge.snp_obj = self.face.bm_face = None
|
|
|
|
_snap_cache = SnapCache()
|
|
|
|
|
|
def snap_utilities(
|
|
sctx, main_snap_obj,
|
|
mcursor,
|
|
constrain = None,
|
|
previous_vert = None,
|
|
increment = 0.0):
|
|
|
|
snp_obj, loc, elem, elem_co, view_vector, orig, bm, bm_geom = get_snap_bm_geom(sctx, main_snap_obj, mcursor)
|
|
|
|
is_increment = False
|
|
r_loc = None
|
|
r_type = 'OUT'
|
|
r_len = 0.0
|
|
|
|
if not snp_obj:
|
|
is_increment = True
|
|
if constrain:
|
|
end = orig + view_vector
|
|
t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
|
|
if t_loc is None:
|
|
t_loc = constrain
|
|
r_loc = t_loc[0]
|
|
else:
|
|
r_loc = out_Location(sctx.rv3d, orig, view_vector)
|
|
|
|
elif len(elem) == 1:
|
|
r_type = 'VERT'
|
|
|
|
if constrain:
|
|
r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
|
|
else:
|
|
r_loc = loc
|
|
|
|
elif len(elem) == 2:
|
|
r_type = 'EDGE'
|
|
|
|
if _snap_cache.edge.snp_obj is not snp_obj or not (elem == _snap_cache.edge.elem).all():
|
|
_snap_cache.edge.snp_obj = snp_obj
|
|
_snap_cache.edge.elem = elem
|
|
|
|
v0 = elem_co[0]
|
|
v1 = elem_co[1]
|
|
_snap_cache.edge.vmid = 0.5 * (v0 + v1)
|
|
_snap_cache.edge.v2dmid = location_3d_to_region_2d(
|
|
sctx.region, sctx.rv3d, _snap_cache.edge.vmid)
|
|
|
|
if previous_vert and (not bm_geom or previous_vert not in bm_geom.verts):
|
|
pvert_co = main_snap_obj.mat @ previous_vert.co
|
|
perp_point = intersect_point_line(pvert_co, v0, v1)
|
|
_snap_cache.edge.vperp = perp_point[0]
|
|
#factor = point_perpendicular[1]
|
|
_snap_cache.edge.v2dperp = location_3d_to_region_2d(sctx.region, sctx.rv3d, perp_point[0])
|
|
_snap_cache.edge.is_increment = False
|
|
else:
|
|
_snap_cache.edge.is_increment = True
|
|
|
|
#else: _snap_cache.edge.v2dperp = None
|
|
|
|
if constrain:
|
|
t_loc = intersect_line_line(constrain[0], constrain[1], elem_co[0], elem_co[1])
|
|
if t_loc is None:
|
|
is_increment = True
|
|
end = orig + view_vector
|
|
t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
|
|
r_loc = t_loc[0]
|
|
|
|
elif _snap_cache.edge.v2dperp and\
|
|
abs(_snap_cache.edge.v2dperp[0] - mcursor[0]) < 10 and abs(_snap_cache.edge.v2dperp[1] - mcursor[1]) < 10:
|
|
r_type = 'PERPENDICULAR'
|
|
r_loc = _snap_cache.edge.vperp
|
|
|
|
elif abs(_snap_cache.edge.v2dmid[0] - mcursor[0]) < 10 and abs(_snap_cache.edge.v2dmid[1] - mcursor[1]) < 10:
|
|
r_type = 'CENTER'
|
|
r_loc = _snap_cache.edge.vmid
|
|
|
|
else:
|
|
r_loc = loc
|
|
is_increment = _snap_cache.edge.is_increment
|
|
|
|
elif len(elem) == 3:
|
|
r_type = 'FACE'
|
|
|
|
# vmid = v2dmid = None
|
|
# if bm_geom and _snap_cache.face is not bm_geom:
|
|
# _snap_cache.face.bm_face = bm_geom
|
|
# vmid = _snap_cache.face.vmid = bm_geom.calc_center_median()
|
|
# v2dmid = _snap_cache.face.v2dmid = location_3d_to_region_2d(
|
|
# sctx.region, sctx.rv3d, _snap_cache.face.vmid)
|
|
|
|
if constrain:
|
|
is_increment = False
|
|
# elem_world_co = [snp_obj.mat @ co for co in elem_co]
|
|
# ray_dir = constrain[1] - constrain[0]
|
|
# r_loc = intersect_ray_tri(*elem_world_co, ray_dir, constrain[0], False)
|
|
# if r_loc is None:
|
|
# r_loc = intersect_ray_tri(*elem_world_co, -ray_dir, constrain[0], False)
|
|
# if r_loc is None:
|
|
r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
|
|
|
|
# elif v2dmid and abs(v2dmid[0] - mcursor[0]) < 10 and abs(v2dmid[1] - mcursor[1]) < 10:
|
|
# r_type = 'CENTER'
|
|
# r_loc = vmid
|
|
|
|
else:
|
|
r_loc = loc
|
|
is_increment = True
|
|
|
|
if previous_vert:
|
|
pv_co = main_snap_obj.mat @ previous_vert.co
|
|
vec = r_loc - pv_co
|
|
if is_increment and increment:
|
|
r_len = round((1 / increment) * vec.length) * increment
|
|
r_loc = r_len * vec.normalized() + pv_co
|
|
else:
|
|
r_len = vec.length
|
|
|
|
return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len
|
|
|
|
snap_utilities.cache = _snap_cache
|