diff --git a/citation.txt b/citation.txt new file mode 100644 index 0000000..a1d821a --- /dev/null +++ b/citation.txt @@ -0,0 +1,31 @@ +Original Papers First introduced by Chen and Medioni (1991) and Besl and McKay (1992). + + +1. Chen, Yang; Gerard Medioni (1991). + "Object modelling by registration of multiple range images". + Image Vision Comput. + 10 (3): 145–155. doi:10.1016/0262-8856(92)90066-C. + +2. Besl, Paul J.; N.D. McKay (1992). + "A Method for Registration of 3-D Shapes". + IEEE Transactions on Pattern Analysis and Machine Intelligence. + 14 (2): 239–256. doi:10.1109/34.121791. + +3.Transformations Python Library +Author: Christoph Gohlke `_ +Organization: Laboratory for Fluorescence Dynamics. University of California, Irvine +https://www.lfd.uci.edu/~gohlke/code/transformations.py.html + +4. FINDING OPTIMAL ROTATION AND TRANSLATION BETWEEN CORRESPONDING 3D POINTS +Author: Nghia Ho, nghiaho12@yahoo.com +Organization: +https://nghiaho.com/?page_id=671 +http://nghiaho.com/uploads/code/rigid_transform_3D.py + +5. Blender Implementation, Filtering Code, UI, BVH Acceleration +Modal Operator +Patrick Moore: patrick.moore.bu@gmail.com +working as a hobby and later update for D3tool.com + + +### Add Additional Citation Below ### \ No newline at end of file diff --git a/functions/general.py b/functions/general.py index f0eb385..e9266af 100644 --- a/functions/general.py +++ b/functions/general.py @@ -273,7 +273,7 @@ def make_pairs(align_obj, base_obj, base_bvh, vlist, thresh, sample = 0, calc_st vert = align_obj.data.vertices[vert_ind] #closest point for point clouds. Local space of base obj - co_find = imx2 * (mx1 * vert.co) + co_find = imx2 @ (mx1 @ vert.co) #closest surface point for triangle mesh #this is set up for a well modeled aligning object with @@ -285,12 +285,12 @@ def make_pairs(align_obj, base_obj, base_bvh, vlist, thresh, sample = 0, calc_st #res, co1, normal, face_index = base_obj.closest_point_on_mesh(co_find) co1, n, face_index, d = base_bvh.find_nearest(co_find) - dist = (mx2 * co_find - mx2 * co1).length + dist = (mx2 @ co_find - mx2 @ co1).length #d is now returned by bvh.find #dist = mx2.to_scale() * d if face_index != -1 and dist < thresh: verts1.append(vert.co) - verts2.append(imx1 * (mx2 * co1)) + verts2.append(imx1 @ (mx2 @ co1)) if calc_stats: dists.append(dist) diff --git a/functions/utilities.py b/functions/utilities.py index 26df1e1..49342f1 100644 --- a/functions/utilities.py +++ b/functions/utilities.py @@ -71,7 +71,7 @@ def draw_3d_points_revised(context, points, color, size): for vec in points: - vec_4d = perspective_matrix * vec.to_4d() + vec_4d = perspective_matrix @ vec.to_4d() if vec_4d.w > 0.0: x = region_mid_width + region_mid_width * (vec_4d.x / vec_4d.w) y = region_mid_height + region_mid_height * (vec_4d.y / vec_4d.w) @@ -87,7 +87,7 @@ def draw_3d_text(context, font_id, text, vec): region_mid_height = region.height / 2.0 perspective_matrix = region3d.perspective_matrix.copy() - vec_4d = perspective_matrix * vec.to_4d() + vec_4d = perspective_matrix @ vec.to_4d() if vec_4d.w > 0.0: x = region_mid_width + region_mid_width * (vec_4d.x / vec_4d.w) y = region_mid_height + region_mid_height * (vec_4d.y / vec_4d.w) @@ -117,8 +117,8 @@ def get_ray_origin(ray_origin, ray_direction, ob): if abs(ray_direction.x)>0.0001: planes += [(bm,x), (bM,-x)] if abs(ray_direction.y)>0.0001: planes += [(bm,y), (bM,-y)] if abs(ray_direction.z)>0.0001: planes += [(bm,z), (bM,-z)] - dists = [get_ray_plane_intersection(ray_origin,ray_direction,mx*p0,q*no) for p0,no in planes] - return ray_origin + ray_direction * min(dists) + dists = [get_ray_plane_intersection(ray_origin,ray_direction,mx@p0,q@no) for p0,no in planes] + return ray_origin + ray_direction @ min(dists) # Jon Denning for Retopoflow def ray_cast_region2d(region, rv3d, screen_coord, obj): @@ -139,7 +139,7 @@ def ray_cast_region2d(region, rv3d, screen_coord, obj): bver = '%03d.%03d.%03d' % (bpy.app.version[0],bpy.app.version[1],bpy.app.version[2]) if (bver < '002.072.000') and not rv3d.is_perspective: mult *= -1 - st, en = imx*(o-mult*back*d), imx*(o+mult*d) + st, en = imx@(o-mult*back*d), imx@(o+mult*d) if bversion() < '002.077.000': hit = obj.ray_cast(st,en) diff --git a/operators/align_pick_points.py b/operators/align_pick_points.py index ad9dc34..8f52e07 100644 --- a/operators/align_pick_points.py +++ b/operators/align_pick_points.py @@ -14,6 +14,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +#https://cognitivewaves.wordpress.com/opengl-vbo-shader-vao/#shader-with-vertex-buffer-object # System imports import time @@ -22,6 +23,12 @@ # Blender imports import bpy import blf +import bgl +import gpu + +from gpu_extras.batch import batch_for_shader + + from bpy.types import Operator from mathutils import Matrix from bpy_extras import view3d_utils @@ -38,24 +45,49 @@ def draw_callback_px(self, context): y = context.region.height dims = blf.dimensions(0, 'A') - blf.position(font_id, 10, y - 20 - dims[1], 0) + #blf.position(font_id, 10, y - 20 - dims[1], 0) + blf.position(font_id, 10, 20 + dims[1], 0) + blf.size(font_id, 20, 72) if context.area.x == self.area_align.x: blf.draw(font_id, "Align: "+ self.align_msg) - points = [self.obj_align.matrix_world * p for p in self.align_points] + points = [self.obj_align.matrix_world @ p for p in self.align_points] color = (1,0,0,1) else: blf.draw(font_id, "Base: " + self.base_msg) - points = [self.obj_align.matrix_world * p for p in self.base_points] + points = [self.obj_align.matrix_world @ p for p in self.base_points] color = (0,1,0,1) - draw_3d_points_revised(context, points, color, 4) + #draw_3d_points_revised(context, points, color, 4) for i, vec in enumerate(points): ind = str(i) draw_3d_text(context, font_id, ind, vec) + +def draw_callback_view(self, context): + bgl.glPointSize(8) + #print('draw view!') + if context.area.x == self.area_align.x: + if not self.align_shader: + return + + self.align_shader.bind() + self.align_shader.uniform_float("color", (1,0,1,1)) + self.align_batch.draw(self.align_shader) + else: + if not self.base_shader: + return + self.base_shader.bind() + self.base_shader.uniform_float("color", (1,1,0,1)) + self.base_batch.draw(self.base_shader) + + bgl.glPointSize(1) + pass + + + class OBJECT_OT_align_pick_points(Operator): """Align two objects with 3 or more pair of picked points""" bl_idname = "object.align_picked_points" @@ -120,7 +152,7 @@ def modal(self, context, event): view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) - ray_target = ray_origin + (view_vector * ray_max) + ray_target = ray_origin + (ray_max * view_vector) print('in the align object window') (d, (ok,hit, normal, face_index)) = ray_cast_region2d(region, rv3d, coord, self.obj_align) @@ -128,6 +160,7 @@ def modal(self, context, event): print('hit! align_obj %s' % self.obj_align.name) #local space of align object self.align_points.append(hit) + self.create_batch_align() else: @@ -141,15 +174,15 @@ def modal(self, context, event): coord = (event.mouse_x - region.x, event.mouse_y - region.y) view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) - ray_target = ray_origin + (view_vector * ray_max) + ray_target = ray_origin + (ray_max * view_vector) print('in the base object window') (d, (ok,hit, normal, face_index)) = ray_cast_region2d(region, rv3d, coord, self.obj_base) if ok: print('hit! base_obj %s' % self.obj_base.name) #points in local space of align object - self.base_points.append(self.obj_align.matrix_world.inverted() * self.obj_base.matrix_world * hit) - + self.base_points.append(self.obj_align.matrix_world.inverted() @ self.obj_base.matrix_world @ hit) + self.create_batch_base() return {'RUNNING_MODAL'} @@ -157,8 +190,10 @@ def modal(self, context, event): if event.mouse_x > self.area_align.x and event.mouse_x < self.area_align.x + self.area_align.width: self.align_points.pop() + self.create_batch_align() else: self.base_points.pop() + self.create_batch_base() return {'RUNNING_MODAL'} @@ -201,18 +236,20 @@ def modal(self, context, event): return {'PASS_THROUGH'} elif event.type in {'ESC'}: - bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + bpy.types.SpaceView3D.draw_handler_remove(self._2Dhandle, 'WINDOW') + bpy.types.SpaceView3D.draw_handler_remove(self._3Dhandle, 'WINDOW') return {'CANCELLED'} elif event.type == 'RET': if len(self.align_points) >= 3 and len(self.base_points) >= 3 and len(self.align_points) == len(self.base_points): - bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + bpy.types.SpaceView3D.draw_handler_remove(self._2Dhandle, 'WINDOW') + bpy.types.SpaceView3D.draw_handler_remove(self._3Dhandle, 'WINDOW') self.de_localize(context) self.align_obj(context) - context.scene.objects.active = self.obj_align - self.obj_align.select = True + context.view_layer.objects.active = self.obj_align + self.obj_align.select_set(True) self.obj_base = True return {'FINISHED'} @@ -238,9 +275,9 @@ def invoke(self, context, event): obj2_name = [obj for obj in context.selected_objects if obj != context.object][0].name for ob in context.scene.objects: - ob.select = False + ob.select_set(False) - context.scene.objects.active = None + bpy.context.view_layer.objects.active= None#context.scene.objects.active = None #I did this stupid method becuase I was unsure #if some things were being "sticky" and not @@ -249,7 +286,7 @@ def invoke(self, context, event): obj2 = bpy.data.objects[obj2_name] for ob in bpy.data.objects: - if ob.select: + if ob.select_set(True): print(ob.name) screen = context.window.screen @@ -258,31 +295,43 @@ def invoke(self, context, event): if area.type == 'VIEW_3D': break - bpy.ops.view3d.toolshelf() #close the first toolshelf + #bpy.ops.view3d.toolshelf() #close the first toolshelf override = context.copy() override['area'] = area self.area_align = area - bpy.ops.screen.area_split(override, direction='VERTICAL', factor=0.5, mouse_x=-100, mouse_y=-100) + bpy.ops.screen.area_split(direction='VERTICAL', factor=0.5, cursor=(100,-100))#bpy.ops.screen.area_split(override, direction='VERTICAL', factor=0.5, mouse_x=-100, mouse_y=-100) #bpy.ops.view3d.toolshelf() #close the 2nd toolshelf + - context.scene.objects.active = obj1 - obj1.select = True - obj2.select = False + bpy.context.view_layer.objects.active = obj1 + obj1.select_set(True) + obj2.select_set(False) bpy.ops.view3d.localview(override) + + + #..........Hide sidebar after area split........................... + for A in bpy.context.screen.areas: + if A.type == 'VIEW_3D' : + ctx = bpy.context.copy() + ctx['area'] = A + bpy.ops.screen.region_toggle(ctx, region_type='UI') +#................................................................... + + - obj1.select = False - context.scene.objects.active = None + obj1.select_set(False) + bpy.context.view_layer.objects.active = None override = context.copy() for area in screen.areas: if area.as_pointer() not in areas: override['area'] = area self.area_base = area bpy.ops.object.select_all(action = 'DESELECT') - context.scene.objects.active = obj2 - obj2.select = True + bpy.context.view_layer.objects.active = obj2 + obj2.select_set(True) override['selected_objects'] = [obj2] override['selected_editable_objects'] = [obj2] override['object'] = obj2 @@ -298,13 +347,34 @@ def invoke(self, context, event): self.align_points = [] self.base_points = [] + self.base_batch = None + self.base_shader = None + self.align_batch = None + self.align_shader = None context.window_manager.modal_handler_add(self) - self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') + self._2Dhandle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') + self._3Dhandle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_view, (self, context), 'WINDOW', 'POST_VIEW') + + return {'RUNNING_MODAL'} ############################################# # class methods + + def create_batch_base(self): + verts = [self.obj_align.matrix_world @ p for p in self.base_points] + vertices = [(v.x, v.y, v.z) for v in verts] + self.base_shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') + self.base_batch = batch_for_shader(self.base_shader, 'POINTS', {"pos":vertices}) + + + def create_batch_align(self): + verts = [self.obj_align.matrix_world @ p for p in self.align_points] + vertices = [(v.x, v.y, v.z) for v in verts] + self.align_shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') + self.align_batch = batch_for_shader(self.base_shader, 'POINTS', {"pos":vertices}) + def de_localize(self,context): override = context.copy() @@ -316,10 +386,15 @@ def de_localize(self,context): bpy.ops.view3d.localview(override) bpy.ops.view3d.view_selected(override) - #Crash Blender? - bpy.ops.screen.area_join(min_x=self.area_align.x,min_y=self.area_align.y, max_x=self.area_base.x, max_y=self.area_base.y) - bpy.ops.view3d.toolshelf() +#............Crash Blender? Resolve................................ + xj = int(self.area_align.width + 1) + yj = int(self.area_align.y + self.area_align.height / 2) + bpy.ops.screen.area_join(cursor=(xj,yj)) +#.................................................................. + #bpy.ops.view3d.toolshelf() + bpy.ops.screen.screen_full_area() + bpy.ops.screen.screen_full_area() #ret = bpy.ops.screen.area_join(min_x=area_base.x,min_y=area_base.y, max_x=area_align.x, max_y=area_align.y) def align_obj(self,context): @@ -361,7 +436,7 @@ def align_obj(self,context): #because we calced transform in local space #it's this easy to update the obj... - self.obj_align.matrix_world = self.obj_align.matrix_world * new_mat + self.obj_align.matrix_world = self.obj_align.matrix_world @ new_mat self.obj_align.update_tag() - context.scene.update() + context.view_layer.update() diff --git a/operators/icp_align.py b/operators/icp_align.py index 723ab4d..39cc178 100644 --- a/operators/icp_align.py +++ b/operators/icp_align.py @@ -41,8 +41,8 @@ class OBJECT_OT_icp_align(Operator): @classmethod def poll(cls, context): condition_1 = len(context.selected_objects) == 2 - conidion_2 = context.object.type == 'MESH' - return condition_1 and condition_1 + condition_2 = context.object.type == 'MESH' + return condition_1 and condition_2 def execute(self, context): settings = get_addon_preferences() @@ -50,7 +50,7 @@ def execute(self, context): start = time.time() align_obj = context.object base_obj = [obj for obj in context.selected_objects if obj != align_obj][0] - base_bvh = BVHTree.FromObject(base_obj, context.scene) + base_bvh = BVHTree.FromObject(base_obj, context.evaluated_depsgraph_get()) align_obj.rotation_mode = 'QUATERNION' vlist = [] @@ -108,12 +108,13 @@ def execute(self, context): for z in range(0,4): new_mat[y][z] = M[y][z] - align_obj.matrix_world = align_obj.matrix_world * new_mat + align_obj.matrix_world = align_obj.matrix_world @ new_mat trans = new_mat.to_translation() quat = new_mat.to_quaternion() align_obj.update_tag() - context.scene.update() + context.view_layer.update() + #context.scene.update() if d_stats: i = int(fmod(n,5)) diff --git a/operators/icp_align_feedback.py b/operators/icp_align_feedback.py index 5145c8f..8615c3c 100644 --- a/operators/icp_align_feedback.py +++ b/operators/icp_align_feedback.py @@ -46,7 +46,7 @@ def poll(cls, context): def invoke(self,context, event): wm = context.window_manager - self._timer = wm.event_timer_add(0.01, context.window) + self._timer = wm.event_timer_add(time_step = 0.01, window = context.window) wm.modal_handler_add(self) settings = get_addon_preferences() @@ -54,7 +54,7 @@ def invoke(self,context, event): self.start = time.time() self.align_obj = context.object self.base_obj = [obj for obj in context.selected_objects if obj != self.align_obj][0] - self.base_bvh = BVHTree.FromObject(self.base_obj, context.scene) + self.base_bvh = BVHTree.FromObject(self.base_obj, context.evaluated_depsgraph_get()) self.align_obj.rotation_mode = 'QUATERNION' self.vlist = [] @@ -132,7 +132,7 @@ def execute(self, context): start = time.time() align_obj = context.object base_obj = [obj for obj in context.selected_objects if obj != align_obj][0] - base_bvh = BVHTree.FromObject(base_obj, context.scene) + base_bvh = BVHTree.FromObject(base_obj, context.evaluated_depsgraph_get()) align_obj.rotation_mode = 'QUATERNION' vlist = [] @@ -190,12 +190,13 @@ def execute(self, context): for z in range(0,4): new_mat[y][z] = M[y][z] - align_obj.matrix_world = align_obj.matrix_world * new_mat + align_obj.matrix_world = align_obj.matrix_world @ new_mat trans = new_mat.to_translation() quat = new_mat.to_quaternion() align_obj.update_tag() - context.scene.update() + #context.scene.update() + context.view_layer.update() if d_stats: i = int(fmod(n,5)) @@ -252,7 +253,7 @@ def iterate(self,context): for z in range(0,4): new_mat[y][z] = M[y][z] - self.align_obj.matrix_world = self.align_obj.matrix_world * new_mat + self.align_obj.matrix_world = self.align_obj.matrix_world @ new_mat trans = new_mat.to_translation() quat = new_mat.to_quaternion()