diff options
Diffstat (limited to 'mesh_snap_utilities_line/snap_context_l/__init__.py')
-rw-r--r-- | mesh_snap_utilities_line/snap_context_l/__init__.py | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/mesh_snap_utilities_line/snap_context_l/__init__.py b/mesh_snap_utilities_line/snap_context_l/__init__.py new file mode 100644 index 00000000..c4b03156 --- /dev/null +++ b/mesh_snap_utilities_line/snap_context_l/__init__.py @@ -0,0 +1,442 @@ +# ##### 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 ##### + +__all__ = ( + "SnapContext", + ) + +import bgl +from mathutils import Vector + +VERT = 1 +EDGE = 2 +FACE = 4 + + +class _Internal: + from .mesh_drawing import ( + gpu_Indices_enable_state, + gpu_Indices_restore_state, + gpu_Indices_use_clip_planes, + gpu_Indices_set_ProjectionMatrix, + ) + + from .utils_projection import ( + region_2d_to_orig_and_view_vector, + intersect_boundbox_threshold, + intersect_ray_segment_fac, + project_co_v3, + ) + + from mathutils.geometry import intersect_line_plane + + +class _SnapObjectData(): + __slots__ = ('data', 'mat') + def __init__(self, data, omat): + self.data = data + self.mat = omat + + +class _SnapOffscreen(): + bound = None + def __init__(self, width, height): + import ctypes + + self.freed = False + self.is_bound = False + + self.width = width + self.height = height + + self.fbo = bgl.Buffer(bgl.GL_INT, 1) + self.buf_color = bgl.Buffer(bgl.GL_INT, 1) + self.buf_depth = bgl.Buffer(bgl.GL_INT, 1) + + self.cur_fbo = bgl.Buffer(bgl.GL_INT, 1) + self.cur_viewport = bgl.Buffer(bgl.GL_INT, 4) + + bgl.glGenRenderbuffers(1, self.buf_depth) + bgl.glBindRenderbuffer(bgl.GL_RENDERBUFFER, self.buf_depth[0]) + bgl.glRenderbufferStorage(bgl.GL_RENDERBUFFER, bgl.GL_DEPTH_COMPONENT, width, height) + + bgl.glGenTextures(1, self.buf_color) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.buf_color[0]) + NULL = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0)) + bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_R32UI, width, height, 0, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, NULL) + del NULL + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST) + + bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo) + + bgl.glGenFramebuffers(1, self.fbo) + bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0]) + bgl.glFramebufferRenderbuffer(bgl.GL_FRAMEBUFFER, bgl.GL_DEPTH_ATTACHMENT, bgl.GL_RENDERBUFFER, self.buf_depth[0]) + bgl.glFramebufferTexture(bgl.GL_FRAMEBUFFER, bgl.GL_COLOR_ATTACHMENT0, self.buf_color[0], 0) + + bgl.glDrawBuffers(1, bgl.Buffer(bgl.GL_INT, 1, [bgl.GL_COLOR_ATTACHMENT0])) + + status = bgl.glCheckFramebufferStatus(bgl.GL_FRAMEBUFFER) + if status != bgl.GL_FRAMEBUFFER_COMPLETE: + print("Framebuffer Invalid", status) + + bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0]) + + def bind(self): + if self is not _SnapOffscreen.bound: + if _SnapOffscreen.bound is None: + bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo) + bgl.glGetIntegerv(bgl.GL_VIEWPORT, self.cur_viewport) + + bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0]) + bgl.glViewport(0, 0, self.width, self.height) + _SnapOffscreen.bound = self + + def unbind(self): + if self is _SnapOffscreen.bound: + bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0]) + bgl.glViewport(*self.cur_viewport) + _SnapOffscreen.bound = None + + def clear(self): + is_bound = self is _SnapOffscreen.bound + if not is_bound: + self.bind() + + bgl.glColorMask(bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE) + bgl.glClearColor(0.0, 0.0, 0.0, 0.0) + + bgl.glDepthMask(bgl.GL_TRUE) + bgl.glClearDepth(1.0); + + bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT) + + if not is_bound: + self.unbind() + + def __del__(self): + if not self.freed: + bgl.glDeleteFramebuffers(1, self.fbo) + bgl.glDeleteRenderbuffers(1, self.buf_depth) + bgl.glDeleteTextures(1, self.buf_color) + del self.fbo + del self.buf_color + del self.buf_depth + + del self.cur_fbo + del self.cur_viewport + + def free(self): + self.__del__() + self.freed = True + + +class SnapContext(): + """ + Initializes the snap context with the region and space where the snap objects will be added. + + .. note:: + After the context has been created, add the objects with the `add_obj` method. + + :arg region: region of the 3D viewport, typically bpy.context.region. + :type region: :class:`bpy.types.Region` + :arg space: 3D region data, typically bpy.context.space_data. + :type space: :class:`bpy.types.SpaceView3D` + """ + + def __init__(self, region, space): + #print('Render:', bgl.glGetString(bgl.GL_RENDERER)) + #print('OpenGL Version:', bgl.glGetString(bgl.GL_VERSION)) + + self.freed = False + self.snap_objects = [] + self.drawn_count = 0 + self._offset_cur = 1 # Starts with index 1 + self.region = region + self.rv3d = space.region_3d + + if self.rv3d.is_perspective: + self.depth_range = Vector((space.clip_start, space.clip_end)) + else: + self.depth_range = Vector((-space.clip_end, space.clip_end)) + + self.proj_mat = None + self.mval = Vector((0, 0)) + self._snap_mode = VERT | EDGE | FACE + + self.set_pixel_dist(12) + + self._offscreen = _SnapOffscreen(self.region.width, self.region.height) + + self.winsize = Vector((self._offscreen.width, self._offscreen.height)) + + self._offscreen.clear() + + ## PRIVATE ## + + def _get_snap_obj_by_index(self, index): + if index: + for snap_obj in self.snap_objects[:self.drawn_count]: + data = snap_obj.data[1] + if index < data.first_index + data.get_tot_elems(): + return snap_obj + return None + + def _get_nearest_index(self): + r_snap_obj = None + r_value = 0 + + loc = [self._dist_px, self._dist_px] + d = 1 + m = self.threshold + max_val = 2 * m - 1 + last_value = -1 + find_next_index = self._snap_mode & FACE and self._snap_mode & (VERT | EDGE) + while m < max_val: + for i in range(2): + while 2 * loc[i] * d < m: + value = int(self._snap_buffer[loc[0]][loc[1]]) + loc[i] += d + if value != last_value: + r_value = value + if find_next_index: + last_value = value + r_snap_obj = self._get_snap_obj_by_index(value) + if (r_snap_obj is None) or value < (r_snap_obj.data[1].first_index + len(r_snap_obj.data[1].tri_verts)): + continue + find_next_index = False + elif (r_snap_obj is None) or\ + (value < r_snap_obj.data[1].first_index) or\ + (value >= (r_snap_obj.data[1].first_index + r_snap_obj.data[1].get_tot_elems())): + r_snap_obj = self._get_snap_obj_by_index(value) + return r_snap_obj, r_value + d = -d + m += 4 * self._dist_px * d + 1 + + return r_snap_obj, r_value + + def _get_loc(self, snap_obj, index): + index -= snap_obj.data[1].first_index + gpu_data = snap_obj.data[1] + + if gpu_data.draw_tris: + num_tris = len(snap_obj.data[1].tri_verts) + if index < num_tris: + tri_verts = gpu_data.get_tri_verts(index) + tri_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_tri_co(index)] + nor = (tri_co[1] - tri_co[0]).cross(tri_co[2] - tri_co[0]) + return _Internal.intersect_line_plane(self.last_ray[1], self.last_ray[1] + self.last_ray[0], tri_co[0], nor), tri_verts, tri_co + + index -= num_tris + + if gpu_data.draw_edges: + num_edges = len(snap_obj.data[1].edge_verts) + if index < num_edges: + edge_verts = gpu_data.get_edge_verts(index) + edge_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_edge_co(index)] + fac = _Internal.intersect_ray_segment_fac(*edge_co, *self.last_ray) + + if (self._snap_mode) & VERT and (fac < 0.25 or fac > 0.75): + co = edge_co[0] if fac < 0.5 else edge_co[1] + proj_co = _Internal.project_co_v3(self, co) + dist = self.mval - proj_co + if abs(dist.x) < self._dist_px and abs(dist.y) < self._dist_px: + return co, (edge_verts[0] if fac < 0.5 else edge_verts[1],), co + + if fac <= 0.0: + co = edge_co[0] + elif fac >= 1.0: + co = edge_co[1] + else: + co = edge_co[0] + fac * (edge_co[1] - edge_co[0]) + + return co, edge_verts, edge_co + + index -= num_edges + + if gpu_data.draw_verts: + if index < len(snap_obj.data[1].looseverts): + co = snap_obj.mat @ Vector(gpu_data.get_loosevert_co(index)) + return co, (gpu_data.get_loosevert_index(index),), co + + return None, None, None + + + def _get_snap_obj_by_obj(self, obj): + for snap_obj in self.snap_objects: + if obj == snap_obj.data[0]: + return snap_obj + + def __del__(self): + if not self.freed: + self._offscreen.free() + # Some objects may still be being referenced + for snap_obj in self.snap_objects: + del snap_obj.data + del snap_obj.mat + del snap_obj + del self.snap_objects + + ## PUBLIC ## + + def update_all(self): + self.drawn_count = 0 + self._offset_cur = 1 + self._offscreen.clear() + + def tag_update_drawn_snap_object(self, snap_obj): + if len(snap_obj.data) > 1: + del snap_obj.data[1:] + #self.update_all() + # Update on next snap_get call # + self.proj_mat = None + + def update_drawn_snap_object(self, snap_obj): + if len(snap_obj.data) > 1: + _Internal.gpu_Indices_enable_state() + + from .mesh_drawing import GPU_Indices_Mesh + snap_vert = self._snap_mode & VERT != 0 + snap_edge = self._snap_mode & EDGE != 0 + snap_face = self._snap_mode & FACE != 0 + snap_obj.data[1] = GPU_Indices_Mesh(snap_obj.data[0], snap_face, snap_edge, snap_vert) + + _Internal.gpu_Indices_restore_state() + + def use_clip_planes(self, value): + _Internal.gpu_Indices_use_clip_planes(self.rv3d, value) + + def set_pixel_dist(self, dist_px): + self._dist_px = int(dist_px) + self._dist_px_sq = self._dist_px ** 2 + self.threshold = 2 * self._dist_px + 1 + self._snap_buffer = bgl.Buffer(bgl.GL_INT, (self.threshold, self.threshold)) + + def set_snap_mode(self, snap_to_vert, snap_to_edge, snap_to_face): + snap_mode = 0 + if snap_to_vert: + snap_mode |= VERT + if snap_to_edge: + snap_mode |= EDGE + if snap_to_face: + snap_mode |= FACE + + if snap_mode != self._snap_mode: + self._snap_mode = snap_mode + self.update_all() + + def add_obj(self, obj, matrix): + matrix = matrix.copy() + snap_obj = self._get_snap_obj_by_obj(obj) + if not snap_obj: + self.snap_objects.append(_SnapObjectData([obj], matrix)) + else: + self.snap_objects.append(_SnapObjectData(snap_obj.data, matrix)) + + return self.snap_objects[-1] + + def get_ray(self, mval): + self.last_ray = _Internal.region_2d_to_orig_and_view_vector(self.region, self.rv3d, mval) + return self.last_ray + + def snap_get(self, mval, main_snap_obj = None): + ret = None, None, None + self.mval[:] = mval + snap_vert = self._snap_mode & VERT != 0 + snap_edge = self._snap_mode & EDGE != 0 + snap_face = self._snap_mode & FACE != 0 + + _Internal.gpu_Indices_enable_state() + self._offscreen.bind() + + #bgl.glDisable(bgl.GL_DITHER) # dithering and AA break color coding, so disable # + #multisample_enabled = bgl.glIsEnabled(bgl.GL_MULTISAMPLE) + #bgl.glDisable(bgl.GL_MULTISAMPLE) + bgl.glEnable(bgl.GL_DEPTH_TEST) + + proj_mat = self.rv3d.perspective_matrix.copy() + if self.proj_mat != proj_mat: + self.proj_mat = proj_mat + _Internal.gpu_Indices_set_ProjectionMatrix(self.proj_mat) + self.update_all() + + ray_dir, ray_orig = self.get_ray(mval) + for i, snap_obj in enumerate(self.snap_objects[self.drawn_count:], self.drawn_count): + obj = snap_obj.data[0] + try: + bbmin = Vector(obj.bound_box[0]) + bbmax = Vector(obj.bound_box[6]) + except ReferenceError: + self.snap_objects.remove(snap_obj) + continue + + if bbmin != bbmax: + MVP = proj_mat @ snap_obj.mat + mat_inv = snap_obj.mat.inverted() + ray_orig_local = mat_inv @ ray_orig + ray_dir_local = mat_inv.to_3x3() @ ray_dir + in_threshold = _Internal.intersect_boundbox_threshold( + self, MVP, ray_orig_local, ray_dir_local, bbmin, bbmax) + else: + proj_co = _Internal.project_co_v3(self, snap_obj.mat.translation) + dist = self.mval - proj_co + in_threshold = abs(dist.x) < self._dist_px and abs(dist.y) < self._dist_px + #snap_obj.data[1] = primitive_point + + if in_threshold: + if len(snap_obj.data) == 1: + from .mesh_drawing import GPU_Indices_Mesh + snap_obj.data.append(GPU_Indices_Mesh(obj, snap_face, snap_edge, snap_vert)) + snap_obj.data[1].set_draw_mode(snap_face, snap_edge, snap_vert) + snap_obj.data[1].set_ModelViewMatrix(snap_obj.mat) + + if snap_obj == main_snap_obj: + snap_obj.data[1].Draw(self._offset_cur, -0.0001) + else: + snap_obj.data[1].Draw(self._offset_cur) + self._offset_cur += snap_obj.data[1].get_tot_elems() + + tmp = self.snap_objects[self.drawn_count] + self.snap_objects[self.drawn_count] = self.snap_objects[i] + self.snap_objects[i] = tmp + + self.drawn_count += 1 + + bgl.glReadBuffer(bgl.GL_COLOR_ATTACHMENT0) + bgl.glReadPixels( + int(self.mval[0]) - self._dist_px, int(self.mval[1]) - self._dist_px, + self.threshold, self.threshold, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, self._snap_buffer) + #bgl.glReadBuffer(bgl.GL_BACK) + #import numpy as np + #a = np.array(self._snap_buffer) + #print(a) + + snap_obj, index = self._get_nearest_index() + #print("index:", index) + if snap_obj: + ret = self._get_loc(snap_obj, index) + + bgl.glDisable(bgl.GL_DEPTH_TEST) + self._offscreen.unbind() + _Internal.gpu_Indices_restore_state() + + return (snap_obj, *ret) + + def free(self): + self.__del__() + self.freed = True |