Node Wrangler: Add more specific poll methods
[blender-addons.git] / mesh_snap_utilities_line / common_utilities.py
bloba84310b07b7c65280ec98f34798933d58b495dff
1 # SPDX-FileCopyrightText: 2018-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # python tip: from-imports don't save memory.
6 # They execute and cache the entire module just like a regular import.
8 import bpy
9 import bmesh
11 from mathutils import Vector
12 from mathutils.geometry import (
13 intersect_point_line,
14 intersect_line_line,
15 intersect_ray_tri,
18 from .snap_context_l import SnapContext
21 def get_units_info(scale, unit_system, separate_units):
22 if unit_system == 'METRIC':
23 scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
24 (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
25 elif unit_system == 'IMPERIAL':
26 scale_steps = ((5280, 'mi'), (1, '\''),
27 (1 / 12, '"'), (1 / 12000, 'thou'))
28 scale /= 0.3048 # BU to feet
29 else:
30 scale_steps = ((1, ' BU'),)
31 separate_units = False
33 return (scale, scale_steps, separate_units)
36 def convert_distance(val, units_info, precision=5):
37 scale, scale_steps, separate_units = units_info
38 sval = val * scale
39 idx = 0
40 while idx < len(scale_steps) - 1:
41 if sval >= scale_steps[idx][0]:
42 break
43 idx += 1
44 factor, suffix = scale_steps[idx]
45 sval /= factor
46 if not separate_units or idx == len(scale_steps) - 1:
47 dval = str(round(sval, precision)) + suffix
48 else:
49 ival = int(sval)
50 dval = str(round(ival, precision)) + suffix
51 fval = sval - ival
52 idx += 1
53 while idx < len(scale_steps):
54 fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
55 if fval >= 1:
56 dval += ' ' \
57 + ("%.1f" % fval) \
58 + scale_steps[idx][1]
59 break
60 idx += 1
62 return dval
65 def location_3d_to_region_2d(region, rv3d, coord):
66 prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0))
67 width_half = region.width / 2.0
68 height_half = region.height / 2.0
69 return Vector((width_half + width_half * (prj.x / prj.w),
70 height_half + height_half * (prj.y / prj.w)
74 def out_Location(rv3d, orig, vector):
75 view_matrix = rv3d.view_matrix
76 v1 = (int(view_matrix[0][0] * 1.5), int(view_matrix[0][1] * 1.5), int(view_matrix[0][2] * 1.5))
77 v2 = (int(view_matrix[1][0] * 1.5), int(view_matrix[1][1] * 1.5), int(view_matrix[1][2] * 1.5))
79 hit = intersect_ray_tri((1, 0, 0), (0, 1, 0),
80 (0, 0, 0), (vector), (orig), False)
81 if hit is None:
82 hit = intersect_ray_tri(v1, v2, (0, 0, 0), (vector), (orig), False)
83 if hit is None:
84 hit = intersect_ray_tri(v1, v2, (0, 0, 0), (-vector), (orig), False)
85 if hit is None:
86 hit = Vector()
87 return hit
90 def get_snap_bm_geom(sctx, main_snap_obj, mcursor):
92 r_snp_obj, r_loc, r_elem, r_elem_co = sctx.snap_get(mcursor, main_snap_obj)
93 r_view_vector, r_orig = sctx.last_ray
94 r_bm = None
95 r_bm_geom = None
97 if r_snp_obj is not None:
98 obj = r_snp_obj.data[0]
100 if obj.type == 'MESH' and obj.data.is_editmode:
101 r_bm = bmesh.from_edit_mesh(obj.data)
102 if len(r_elem) == 1:
103 r_bm_geom = r_bm.verts[r_elem[0]]
105 elif len(r_elem) == 2:
106 try:
107 v1 = r_bm.verts[r_elem[0]]
108 v2 = r_bm.verts[r_elem[1]]
109 r_bm_geom = r_bm.edges.get([v1, v2])
110 except IndexError:
111 r_bm.verts.ensure_lookup_table()
113 elif len(r_elem) == 3:
114 tri = [
115 r_bm.verts[r_elem[0]],
116 r_bm.verts[r_elem[1]],
117 r_bm.verts[r_elem[2]],
120 faces = set(tri[0].link_faces).intersection(
121 tri[1].link_faces, tri[2].link_faces)
122 if len(faces) == 1:
123 r_bm_geom = faces.pop()
124 else:
125 i = -2
126 edge = None
127 while not edge and i != 1:
128 edge = r_bm.edges.get([tri[i], tri[i + 1]])
129 i += 1
130 if edge:
131 for l in edge.link_loops:
132 if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]:
133 r_bm_geom = l.face
134 break
135 if r_loc is None:
136 r_loc = r_elem_co[0]
138 return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom
141 class SnapCache(object):
142 __slots__ = 'edge', 'face'
144 class Edge:
145 __slots__ = 'snp_obj', 'elem', 'vmid', 'vperp', 'v2dmid', 'v2dperp', 'is_increment'
147 def __init__(self):
148 self.snp_obj = None
149 self.elem = None
150 self.vmid = None
151 self.vperp = None
152 self.v2dmid = None
153 self.v2dperp = None
154 self.is_increment = False
156 class Face:
157 __slots__ = 'bm_face', 'vmid', 'v2dmid'
159 def __init__(self):
160 self.bm_face = None
162 def __init__(self):
163 self.edge = self.Edge()
164 self.face = self.Face()
166 def clear(self):
167 self.edge.snp_obj = self.face.bm_face = None
170 _snap_cache = SnapCache()
173 def snap_utilities(
174 sctx, main_snap_obj,
175 mcursor,
176 constrain=None,
177 previous_vert=None,
178 increment=0.0):
180 snp_obj, loc, elem, elem_co, view_vector, orig, bm, bm_geom = get_snap_bm_geom(
181 sctx, main_snap_obj, mcursor)
183 is_increment = False
184 r_loc = None
185 r_type = 'OUT'
186 r_len = 0.0
188 if not snp_obj:
189 is_increment = True
190 if constrain:
191 end = orig + view_vector
192 t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
193 if t_loc is None:
194 t_loc = constrain
195 r_loc = t_loc[0]
196 else:
197 r_loc = out_Location(sctx.rv3d, orig, view_vector)
199 elif len(elem) == 1:
200 r_type = 'VERT'
202 if constrain:
203 r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
204 else:
205 r_loc = loc
207 elif len(elem) == 2:
208 r_type = 'EDGE'
210 if _snap_cache.edge.snp_obj is not snp_obj or not (elem == _snap_cache.edge.elem).all():
211 _snap_cache.edge.snp_obj = snp_obj
212 _snap_cache.edge.elem = elem
214 v0 = elem_co[0]
215 v1 = elem_co[1]
216 _snap_cache.edge.vmid = 0.5 * (v0 + v1)
217 _snap_cache.edge.v2dmid = location_3d_to_region_2d(
218 sctx.region, sctx.rv3d, _snap_cache.edge.vmid)
220 if previous_vert and (not bm_geom or previous_vert not in bm_geom.verts):
221 pvert_co = main_snap_obj.mat @ previous_vert.co
222 perp_point = intersect_point_line(pvert_co, v0, v1)
223 _snap_cache.edge.vperp = perp_point[0]
224 # factor = point_perpendicular[1]
225 _snap_cache.edge.v2dperp = location_3d_to_region_2d(
226 sctx.region, sctx.rv3d, perp_point[0])
227 _snap_cache.edge.is_increment = False
228 else:
229 _snap_cache.edge.is_increment = True
231 # else: _snap_cache.edge.v2dperp = None
233 if constrain:
234 t_loc = intersect_line_line(
235 constrain[0], constrain[1], elem_co[0], elem_co[1])
236 if t_loc is None:
237 is_increment = True
238 end = orig + view_vector
239 t_loc = intersect_line_line(
240 constrain[0], constrain[1], orig, end)
241 r_loc = t_loc[0]
243 elif _snap_cache.edge.v2dperp and\
244 abs(_snap_cache.edge.v2dperp[0] - mcursor[0]) < sctx._dist_px and abs(_snap_cache.edge.v2dperp[1] - mcursor[1]) < sctx._dist_px:
245 r_type = 'PERPENDICULAR'
246 r_loc = _snap_cache.edge.vperp
248 elif abs(_snap_cache.edge.v2dmid[0] - mcursor[0]) < sctx._dist_px and abs(_snap_cache.edge.v2dmid[1] - mcursor[1]) < sctx._dist_px:
249 r_type = 'CENTER'
250 r_loc = _snap_cache.edge.vmid
252 else:
253 r_loc = loc
254 is_increment = _snap_cache.edge.is_increment
256 elif len(elem) == 3:
257 r_type = 'FACE'
259 # vmid = v2dmid = None
260 # if bm_geom and _snap_cache.face is not bm_geom:
261 # _snap_cache.face.bm_face = bm_geom
262 # vmid = _snap_cache.face.vmid = bm_geom.calc_center_median()
263 # v2dmid = _snap_cache.face.v2dmid = location_3d_to_region_2d(
264 # sctx.region, sctx.rv3d, _snap_cache.face.vmid)
266 if constrain:
267 is_increment = False
268 # elem_world_co = [snp_obj.mat @ co for co in elem_co]
269 # ray_dir = constrain[1] - constrain[0]
270 # r_loc = intersect_ray_tri(*elem_world_co, ray_dir, constrain[0], False)
271 # if r_loc is None:
272 # r_loc = intersect_ray_tri(*elem_world_co, -ray_dir, constrain[0], False)
273 # if r_loc is None:
274 r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
276 # elif v2dmid and abs(v2dmid[0] - mcursor[0]) < 10 and abs(v2dmid[1] - mcursor[1]) < 10:
277 # r_type = 'CENTER'
278 # r_loc = vmid
280 else:
281 r_loc = loc
282 is_increment = True
284 if previous_vert:
285 pv_co = main_snap_obj.mat @ previous_vert.co
286 vec = r_loc - pv_co
287 if is_increment and increment:
288 r_len = round((1 / increment) * vec.length) * increment
289 r_loc = r_len * vec.normalized() + pv_co
290 else:
291 r_len = vec.length
293 return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len
296 snap_utilities.cache = _snap_cache