-
Germano Cavalcante authoredGermano Cavalcante authored
common_utilities.py 9.53 KiB
# SPDX-License-Identifier: GPL-2.0-or-later
# 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]) < sctx._dist_px and abs(_snap_cache.edge.v2dperp[1] - mcursor[1]) < sctx._dist_px:
r_type = 'PERPENDICULAR'
r_loc = _snap_cache.edge.vperp
elif abs(_snap_cache.edge.v2dmid[0] - mcursor[0]) < sctx._dist_px and abs(_snap_cache.edge.v2dmid[1] - mcursor[1]) < sctx._dist_px:
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