Cleanup: trailing space
[blender-addons.git] / mesh_snap_utilities_line / common_utilities.py
blob9c63c3f2e12596ff6806c04add3593d1b69b6ff6
1 ### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 3
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 # ##### END GPL LICENSE BLOCK #####
18 #python tip: from-imports don't save memory.
19 #They execute and cache the entire module just like a regular import.
21 import bpy
22 import bmesh
24 from mathutils import Vector
25 from mathutils.geometry import (
26 intersect_point_line,
27 intersect_line_line,
28 intersect_ray_tri,
31 from .snap_context_l import SnapContext
34 def get_units_info(scale, unit_system, separate_units):
35 if unit_system == 'METRIC':
36 scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
37 (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
38 elif unit_system == 'IMPERIAL':
39 scale_steps = ((5280, 'mi'), (1, '\''),
40 (1 / 12, '"'), (1 / 12000, 'thou'))
41 scale /= 0.3048 # BU to feet
42 else:
43 scale_steps = ((1, ' BU'),)
44 separate_units = False
46 return (scale, scale_steps, separate_units)
49 def convert_distance(val, units_info, precision=5):
50 scale, scale_steps, separate_units = units_info
51 sval = val * scale
52 idx = 0
53 while idx < len(scale_steps) - 1:
54 if sval >= scale_steps[idx][0]:
55 break
56 idx += 1
57 factor, suffix = scale_steps[idx]
58 sval /= factor
59 if not separate_units or idx == len(scale_steps) - 1:
60 dval = str(round(sval, precision)) + suffix
61 else:
62 ival = int(sval)
63 dval = str(round(ival, precision)) + suffix
64 fval = sval - ival
65 idx += 1
66 while idx < len(scale_steps):
67 fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
68 if fval >= 1:
69 dval += ' ' \
70 + ("%.1f" % fval) \
71 + scale_steps[idx][1]
72 break
73 idx += 1
75 return dval
78 def location_3d_to_region_2d(region, rv3d, coord):
79 prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0))
80 width_half = region.width / 2.0
81 height_half = region.height / 2.0
82 return Vector((width_half + width_half * (prj.x / prj.w),
83 height_half + height_half * (prj.y / prj.w)
87 def out_Location(rv3d, orig, vector):
88 view_matrix = rv3d.view_matrix
89 v1 = (int(view_matrix[0][0]*1.5), int(view_matrix[0][1]*1.5), int(view_matrix[0][2]*1.5))
90 v2 = (int(view_matrix[1][0]*1.5), int(view_matrix[1][1]*1.5), int(view_matrix[1][2]*1.5))
92 hit = intersect_ray_tri((1,0,0), (0,1,0), (0,0,0), (vector), (orig), False)
93 if hit is None:
94 hit = intersect_ray_tri(v1, v2, (0,0,0), (vector), (orig), False)
95 if hit is None:
96 hit = intersect_ray_tri(v1, v2, (0,0,0), (-vector), (orig), False)
97 if hit is None:
98 hit = Vector()
99 return hit
102 def get_snap_bm_geom(sctx, main_snap_obj, mcursor):
104 r_snp_obj, r_loc, r_elem, r_elem_co = sctx.snap_get(mcursor, main_snap_obj)
105 r_view_vector, r_orig = sctx.last_ray
106 r_bm = None
107 r_bm_geom = None
109 if r_snp_obj is not None:
110 obj = r_snp_obj.data[0]
112 if obj.type == 'MESH' and obj.data.is_editmode:
113 r_bm = bmesh.from_edit_mesh(obj.data)
114 if len(r_elem) == 1:
115 r_bm_geom = r_bm.verts[r_elem[0]]
117 elif len(r_elem) == 2:
118 try:
119 v1 = r_bm.verts[r_elem[0]]
120 v2 = r_bm.verts[r_elem[1]]
121 r_bm_geom = r_bm.edges.get([v1, v2])
122 except IndexError:
123 r_bm.verts.ensure_lookup_table()
125 elif len(r_elem) == 3:
126 tri = [
127 r_bm.verts[r_elem[0]],
128 r_bm.verts[r_elem[1]],
129 r_bm.verts[r_elem[2]],
132 faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces)
133 if len(faces) == 1:
134 r_bm_geom = faces.pop()
135 else:
136 i = -2
137 edge = None
138 while not edge and i != 1:
139 edge = r_bm.edges.get([tri[i], tri[i + 1]])
140 i += 1
141 if edge:
142 for l in edge.link_loops:
143 if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]:
144 r_bm_geom = l.face
145 break
146 if r_loc is None:
147 r_loc = r_elem_co[0]
149 return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom
152 class SnapCache(object):
153 __slots__ = 'edge', 'face'
155 class Edge:
156 __slots__ = 'snp_obj', 'elem', 'vmid', 'vperp', 'v2dmid', 'v2dperp', 'is_increment'
158 def __init__(self):
159 self.snp_obj = None
160 self.elem = None
161 self.vmid = None
162 self.vperp = None
163 self.v2dmid = None
164 self.v2dperp = None
165 self.is_increment = False
167 class Face:
168 __slots__ = 'bm_face', 'vmid', 'v2dmid'
170 def __init__(self):
171 self.bm_face = None
173 def __init__(self):
174 self.edge = self.Edge()
175 self.face = self.Face()
177 def clear(self):
178 self.edge.snp_obj = self.face.bm_face = None
180 _snap_cache = SnapCache()
183 def snap_utilities(
184 sctx, main_snap_obj,
185 mcursor,
186 constrain = None,
187 previous_vert = None,
188 increment = 0.0):
190 snp_obj, loc, elem, elem_co, view_vector, orig, bm, bm_geom = get_snap_bm_geom(sctx, main_snap_obj, mcursor)
192 is_increment = False
193 r_loc = None
194 r_type = 'OUT'
195 r_len = 0.0
197 if not snp_obj:
198 is_increment = True
199 if constrain:
200 end = orig + view_vector
201 t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
202 if t_loc is None:
203 t_loc = constrain
204 r_loc = t_loc[0]
205 else:
206 r_loc = out_Location(sctx.rv3d, orig, view_vector)
208 elif len(elem) == 1:
209 r_type = 'VERT'
211 if constrain:
212 r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
213 else:
214 r_loc = loc
216 elif len(elem) == 2:
217 r_type = 'EDGE'
219 if _snap_cache.edge.snp_obj is not snp_obj or not (elem == _snap_cache.edge.elem).all():
220 _snap_cache.edge.snp_obj = snp_obj
221 _snap_cache.edge.elem = elem
223 v0 = elem_co[0]
224 v1 = elem_co[1]
225 _snap_cache.edge.vmid = 0.5 * (v0 + v1)
226 _snap_cache.edge.v2dmid = location_3d_to_region_2d(
227 sctx.region, sctx.rv3d, _snap_cache.edge.vmid)
229 if previous_vert and (not bm_geom or previous_vert not in bm_geom.verts):
230 pvert_co = main_snap_obj.mat @ previous_vert.co
231 perp_point = intersect_point_line(pvert_co, v0, v1)
232 _snap_cache.edge.vperp = perp_point[0]
233 #factor = point_perpendicular[1]
234 _snap_cache.edge.v2dperp = location_3d_to_region_2d(sctx.region, sctx.rv3d, perp_point[0])
235 _snap_cache.edge.is_increment = False
236 else:
237 _snap_cache.edge.is_increment = True
239 #else: _snap_cache.edge.v2dperp = None
241 if constrain:
242 t_loc = intersect_line_line(constrain[0], constrain[1], elem_co[0], elem_co[1])
243 if t_loc is None:
244 is_increment = True
245 end = orig + view_vector
246 t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
247 r_loc = t_loc[0]
249 elif _snap_cache.edge.v2dperp and\
250 abs(_snap_cache.edge.v2dperp[0] - mcursor[0]) < 10 and abs(_snap_cache.edge.v2dperp[1] - mcursor[1]) < 10:
251 r_type = 'PERPENDICULAR'
252 r_loc = _snap_cache.edge.vperp
254 elif abs(_snap_cache.edge.v2dmid[0] - mcursor[0]) < 10 and abs(_snap_cache.edge.v2dmid[1] - mcursor[1]) < 10:
255 r_type = 'CENTER'
256 r_loc = _snap_cache.edge.vmid
258 else:
259 r_loc = loc
260 is_increment = _snap_cache.edge.is_increment
262 elif len(elem) == 3:
263 r_type = 'FACE'
265 # vmid = v2dmid = None
266 # if bm_geom and _snap_cache.face is not bm_geom:
267 # _snap_cache.face.bm_face = bm_geom
268 # vmid = _snap_cache.face.vmid = bm_geom.calc_center_median()
269 # v2dmid = _snap_cache.face.v2dmid = location_3d_to_region_2d(
270 # sctx.region, sctx.rv3d, _snap_cache.face.vmid)
272 if constrain:
273 is_increment = False
274 # elem_world_co = [snp_obj.mat @ co for co in elem_co]
275 # ray_dir = constrain[1] - constrain[0]
276 # r_loc = intersect_ray_tri(*elem_world_co, ray_dir, constrain[0], False)
277 # if r_loc is None:
278 # r_loc = intersect_ray_tri(*elem_world_co, -ray_dir, constrain[0], False)
279 # if r_loc is None:
280 r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
282 # elif v2dmid and abs(v2dmid[0] - mcursor[0]) < 10 and abs(v2dmid[1] - mcursor[1]) < 10:
283 # r_type = 'CENTER'
284 # r_loc = vmid
286 else:
287 r_loc = loc
288 is_increment = True
290 if previous_vert:
291 pv_co = main_snap_obj.mat @ previous_vert.co
292 vec = r_loc - pv_co
293 if is_increment and increment:
294 r_len = round((1 / increment) * vec.length) * increment
295 r_loc = r_len * vec.normalized() + pv_co
296 else:
297 r_len = vec.length
299 return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len
301 snap_utilities.cache = _snap_cache