Fix T70724: PLY import fails with strings
[blender-addons.git] / mesh_snap_utilities_line / common_utilities.py
blob5a11a4c4af38817244d3d74d68d70038fc1d3db3
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
147 return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom
150 class SnapCache(object):
151 __slots__ = 'edge', 'face'
153 class Edge:
154 __slots__ = 'snp_obj', 'elem', 'vmid', 'vperp', 'v2dmid', 'v2dperp', 'is_increment'
156 def __init__(self):
157 self.snp_obj = None
158 self.elem = None
159 self.vmid = None
160 self.vperp = None
161 self.v2dmid = None
162 self.v2dperp = None
163 self.is_increment = False
165 class Face:
166 __slots__ = 'bm_face', 'vmid', 'v2dmid'
168 def __init__(self):
169 self.bm_face = None
171 def __init__(self):
172 self.edge = self.Edge()
173 self.face = self.Face()
175 def clear(self):
176 self.edge.snp_obj = self.face.bm_face = None
178 _snap_cache = SnapCache()
181 def snap_utilities(
182 sctx, main_snap_obj,
183 mcursor,
184 constrain = None,
185 previous_vert = None,
186 increment = 0.0):
188 snp_obj, loc, elem, elem_co, view_vector, orig, bm, bm_geom = get_snap_bm_geom(sctx, main_snap_obj, mcursor)
190 is_increment = False
191 r_loc = None
192 r_type = 'OUT'
193 r_len = 0.0
195 if not snp_obj:
196 is_increment = True
197 if constrain:
198 end = orig + view_vector
199 t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
200 if t_loc is None:
201 t_loc = constrain
202 r_loc = t_loc[0]
203 else:
204 r_loc = out_Location(sctx.rv3d, orig, view_vector)
206 elif len(elem) == 1:
207 r_type = 'VERT'
209 if constrain:
210 r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
211 else:
212 r_loc = loc
214 elif len(elem) == 2:
215 r_type = 'EDGE'
217 if _snap_cache.edge.snp_obj is not snp_obj or not (elem == _snap_cache.edge.elem).all():
218 _snap_cache.edge.snp_obj = snp_obj
219 _snap_cache.edge.elem = elem
221 v0 = elem_co[0]
222 v1 = elem_co[1]
223 _snap_cache.edge.vmid = 0.5 * (v0 + v1)
224 _snap_cache.edge.v2dmid = location_3d_to_region_2d(
225 sctx.region, sctx.rv3d, _snap_cache.edge.vmid)
227 if previous_vert and (not bm_geom or previous_vert not in bm_geom.verts):
228 pvert_co = main_snap_obj.mat @ previous_vert.co
229 perp_point = intersect_point_line(pvert_co, v0, v1)
230 _snap_cache.edge.vperp = perp_point[0]
231 #factor = point_perpendicular[1]
232 _snap_cache.edge.v2dperp = location_3d_to_region_2d(sctx.region, sctx.rv3d, perp_point[0])
233 _snap_cache.edge.is_increment = False
234 else:
235 _snap_cache.edge.is_increment = True
237 #else: _snap_cache.edge.v2dperp = None
239 if constrain:
240 t_loc = intersect_line_line(constrain[0], constrain[1], elem_co[0], elem_co[1])
241 if t_loc is None:
242 is_increment = True
243 end = orig + view_vector
244 t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
245 r_loc = t_loc[0]
247 elif _snap_cache.edge.v2dperp and\
248 abs(_snap_cache.edge.v2dperp[0] - mcursor[0]) < 10 and abs(_snap_cache.edge.v2dperp[1] - mcursor[1]) < 10:
249 r_type = 'PERPENDICULAR'
250 r_loc = _snap_cache.edge.vperp
252 elif abs(_snap_cache.edge.v2dmid[0] - mcursor[0]) < 10 and abs(_snap_cache.edge.v2dmid[1] - mcursor[1]) < 10:
253 r_type = 'CENTER'
254 r_loc = _snap_cache.edge.vmid
256 else:
257 r_loc = loc
258 is_increment = _snap_cache.edge.is_increment
260 elif len(elem) == 3:
261 r_type = 'FACE'
263 # vmid = v2dmid = None
264 # if bm_geom and _snap_cache.face is not bm_geom:
265 # _snap_cache.face.bm_face = bm_geom
266 # vmid = _snap_cache.face.vmid = bm_geom.calc_center_median()
267 # v2dmid = _snap_cache.face.v2dmid = location_3d_to_region_2d(
268 # sctx.region, sctx.rv3d, _snap_cache.face.vmid)
270 if constrain:
271 is_increment = False
272 # elem_world_co = [snp_obj.mat @ co for co in elem_co]
273 # ray_dir = constrain[1] - constrain[0]
274 # r_loc = intersect_ray_tri(*elem_world_co, ray_dir, constrain[0], False)
275 # if r_loc is None:
276 # r_loc = intersect_ray_tri(*elem_world_co, -ray_dir, constrain[0], False)
277 # if r_loc is None:
278 r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
280 # elif v2dmid and abs(v2dmid[0] - mcursor[0]) < 10 and abs(v2dmid[1] - mcursor[1]) < 10:
281 # r_type = 'CENTER'
282 # r_loc = vmid
284 else:
285 r_loc = loc
286 is_increment = True
288 if previous_vert:
289 pv_co = main_snap_obj.mat @ previous_vert.co
290 vec = r_loc - pv_co
291 if is_increment and increment:
292 r_len = round((1 / increment) * vec.length) * increment
293 r_loc = r_len * vec.normalized() + pv_co
294 else:
295 r_len = vec.length
297 return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len
299 snap_utilities.cache = _snap_cache