From b1f54da0f4182c4a6be5b13aa735c2d91cf96ee8 Mon Sep 17 00:00:00 2001 From: varkenvarken Date: Fri, 19 Jan 2024 10:21:19 +0100 Subject: [PATCH] updated linefit.py for Blender 4.x --- linefit.py | 111 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 48 deletions(-) diff --git a/linefit.py b/linefit.py index d570906..274c1cb 100644 --- a/linefit.py +++ b/linefit.py @@ -1,6 +1,6 @@ # ##### BEGIN GPL LICENSE BLOCK ##### # -# LineFit, (c) 2017 Michel Anders (varkenvarken) +# LineFit, (c) 2017, 2024 Michel Anders (varkenvarken) # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,8 +21,8 @@ bl_info = { "name": "LineFit", "author": "Michel Anders (varkenvarken)", - "version": (0, 0, 201712201343), - "blender": (2, 79, 0), + "version": (0, 0, 20240119100755), + "blender": (4, 0, 0), "location": "Edit mode 3d-view, Add-->LineFit", "description": "Add a single edge to the mesh that best fits a collection of selected vertices", "warning": "", @@ -32,64 +32,79 @@ import numpy as np + def lineFit(points): ctr = points.mean(axis=0) x = points - ctr M = np.cov(x.T) - eigenvalues,eigenvectors = np.linalg.eig(M) - direction = eigenvectors[:,eigenvalues.argmax()] - return ctr,direction + eigenvalues, eigenvectors = np.linalg.eig(M) + direction = eigenvectors[:, eigenvalues.argmax()] + return ctr, direction + import bpy + class LineFit(bpy.types.Operator): - bl_idname = 'mesh.linefit' - bl_label = 'LineFit' - bl_options = {'REGISTER', 'UNDO'} - - size = bpy.props.FloatProperty(name="Length", description="Length of the line segment", default=1, min=0, soft_max=10) - - @classmethod - def poll(self, context): - return (context.mode == 'EDIT_MESH' and context.active_object.type == 'MESH') - - def execute(self, context): - bpy.ops.object.editmode_toggle() - me = context.active_object.data - count = len(me.vertices) - if count > 0: # degenerate mesh, but better safe than sorry - shape = (count, 3) - verts = np.empty(count*3, dtype=np.float32) - selected = np.empty(count, dtype=np.bool) - me.vertices.foreach_get('co', verts) - me.vertices.foreach_get('select', selected) - verts.shape = shape - if np.count_nonzero(selected) >= 2 : - ctr, direction = lineFit(verts[selected]) - # can't use mesh.from_pydata here because that won't let us ADD to a mesh - me.vertices.add(2) - me.vertices[count ].co = ctr-direction*self.size - me.vertices[count+1].co = ctr+direction*self.size - ecount = len(me.edges) - me.edges.add(1) - me.edges[ecount].vertices = [count,count+1] - me.update(calc_edges=False) - else: - self.report({'WARNING'}, "Need at least 2 selected vertices to fit a line through") - bpy.ops.object.editmode_toggle() - return {'FINISHED'} + bl_idname = "mesh.linefit" + bl_label = "LineFit" + bl_options = {"REGISTER", "UNDO"} + + size : bpy.props.FloatProperty( + name="Length", + description="Length of the line segment", + default=1, + min=0, + soft_max=10, + ) + + @classmethod + def poll(self, context): + return context.mode == "EDIT_MESH" and context.active_object.type == "MESH" + + def execute(self, context): + bpy.ops.object.editmode_toggle() + me = context.active_object.data + count = len(me.vertices) + if count > 0: # degenerate mesh, but better safe than sorry + shape = (count, 3) + verts = np.empty(count * 3, dtype=np.float32) + selected = np.empty(count, dtype=np.bool) + me.vertices.foreach_get("co", verts) + me.vertices.foreach_get("select", selected) + verts.shape = shape + if np.count_nonzero(selected) >= 2: + ctr, direction = lineFit(verts[selected]) + # can't use mesh.from_pydata here because that won't let us ADD to a mesh + me.vertices.add(2) + me.vertices[count].co = ctr - direction * self.size + me.vertices[count + 1].co = ctr + direction * self.size + ecount = len(me.edges) + me.edges.add(1) + me.edges[ecount].vertices = [count, count + 1] + me.update(calc_edges=False) + else: + self.report( + {"WARNING"}, + "Need at least 2 selected vertices to fit a line through", + ) + bpy.ops.object.editmode_toggle() + return {"FINISHED"} + def menu_func(self, context): - self.layout.operator(LineFit.bl_idname, text="Fit line to selected", - icon='PLUGIN') + self.layout.operator(LineFit.bl_idname, text="Fit line to selected", icon="PLUGIN") + def register(): - bpy.utils.register_module(__name__) - bpy.types.INFO_MT_mesh_add.append(menu_func) + bpy.utils.register_class(LineFit) + bpy.types.VIEW3D_MT_mesh_add.append(menu_func) + def unregister(): - bpy.types.INFO_MT_mesh_add.remove(menu_func) - bpy.utils.unregister_module(__name__) + bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) + bpy.utils.unregister_class(LineFit) + if __name__ == "__main__": - register() + register()