From c1b1d2c92488c0b7b336b47bc95c250075f78e16 Mon Sep 17 00:00:00 2001 From: satabol Date: Sun, 22 Oct 2023 17:33:05 +0300 Subject: [PATCH 1/8] fix #5030. New node "Aligned Bounding Box" --- index.yaml | 1 + menus/full_by_data_type.yaml | 1 + menus/full_nortikin.yaml | 1 + nodes/analyzer/bbox_aligned.py | 249 +++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 nodes/analyzer/bbox_aligned.py diff --git a/index.yaml b/index.yaml index fdeef479c1..f8ca4cf9ff 100644 --- a/index.yaml +++ b/index.yaml @@ -355,6 +355,7 @@ - icon_name: VIEWZOOM - extra_menu: MeshPartialMenu - SvBBoxNodeMk3 + - SvAlignedBBoxNode - SvComponentAnalyzerNode - SvDiameterNode - SvVolumeNodeMK2 diff --git a/menus/full_by_data_type.yaml b/menus/full_by_data_type.yaml index ca2e9b4591..46c02c6102 100644 --- a/menus/full_by_data_type.yaml +++ b/menus/full_by_data_type.yaml @@ -181,6 +181,7 @@ - extra_menu: MeshPartialMenu # to make the category available in another menu (1,2,3,4,5) - icon_name: VIEWZOOM - SvBBoxNodeMk3 + - SvAlignedBBoxNode - SvComponentAnalyzerNode - SvDiameterNode - SvVolumeNodeMK2 diff --git a/menus/full_nortikin.yaml b/menus/full_nortikin.yaml index 80929b5f7b..b4dfad91e1 100644 --- a/menus/full_nortikin.yaml +++ b/menus/full_nortikin.yaml @@ -407,6 +407,7 @@ - extra_menu: MeshPartialMenu - icon_name: OBJECT_DATAMODE # icon name to show - SvBBoxNodeMk3 + - SvAlignedBBoxNode - SvComponentAnalyzerNode - SvDiameterNode - SvVolumeNodeMK2 diff --git a/nodes/analyzer/bbox_aligned.py b/nodes/analyzer/bbox_aligned.py new file mode 100644 index 0000000000..7c42784ef4 --- /dev/null +++ b/nodes/analyzer/bbox_aligned.py @@ -0,0 +1,249 @@ +# ##### 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 2 +# 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, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +from itertools import product +import numpy as np +import bpy +from bpy.props import BoolVectorProperty, EnumProperty +from mathutils import Matrix + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.utils.nodes_mixins.recursive_nodes import SvRecursiveNode + +from sverchok.data_structure import dataCorrect, updateNode +from sverchok.utils.geom import bounding_box_aligned, bounding_box_aligned_01 + +EDGES = [ + (0, 1), (1, 3), (3, 2), (2, 0), # bottom edges + (4, 5), (5, 7), (7, 6), (6, 4), # top edges + (0, 4), (1, 5), (2, 6), (3, 7) # sides +] +def generate_matrix(maxmin, dims, to_2d): + center = [(u+v)*.5 for u, v in maxmin[:dims]] + scale = [(u-v) for u, v in maxmin[:dims]] + if to_2d: + center += [0] + scale += [1] + mat = Matrix.Translation(center) + for i, sca in enumerate(scale): + mat[i][i] = sca + return mat + +def generate_mean_np(verts, dims, to_2d): + avr = (np.sum(verts[:, :dims], axis=0)/len(verts)).tolist() + if to_2d: + avr += [0] + return [avr] + +def generate_mean(verts, dims, to_2d): + avr = list(map(sum, zip(*verts))) + avr = [n/len(verts) for n in avr[:dims]] + if to_2d: + avr += [0] + return [avr] + +def bounding_box(verts, + box_dimensions='2D', + output_verts=True, + output_mat=True, + output_mean=True, + output_limits=True): + ''' + verts expects a list of level 3 [[[0,0,0],[1,1,1]..],[]] + returns per sublist: + verts_out: vertices of the bounding box + edges_out: edges of the bounding box + mean_out: mean of all vertcies + mat_out: Matrix that would transform a box of 1 unit into the bbox + *min_vals, Min X, Y and Z of the bounding box + *max_vals, Max X, Y and Z of the bounding box + *size_vals Size X, Y and Z of the bounding box + ''' + verts_out = [] + edges_out = [] + edges = EDGES + + mat_out = [] + mean_out = [] + min_vals = [[], [], []] + max_vals = [[], [], []] + size_vals = [[], [], []] + to_2d = box_dimensions == '2D' + dims = int(box_dimensions[0]) + calc_maxmin = output_mat or output_verts or output_limits + + for vec in verts: + if calc_maxmin: + if isinstance(vec, np.ndarray): + np_vec = vec + else: + np_vec = np.array(vec) + bbox_max = np.amax(np_vec, axis=0) + bbox_min = np.amin(np_vec, axis=0) + maxmin = np.concatenate([bbox_max, bbox_min]).reshape(2,3).T.tolist() + + if output_verts: + out = list(product(*reversed(maxmin))) + v_out = [l[::-1] for l in out[::-1]] + if to_2d: + verts_out.append([[v[0], v[1], 0] for v in v_out[:4]]) + edges = edges[:4] + else: + verts_out.append(v_out) + edges_out.append(edges) + + if output_mat: + mat_out.append(generate_matrix(maxmin, dims, to_2d)) + + if output_mean: + if calc_maxmin: + mean_out.append(generate_mean_np(np_vec, dims, to_2d)) + else: + if isinstance(vec, np.ndarray): + mean_out.append(generate_mean_np(vec, dims, to_2d)) + else: + mean_out.append(generate_mean(vec, dims, to_2d)) + + if output_limits: + for i in range(dims): + min_vals[i].append([maxmin[i][1]]) + max_vals[i].append([maxmin[i][0]]) + size_vals[i].append([maxmin[i][0] - maxmin[i][1]]) + + return (verts_out, + edges_out, + mean_out, + mat_out, + *min_vals, + *max_vals, + *size_vals) + + +class SvAlignedBBoxNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): + """ + Triggers: Bbox 2D or 3D + Tooltip: Get vertices bounding box (vertices, sizes, center) + """ + bl_idname = 'SvAlignedBBoxNode' + bl_label = 'Aligned Bounding Box' + bl_icon = 'SHADING_BBOX' + sv_icon = 'SV_BOUNDING_BOX' + + def update_sockets(self, context): + bools = [self.min_list, self.max_list, self.size_list] + dims = int(self.box_dimensions[0]) + for i in range(3): + for j in range(3): + out_index = 4 + j + 3*i + hidden = self.outputs[out_index].hide_safe + if bools[i][j] and j < dims: + if hidden: + self.outputs[out_index].hide_safe = False + else: + self.outputs[out_index].hide_safe = True + + updateNode(self, context) + + min_list: BoolVectorProperty( + name='Min', description="Show Minimum values sockets", size=3, update=update_sockets) + max_list: BoolVectorProperty( + name='Max', description="Show Maximum values sockets", size=3, update=update_sockets) + size_list: BoolVectorProperty( + name='Size', description="Show Size values sockets", size=3, update=update_sockets) + implentation_modes = [ + ("2D", "2D", "Outputs Rectangle over XY plane", 0), + ("3D", "3D", "Outputs standard bounding box", 1)] + box_dimensions: EnumProperty( + name='Implementation', items=implentation_modes, + description='Choose calculation method', + default="3D", update=update_sockets) + + + + def draw_buttons(self, context, layout): + # layout .prop(self, 'box_dimensions', expand=True) + # col = layout.column(align=True) + # titles = ["Min", "Max", "Size"] + # prop = ['min_list', 'max_list', 'size_list'] + # dims = int(self.box_dimensions[0]) + # for i in range(3): + # row = col.row(align=True) + # row.label(text=titles[i]) + # row2 = row.row(align=True) + # for j in range(dims): + # row2 .prop(self, prop[i], index=j, text='XYZ'[j], toggle=True) + pass + + def sv_init(self, context): + son = self.outputs.new + self.inputs.new('SvVerticesSocket', 'Vertices').is_mandatory = True + + son('SvVerticesSocket', 'Vertices') + son('SvStringsSocket', 'Edges') + son('SvStringsSocket', 'Faces') + + self.update_sockets(context) + + # def migrate_from(self, old_node): + # self.box_dimensions = old_node.dimensions + + # def process_data(self, params): + + # verts = params[0] + + # output_mat = self.outputs['Center'].is_linked + # output_mean = self.outputs['Mean'].is_linked + # output_verts = self.outputs['Vertices'].is_linked + # output_limits = any(s.is_linked for s in self.outputs[4:]) + # return bounding_box(verts, + # box_dimensions=self.box_dimensions, + # output_verts=output_verts, + # output_mat=output_mat, + # output_mean=output_mean, + # output_limits=output_limits) + + def process(self): + inputs = self.inputs + Vertices = inputs["Vertices"].sv_get(default=None) + + outputs = self.outputs + if not outputs['Vertices'].is_linked or not all([Vertices,]): + return + + lst_bba_vertices = [] + lst_bba_edges = [] + lst_bba_faces = [] + for verts in Vertices: + #bba = bounding_box_aligned_01(verts) + bba = bounding_box_aligned(verts) + lst_bba_vertices.append( bba[0].tolist() ) + lst_bba_edges.append( [[0,1], [1,2], [2,3], [3,0], [0,4], [1,5], [2,6], [3,7], [4,5], [5,6], [6,7], [7,4]]) + lst_bba_faces.append( [[0,3,2,1], [0,1,5,4], [1,2,6,5], [2,3,7,6], [3,0,4,7], [4,5,6,7]]) + + outputs['Vertices'].sv_set(lst_bba_vertices) + outputs['Edges'].sv_set(lst_bba_edges) + outputs['Faces'].sv_set(lst_bba_faces) + + + +def register(): + bpy.utils.register_class(SvAlignedBBoxNode) + + +def unregister(): + bpy.utils.unregister_class(SvAlignedBBoxNode) From e8a52f17ac5072b6362e057d20d2a0abba45e538 Mon Sep 17 00:00:00 2001 From: satabol Date: Mon, 23 Oct 2023 00:24:43 +0300 Subject: [PATCH 2/8] fix #5030. Orthogonalize evec. --- nodes/analyzer/bbox_aligned.py | 2 +- utils/geom.py | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/nodes/analyzer/bbox_aligned.py b/nodes/analyzer/bbox_aligned.py index 7c42784ef4..d099f64c7e 100644 --- a/nodes/analyzer/bbox_aligned.py +++ b/nodes/analyzer/bbox_aligned.py @@ -26,7 +26,7 @@ from sverchok.utils.nodes_mixins.recursive_nodes import SvRecursiveNode from sverchok.data_structure import dataCorrect, updateNode -from sverchok.utils.geom import bounding_box_aligned, bounding_box_aligned_01 +from sverchok.utils.geom import bounding_box_aligned EDGES = [ (0, 1), (1, 3), (3, 2), (2, 0), # bottom edges diff --git a/utils/geom.py b/utils/geom.py index 33191be15d..770d789204 100644 --- a/utils/geom.py +++ b/utils/geom.py @@ -48,22 +48,37 @@ def bounding_box_aligned(verts): '''res=[[0,0,0], [0,1,0], [1,1,0], [1,0,0], [0,0,1], [0,1,1], [1,1,1], [1,0,1],]; 1-used axis''' # based on "3D Oriented bounding boxes": https://logicatcore.github.io/scratchpad/lidar/sensor-fusion/jupyter/2021/04/20/3D-Oriented-Bounding-Box.html - data = np.vstack(np.array(verts).transpose()) + data = np.vstack(np.array(verts, dtype=np.float64).transpose()) means = np.mean(data, axis=1) cov = np.cov(data) - eval, evec = np.linalg.eig(cov) + eval, evec = np.linalg.eig(cov) # some times evec vectors are not perpendicular each other. What to do for this? + + # make evecs orthogonals each other: + vecs = [[0,1], [1,2], [2,0]] + evec_dots = [abs(np.dot( evec.T[ivect[0]], evec.T[ivect[1]] )) for ivect in vecs] # get dots product vectors each other + evec_dots_sort = np.argsort(evec_dots) + l012 = [0,1,2] + [l012.remove(i) for i in vecs[evec_dots_sort[0]]] + v0 = evec.T[ vecs[evec_dots_sort[0]][0] ] # main vector + v1 = evec.T[ vecs[evec_dots_sort[0]][1] ] # closest by dot product + v2 = evec.T[ l012[0] ] # get last vector + v0_v1_cross = np.cross( v0, v1 ) + v1 = np.cross( v0, v0_v1_cross ) # orthogonal v1 to v0 from v1 source position + if np.dot(v0_v1_cross, v2)<0: # build last vector as orthogonal to v0 and v1 + v2 = - v0_v1_cross + else: + v2 = v0_v1_cross + evec = np.dstack( (v0, v1, v2) )[0] + + centered_data = data - means[:,np.newaxis] - xmin, xmax, ymin, ymax, zmin, zmax = np.min(centered_data[0, :]), np.max(centered_data[0, :]), np.min(centered_data[1, :]), np.max(centered_data[1, :]), np.min(centered_data[2, :]), np.max(centered_data[2, :]) aligned_coords = np.matmul(evec.T, centered_data) xmin, xmax, ymin, ymax, zmin, zmax = np.min(aligned_coords[0, :]), np.max(aligned_coords[0, :]), np.min(aligned_coords[1, :]), np.max(aligned_coords[1, :]), np.min(aligned_coords[2, :]), np.max(aligned_coords[2, :]) rectCoords = lambda x1, y1, z1, x2, y2, z2: np.array([[x1, x1, x2, x2, x1, x1, x2, x2], - [y1, y2, y2, y1, y1, y2, y2, y1], - [z1, z1, z1, z1, z2, z2, z2, z2]]) - - realigned_coords = np.matmul(evec, aligned_coords) - realigned_coords += means[:, np.newaxis] + [y1, y2, y2, y1, y1, y2, y2, y1], + [z1, z1, z1, z1, z2, z2, z2, z2]]) rrc = np.matmul(evec, rectCoords(xmin, ymin, zmin, xmax, ymax, zmax)) rrc += means[:, np.newaxis] From 348a1fc8a373c2bdf2cbfe299bf030436a84147e Mon Sep 17 00:00:00 2001 From: satabol Date: Mon, 23 Oct 2023 01:02:11 +0300 Subject: [PATCH 3/8] fix #5030. Append doc template. --- docs/nodes/analyzer/bbox_aligned.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/nodes/analyzer/bbox_aligned.rst diff --git a/docs/nodes/analyzer/bbox_aligned.rst b/docs/nodes/analyzer/bbox_aligned.rst new file mode 100644 index 0000000000..ccd8504eef --- /dev/null +++ b/docs/nodes/analyzer/bbox_aligned.rst @@ -0,0 +1,15 @@ +Aligned Bounding Box +==================== + +Functionality +------------- + +Generates a special ordered *bounding box* from incoming Vertices. + +Inputs +------ + +**Vertices**, or a nested list of vertices that represent separate objects. + +Parameters +---------- From 271d053c9c83bc1630be334c3c8563b0ea086f91 Mon Sep 17 00:00:00 2001 From: satabol Date: Mon, 23 Oct 2023 01:47:30 +0300 Subject: [PATCH 4/8] fix #5030. Tests troubles. --- nodes/analyzer/bbox_aligned.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes/analyzer/bbox_aligned.py b/nodes/analyzer/bbox_aligned.py index d099f64c7e..a980deb137 100644 --- a/nodes/analyzer/bbox_aligned.py +++ b/nodes/analyzer/bbox_aligned.py @@ -191,7 +191,7 @@ def draw_buttons(self, context, layout): def sv_init(self, context): son = self.outputs.new - self.inputs.new('SvVerticesSocket', 'Vertices').is_mandatory = True + #self.inputs.new('SvVerticesSocket', 'Vertices').is_mandatory = True son('SvVerticesSocket', 'Vertices') son('SvStringsSocket', 'Edges') From 5e80326437844e0467e966604cfe21f6eb7b64d7 Mon Sep 17 00:00:00 2001 From: satabol Date: Mon, 23 Oct 2023 12:47:32 +0300 Subject: [PATCH 5/8] fix #5030. Fix for Tests. --- nodes/analyzer/bbox_aligned.py | 166 +-------------------------------- 1 file changed, 1 insertion(+), 165 deletions(-) diff --git a/nodes/analyzer/bbox_aligned.py b/nodes/analyzer/bbox_aligned.py index a980deb137..2e65ff82d3 100644 --- a/nodes/analyzer/bbox_aligned.py +++ b/nodes/analyzer/bbox_aligned.py @@ -28,112 +28,6 @@ from sverchok.data_structure import dataCorrect, updateNode from sverchok.utils.geom import bounding_box_aligned -EDGES = [ - (0, 1), (1, 3), (3, 2), (2, 0), # bottom edges - (4, 5), (5, 7), (7, 6), (6, 4), # top edges - (0, 4), (1, 5), (2, 6), (3, 7) # sides -] -def generate_matrix(maxmin, dims, to_2d): - center = [(u+v)*.5 for u, v in maxmin[:dims]] - scale = [(u-v) for u, v in maxmin[:dims]] - if to_2d: - center += [0] - scale += [1] - mat = Matrix.Translation(center) - for i, sca in enumerate(scale): - mat[i][i] = sca - return mat - -def generate_mean_np(verts, dims, to_2d): - avr = (np.sum(verts[:, :dims], axis=0)/len(verts)).tolist() - if to_2d: - avr += [0] - return [avr] - -def generate_mean(verts, dims, to_2d): - avr = list(map(sum, zip(*verts))) - avr = [n/len(verts) for n in avr[:dims]] - if to_2d: - avr += [0] - return [avr] - -def bounding_box(verts, - box_dimensions='2D', - output_verts=True, - output_mat=True, - output_mean=True, - output_limits=True): - ''' - verts expects a list of level 3 [[[0,0,0],[1,1,1]..],[]] - returns per sublist: - verts_out: vertices of the bounding box - edges_out: edges of the bounding box - mean_out: mean of all vertcies - mat_out: Matrix that would transform a box of 1 unit into the bbox - *min_vals, Min X, Y and Z of the bounding box - *max_vals, Max X, Y and Z of the bounding box - *size_vals Size X, Y and Z of the bounding box - ''' - verts_out = [] - edges_out = [] - edges = EDGES - - mat_out = [] - mean_out = [] - min_vals = [[], [], []] - max_vals = [[], [], []] - size_vals = [[], [], []] - to_2d = box_dimensions == '2D' - dims = int(box_dimensions[0]) - calc_maxmin = output_mat or output_verts or output_limits - - for vec in verts: - if calc_maxmin: - if isinstance(vec, np.ndarray): - np_vec = vec - else: - np_vec = np.array(vec) - bbox_max = np.amax(np_vec, axis=0) - bbox_min = np.amin(np_vec, axis=0) - maxmin = np.concatenate([bbox_max, bbox_min]).reshape(2,3).T.tolist() - - if output_verts: - out = list(product(*reversed(maxmin))) - v_out = [l[::-1] for l in out[::-1]] - if to_2d: - verts_out.append([[v[0], v[1], 0] for v in v_out[:4]]) - edges = edges[:4] - else: - verts_out.append(v_out) - edges_out.append(edges) - - if output_mat: - mat_out.append(generate_matrix(maxmin, dims, to_2d)) - - if output_mean: - if calc_maxmin: - mean_out.append(generate_mean_np(np_vec, dims, to_2d)) - else: - if isinstance(vec, np.ndarray): - mean_out.append(generate_mean_np(vec, dims, to_2d)) - else: - mean_out.append(generate_mean(vec, dims, to_2d)) - - if output_limits: - for i in range(dims): - min_vals[i].append([maxmin[i][1]]) - max_vals[i].append([maxmin[i][0]]) - size_vals[i].append([maxmin[i][0] - maxmin[i][1]]) - - return (verts_out, - edges_out, - mean_out, - mat_out, - *min_vals, - *max_vals, - *size_vals) - - class SvAlignedBBoxNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): """ Triggers: Bbox 2D or 3D @@ -145,53 +39,14 @@ class SvAlignedBBoxNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): sv_icon = 'SV_BOUNDING_BOX' def update_sockets(self, context): - bools = [self.min_list, self.max_list, self.size_list] - dims = int(self.box_dimensions[0]) - for i in range(3): - for j in range(3): - out_index = 4 + j + 3*i - hidden = self.outputs[out_index].hide_safe - if bools[i][j] and j < dims: - if hidden: - self.outputs[out_index].hide_safe = False - else: - self.outputs[out_index].hide_safe = True - - updateNode(self, context) - - min_list: BoolVectorProperty( - name='Min', description="Show Minimum values sockets", size=3, update=update_sockets) - max_list: BoolVectorProperty( - name='Max', description="Show Maximum values sockets", size=3, update=update_sockets) - size_list: BoolVectorProperty( - name='Size', description="Show Size values sockets", size=3, update=update_sockets) - implentation_modes = [ - ("2D", "2D", "Outputs Rectangle over XY plane", 0), - ("3D", "3D", "Outputs standard bounding box", 1)] - box_dimensions: EnumProperty( - name='Implementation', items=implentation_modes, - description='Choose calculation method', - default="3D", update=update_sockets) - + updateNode(self, context) def draw_buttons(self, context, layout): - # layout .prop(self, 'box_dimensions', expand=True) - # col = layout.column(align=True) - # titles = ["Min", "Max", "Size"] - # prop = ['min_list', 'max_list', 'size_list'] - # dims = int(self.box_dimensions[0]) - # for i in range(3): - # row = col.row(align=True) - # row.label(text=titles[i]) - # row2 = row.row(align=True) - # for j in range(dims): - # row2 .prop(self, prop[i], index=j, text='XYZ'[j], toggle=True) pass def sv_init(self, context): son = self.outputs.new - #self.inputs.new('SvVerticesSocket', 'Vertices').is_mandatory = True son('SvVerticesSocket', 'Vertices') son('SvStringsSocket', 'Edges') @@ -199,24 +54,6 @@ def sv_init(self, context): self.update_sockets(context) - # def migrate_from(self, old_node): - # self.box_dimensions = old_node.dimensions - - # def process_data(self, params): - - # verts = params[0] - - # output_mat = self.outputs['Center'].is_linked - # output_mean = self.outputs['Mean'].is_linked - # output_verts = self.outputs['Vertices'].is_linked - # output_limits = any(s.is_linked for s in self.outputs[4:]) - # return bounding_box(verts, - # box_dimensions=self.box_dimensions, - # output_verts=output_verts, - # output_mat=output_mat, - # output_mean=output_mean, - # output_limits=output_limits) - def process(self): inputs = self.inputs Vertices = inputs["Vertices"].sv_get(default=None) @@ -229,7 +66,6 @@ def process(self): lst_bba_edges = [] lst_bba_faces = [] for verts in Vertices: - #bba = bounding_box_aligned_01(verts) bba = bounding_box_aligned(verts) lst_bba_vertices.append( bba[0].tolist() ) lst_bba_edges.append( [[0,1], [1,2], [2,3], [3,0], [0,4], [1,5], [2,6], [3,7], [4,5], [5,6], [6,7], [7,4]]) From 1fafdbf8e76d139facd133b0dfb59d466939d921 Mon Sep 17 00:00:00 2001 From: satabol Date: Wed, 25 Oct 2023 21:58:19 +0300 Subject: [PATCH 6/8] fix #5030. Append input socket matrixes and factor to mix input matrixes and evec matrixes --- nodes/analyzer/bbox_aligned.py | 222 ++++++++++++++++++++++++++++++- nodes/spatial/delaunay_3d_mk2.py | 2 +- utils/geom.py | 90 +++++++++---- utils/voronoi3d.py | 2 +- 4 files changed, 284 insertions(+), 32 deletions(-) diff --git a/nodes/analyzer/bbox_aligned.py b/nodes/analyzer/bbox_aligned.py index 2e65ff82d3..00105aeccb 100644 --- a/nodes/analyzer/bbox_aligned.py +++ b/nodes/analyzer/bbox_aligned.py @@ -19,13 +19,15 @@ from itertools import product import numpy as np import bpy -from bpy.props import BoolVectorProperty, EnumProperty +from bpy.props import BoolVectorProperty, EnumProperty, BoolProperty, FloatProperty from mathutils import Matrix from sverchok.node_tree import SverchCustomTreeNode from sverchok.utils.nodes_mixins.recursive_nodes import SvRecursiveNode +from sverchok.utils.sv_mesh_utils import mesh_join +from sverchok.utils.modules.matrix_utils import matrix_apply_np -from sverchok.data_structure import dataCorrect, updateNode +from sverchok.data_structure import dataCorrect, updateNode, zip_long_repeat from sverchok.utils.geom import bounding_box_aligned class SvAlignedBBoxNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): @@ -38,25 +40,56 @@ class SvAlignedBBoxNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): bl_icon = 'SHADING_BBOX' sv_icon = 'SV_BOUNDING_BOX' + mesh_join : BoolProperty( + name = "merge", + description = "If checked, join mesh elements into one object", + default = False, + update = updateNode) + + factor : FloatProperty( + name = "Factor", + description = "Matrix interpolation", + default = 1.0, + min=0.0, max=1.0, + update = updateNode) + + def update_sockets(self, context): updateNode(self, context) def draw_buttons(self, context, layout): + layout.row().prop(self, 'mesh_join') pass def sv_init(self, context): son = self.outputs.new + self.inputs.new('SvVerticesSocket', 'Vertices') + #self.inputs.new('SvStringsSocket', 'Faces') + self.inputs.new('SvMatrixSocket', 'Matrix') + self.inputs.new('SvStringsSocket', 'Factor').prop_name = 'factor' son('SvVerticesSocket', 'Vertices') + #son('SvVerticesSocket', 'Vertices_tests') son('SvStringsSocket', 'Edges') son('SvStringsSocket', 'Faces') + son('SvMatrixSocket', "Matrix") + + son('SvStringsSocket', 'Length') + son('SvStringsSocket', 'Width') + son('SvStringsSocket', 'Height') + self.update_sockets(context) def process(self): inputs = self.inputs Vertices = inputs["Vertices"].sv_get(default=None) + Faces = [[]] #inputs["Faces"].sv_get(default=[[]]) + Matrixes = inputs["Matrix"].sv_get(default=-1) + if Matrixes==-1: + Matrixes = [None] + Factors = inputs["Factor"].sv_get() outputs = self.outputs if not outputs['Vertices'].is_linked or not all([Vertices,]): @@ -65,15 +98,190 @@ def process(self): lst_bba_vertices = [] lst_bba_edges = [] lst_bba_faces = [] - for verts in Vertices: - bba = bounding_box_aligned(verts) - lst_bba_vertices.append( bba[0].tolist() ) - lst_bba_edges.append( [[0,1], [1,2], [2,3], [3,0], [0,4], [1,5], [2,6], [3,7], [4,5], [5,6], [6,7], [7,4]]) - lst_bba_faces.append( [[0,3,2,1], [0,1,5,4], [1,2,6,5], [2,3,7,6], [3,0,4,7], [4,5,6,7]]) + lst_bba_matrix = [] + lst_bba_Length = [] + lst_bba_Width = [] + lst_bba_Height = [] + m = None + for verts, faces, matrix, factor in zip_long_repeat(Vertices, Faces, Matrixes, Factors[0]): + # align verts to Oxy plane. Find first vectors that are not complanar and use then to align to Oxy + + + + np_verts = np.array(verts) + + evec_external = None + # T, R, S = None, None, None + # if matrix is not None: + # T, R, S = matrix.decompose() + # R = np.array(R.to_matrix()) + # if faces: + # # calc faces normals: + # edges_vectors = np.empty((0,2), dtype=np.int32) + # for face in faces: + # np_faces = np.array(face) + # edges = np.dstack( (np_faces, np.roll(np_faces,1) ) ) + # edges_vectors = np.vstack( (edges_vectors, edges[0]) ) if edges_vectors.size else edges[0] + # #edges_vectors.extend( edges ) + # unique_edges_indexes = np.unique( np.sort( edges_vectors ).reshape(-1,2), axis=0 ) + # edges_vectors = np_verts[ unique_edges_indexes ][:,1] - np_verts[ unique_edges_indexes ][:,0] + # edges_vectors_unique, edges_vectors_count = np.unique( edges_vectors, axis=0, return_counts=True) + # # np_unique_vectors = np.empty((0,3), dtype=np.float64) + # # np_unique_vectors = np.vstack( (np_unique_vectors, edges_vectors_unique[0]) ) + # np_unique_vectors = np.array( [ edges_vectors[0] ]) + # # np_unique_counts = np.empty((0), dtype=np.int32) + # # np_unique_counts = np.dstack( (np_unique_counts, edges_vectors_count[0]) ) + # np_counts = np.array( 0 ) + # for i, vec in enumerate(edges_vectors[1:]): # skip first element. It is used + # exists_vectors = ( abs(np.linalg.norm(np.cross( np_unique_vectors, np.repeat([vec], np_unique_vectors.shape[0], axis=0)), axis=1))>1e-6 ) + # #print(f'{i}. exists_vectors={exists_vectors.tolist()}') + # if np.all(exists_vectors): + # np_unique_vectors = np.vstack( (np_unique_vectors, vec) ) + # np_counts = np.dstack( ( np_counts, 1) ) + # else: + # np_counts = np_counts+np.invert(exists_vectors)*1 + + # max_vectors = np.argsort( np_counts ).reshape(-1) + # evec_external = np_unique_vectors[[max_vectors[-3:]]][0] + # #evec_external = evec_external/np.linalg.norm(evec_external, axis=0) + # evec_external = [ex/np.linalg.norm(ex, axis=0) for ex in evec_external] + # #print(f'max_vectors={max_vectors.tolist()}, {np_counts.tolist()}') + + # X = evec_external[-1] + # Y = evec_external[-2] + # Z = np.cross(X,Y) + # Y = np.cross(Z,X) + # X = X/np.linalg.norm(X) + # Y = Y/np.linalg.norm(Y) + # Z = Z/np.linalg.norm(Z) + # m = np.matrix( + # [[X[0], Y[0], Z[0], 0.0], + # [X[1], Y[1], Z[1], 0.0], + # [X[2], Y[2], Z[2], 0.0], + # [ 0.0, 0.0, 0.0, 1.0]] ) + # np_verts = matrix_apply_np(np_verts, np.linalg.inv(m) ) + # pass + # else: + # vb0 = np_verts[0] + # vbc0 = None + # vbc1 = None + # for v in np_verts: + # if np.linalg.norm(vb0-v)>1e-5: + # vbc0 = v - vb0 + # break + + # if vbc0 is not None: + # for v in np_verts: + # if abs(np.linalg.norm(np.cross(vb0-v, vbc0)))>1e-5: + # vbc1 = v - vb0 + # break + + # if vbc0 is not None and vbc1 is not None: + # # vbc0 and vbc1 has to reorient all vertices to Oxy (vbc0 to Ox) + # X = vbc0 + # Y = vbc1 + # Z = np.cross(X,Y) + # Y = np.cross(Z,X) + # X = X/np.linalg.norm(X) + # Y = Y/np.linalg.norm(Y) + # Z = Z/np.linalg.norm(Z) + # m = np.matrix( + # [[X[0], Y[0], Z[0], 0.0], + # [X[1], Y[1], Z[1], 0.0], + # [X[2], Y[2], Z[2], 0.0], + # [ 0.0, 0.0, 0.0, 1.0]] ) + # np_verts = matrix_apply_np(np_verts, np.linalg.inv(m) ) + # # np_verts_min = np.min(np_verts, axis=0) + # # np_verts_max = np.max(np_verts, axis=0) + # # np_verts = np.vstack( (np_verts, + # # [np_verts_min[2]-.01, 0.0, 0.0], + # # [np_verts_max[2]+.01, 0.0, 0.0], + # # [0.0, np_verts_min[2]-.02, 0.0], + # # [0.0, np_verts_max[2]+.02, 0.0], + # # [0.0, 0.0, np_verts_min[2]-.03], + # # [0.0, 0.0, np_verts_max[2]+.03], + # # ) ) + # pass + + # if m is not None: + # x_min, x_max = np.min(np_verts[:,0]), np.max(np_verts[:,0]) + # y_min, y_max = np.min(np_verts[:,1]), np.max(np_verts[:,1]) + # z_min, z_max = np.min(np_verts[:,2]), np.max(np_verts[:,2]) + # x_size = x_max - x_min + # y_size = y_max - y_min + # z_size = z_max - z_min + + # if abs(x_size-z_size)<1e-6 and abs(y_size-z_size)<1e-6: + # # object is symmetrical all axis + # print(f'symmetrical 3d: {x_size}, {y_size}, {z_size}') + # np_verts = np.vstack( (np_verts, + # [x_min-.01, 0.0, 0.0], + # [x_max+.01, 0.0, 0.0], + # [0.0, y_min-.02, 0.0], + # [0.0, y_max+.02, 0.0], + # [0.0, 0.0, z_min-.03], + # [0.0, 0.0, z_max+.03], + # ) ) + # pass + # elif abs(x_size-z_size)<1e-6 or abs(y_size-z_size)<1e-6 or abs(x_size-y_size)<1e-6: + # print(f'symmetrical 2d: {x_size}, {y_size}, {z_size}') + # if abs(x_size-z_size)<1e-6: + # np_verts = np.vstack( (np_verts, + # [x_min-.01, 0.0, 0.0], + # [x_max+.01, 0.0, 0.0], + # [0.0, 0.0, z_min-.03], + # [0.0, 0.0, z_max+.03], + # ) ) + # pass + # elif abs(y_size-z_size)<1e-6: + # np_verts = np.vstack( (np_verts, + # [0.0, y_min-.02, 0.0], + # [0.0, y_max+.02, 0.0], + # [0.0, 0.0, z_min-.03], + # [0.0, 0.0, z_max+.03], + # ) ) + # pass + # else: + # np_verts = np.vstack( (np_verts, + # [x_min-.01, 0.0, 0.0], + # [x_max+.01, 0.0, 0.0], + # [0.0, y_min-.02, 0.0], + # [0.0, y_max+.02, 0.0], + # ) ) + # pass + # else: + # print(f'symmetrical no: {x_size}, {y_size}, {z_size}') + # pass + + + bba, mat, bbox_size = bounding_box_aligned(np_verts, evec_external=matrix, factor=factor) + #print(f'bbox_size={bbox_size}') + # if m is not None: + # bba = matrix_apply_np(bba, m ) + # mat = m + # pass + lst_bba_vertices.append( bba.tolist() ) + lst_bba_edges .append( [[0,1], [1,2], [2,3], [3,0], [0,4], [1,5], [2,6], [3,7], [4,5], [5,6], [6,7], [7,4]]) + lst_bba_faces .append( [[0,3,2,1], [0,1,5,4], [1,2,6,5], [2,3,7,6], [3,0,4,7], [4,5,6,7]]) + lst_bba_matrix.append( mat ) + lst_bba_Length.append( bbox_size[0] ) + lst_bba_Width .append( bbox_size[1] ) + lst_bba_Height.append( bbox_size[2] ) + + if self.mesh_join: + lst_bba_vertices, lst_bba_edges, lst_bba_faces = mesh_join(lst_bba_vertices, lst_bba_edges, lst_bba_faces) + lst_bba_vertices, lst_bba_edges, lst_bba_faces = [lst_bba_vertices], [lst_bba_edges], [lst_bba_faces] + # calc length, width, height + pass outputs['Vertices'].sv_set(lst_bba_vertices) + #outputs['Vertices_tests'].sv_set([np_verts.tolist()]) outputs['Edges'].sv_set(lst_bba_edges) outputs['Faces'].sv_set(lst_bba_faces) + outputs['Matrix'].sv_set(lst_bba_matrix) + outputs['Length'].sv_set(lst_bba_Length) + outputs['Width'].sv_set(lst_bba_Width) + outputs['Height'].sv_set(lst_bba_Height) diff --git a/nodes/spatial/delaunay_3d_mk2.py b/nodes/spatial/delaunay_3d_mk2.py index edffb4c67a..7d945b9c97 100644 --- a/nodes/spatial/delaunay_3d_mk2.py +++ b/nodes/spatial/delaunay_3d_mk2.py @@ -150,7 +150,7 @@ def get_delaunay_simplices(vertices, threshold): ''' # get shape dimension (dim): - abbox = bounding_box_aligned(vertices)[0] + abbox, *_ = bounding_box_aligned(vertices) oX_abb_vec = abbox[1]-abbox[0] oX_abb_size = abs(np.linalg.norm(oX_abb_vec)) oY_abb_vec = abbox[3]-abbox[0] diff --git a/utils/geom.py b/utils/geom.py index 770d789204..cf93fc2daf 100644 --- a/utils/geom.py +++ b/utils/geom.py @@ -45,45 +45,89 @@ from sverchok.dependencies import numba # not strictly needed i think... from sverchok.utils.decorators_compilation import njit -def bounding_box_aligned(verts): +def bounding_box_aligned(verts, evec_external=None, factor=1.0): '''res=[[0,0,0], [0,1,0], [1,1,0], [1,0,0], [0,0,1], [0,1,1], [1,1,1], [1,0,1],]; 1-used axis''' + + def realign_evec(evec): + # make evecs orthogonals each other: + vecs = [[0,1,2], [1,2,0], [2,0,1]] + evec_dots = np.array( [abs(np.dot( evec.T[ivect[0]], evec.T[ivect[1]] )) for ivect in vecs] ) # get dots product vectors each other + if np.all(evec_dots<1e-8): # if all vectors are very close to orthonormals each other. May be replased by a future algorithm + evec_dots_sort = [0] + else: + evec_dots_sort = np.argsort(evec_dots) + #print(f'sort: {vecs[evec_dots_sort[0]]}') + v0 = evec.T[ vecs[evec_dots_sort[0]][0] ] # main vector + v1 = evec.T[ vecs[evec_dots_sort[0]][1] ] # closest by dot product + v2 = evec.T[ vecs[evec_dots_sort[0]][2] ] # get last vector + v0_v1_cross = np.cross( v0, v1 ) + v1 = np.cross( v0, v0_v1_cross ) # orthogonal v1 to v0 from v1 source position + if np.dot(v0_v1_cross, v2)<0: # build last vector as orthogonal to v0 and v1 + v2 = - v0_v1_cross + else: + v2 = v0_v1_cross + res = np.dstack( (v0, v1, v2) )[0] + return res + # based on "3D Oriented bounding boxes": https://logicatcore.github.io/scratchpad/lidar/sensor-fusion/jupyter/2021/04/20/3D-Oriented-Bounding-Box.html data = np.vstack(np.array(verts, dtype=np.float64).transpose()) means = np.mean(data, axis=1) - cov = np.cov(data) - eval, evec = np.linalg.eig(cov) # some times evec vectors are not perpendicular each other. What to do for this? - - # make evecs orthogonals each other: - vecs = [[0,1], [1,2], [2,0]] - evec_dots = [abs(np.dot( evec.T[ivect[0]], evec.T[ivect[1]] )) for ivect in vecs] # get dots product vectors each other - evec_dots_sort = np.argsort(evec_dots) - l012 = [0,1,2] - [l012.remove(i) for i in vecs[evec_dots_sort[0]]] - v0 = evec.T[ vecs[evec_dots_sort[0]][0] ] # main vector - v1 = evec.T[ vecs[evec_dots_sort[0]][1] ] # closest by dot product - v2 = evec.T[ l012[0] ] # get last vector - v0_v1_cross = np.cross( v0, v1 ) - v1 = np.cross( v0, v0_v1_cross ) # orthogonal v1 to v0 from v1 source position - if np.dot(v0_v1_cross, v2)<0: # build last vector as orthogonal to v0 and v1 - v2 = - v0_v1_cross + if evec_external is not None: + T, R, S = evec_external.decompose() + if factor==1.0: + evec_target = np.array(R.to_matrix()) + pass + else: + cov = np.cov(data) + evalue, evec = np.linalg.eig(cov) # some times evec vectors are not perpendicular each other. What to do for this? + evec = realign_evec(evec) + evec = Matrix(evec).lerp( R.to_matrix(), factor) + evec_target = np.array(evec) else: - v2 = v0_v1_cross - evec = np.dstack( (v0, v1, v2) )[0] - + cov = np.cov(data) + evalue, evec = np.linalg.eig(cov) # some times evec vectors are not perpendicular each other. What to do for this? + + # # make evecs orthogonals each other: + # vecs = [[0,1,2], [1,2,0], [2,0,1]] + # evec_dots = np.array( [abs(np.dot( evec.T[ivect[0]], evec.T[ivect[1]] )) for ivect in vecs] ) # get dots product vectors each other + # if np.all(evec_dots<1e-8): # if all vectors are very close to orthonormals each other. May be replased by a future algorithm + # evec_dots_sort = [0] + # else: + # evec_dots_sort = np.argsort(evec_dots) + # #print(f'sort: {vecs[evec_dots_sort[0]]}') + # v0 = evec.T[ vecs[evec_dots_sort[0]][0] ] # main vector + # v1 = evec.T[ vecs[evec_dots_sort[0]][1] ] # closest by dot product + # v2 = evec.T[ vecs[evec_dots_sort[0]][2] ] # get last vector + # v0_v1_cross = np.cross( v0, v1 ) + # v1 = np.cross( v0, v0_v1_cross ) # orthogonal v1 to v0 from v1 source position + # if np.dot(v0_v1_cross, v2)<0: # build last vector as orthogonal to v0 and v1 + # v2 = - v0_v1_cross + # else: + # v2 = v0_v1_cross + # evec_target = np.dstack( (v0, v1, v2) )[0] + evec_target = realign_evec(evec) centered_data = data - means[:,np.newaxis] - aligned_coords = np.matmul(evec.T, centered_data) + aligned_coords = np.matmul(evec_target.T, centered_data) xmin, xmax, ymin, ymax, zmin, zmax = np.min(aligned_coords[0, :]), np.max(aligned_coords[0, :]), np.min(aligned_coords[1, :]), np.max(aligned_coords[1, :]), np.min(aligned_coords[2, :]), np.max(aligned_coords[2, :]) + abbox_size = [ xmax-xmin, ymax-ymin, zmax-zmin] + abbox_center = [ (xmax+xmin)/2, (ymax+ymin)/2, (zmax+zmin)/2 ] rectCoords = lambda x1, y1, z1, x2, y2, z2: np.array([[x1, x1, x2, x2, x1, x1, x2, x2], [y1, y2, y2, y1, y1, y2, y2, y1], [z1, z1, z1, z1, z2, z2, z2, z2]]) - rrc = np.matmul(evec, rectCoords(xmin, ymin, zmin, xmax, ymax, zmax)) + rrc = np.matmul(evec_target, rectCoords(xmin, ymin, zmin, xmax, ymax, zmax)) rrc += means[:, np.newaxis] rrc = rrc.transpose() - return tuple([rrc]) + abbox_center = np.mean( rrc, axis=0 ) + #mat = mathutils.Matrix.LocRotScale( [ abbox_center[i] for i in vecs[evec_dots_sort[0]] ], Matrix(evec_new).to_euler(), [ abbox_size[i] for i in vecs[evec_dots_sort[0]] ] ) + # mat = mathutils.Matrix.LocRotScale( [ abbox_center[i] for i in vecs[evec_dots_sort[0]] ], Matrix(evec_new).to_euler([ 'XYZ'[i] for i in vecs[evec_dots_sort[0]] ]), [ abbox_size[i] for i in vecs[evec_dots_sort[0]] ] ) + mat_scale = Matrix() + mat_scale[0][0], mat_scale[1][1], mat_scale[2][2] = abbox_size + mat = mathutils.Matrix.Translation(abbox_center) @ Matrix(evec_target).to_euler().to_matrix().to_4x4() @ mat_scale + return rrc, mat, abbox_size # verts, matrix (not use for a while) identity_matrix = Matrix() diff --git a/utils/voronoi3d.py b/utils/voronoi3d.py index 2dbea0df89..5e1c8cf18e 100644 --- a/utils/voronoi3d.py +++ b/utils/voronoi3d.py @@ -415,7 +415,7 @@ def cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing, center_of_mas # calc center of mass. Using for sort of bisect planes for sites. center_of_mass = np.average( verts, axis=0 ) # using for precalc unneeded bisects - bbox_aligned = bounding_box_aligned(verts)[0] + bbox_aligned, *_ = bounding_box_aligned(verts) start_mesh = bmesh_from_pydata(verts, [], faces, normal_update=False) used_sites_idx = [] From 4503ccee591de6ccf65682599507161098310cfa Mon Sep 17 00:00:00 2001 From: satabol Date: Thu, 26 Oct 2023 22:48:57 +0300 Subject: [PATCH 7/8] fix #5030. Append docs to new node Analyzer Aligned Bounding Box. --- docs/nodes/analyzer/bbox_aligned.rst | 53 ++++++++- nodes/analyzer/bbox_aligned.py | 167 +-------------------------- utils/geom.py | 32 ++--- 3 files changed, 65 insertions(+), 187 deletions(-) diff --git a/docs/nodes/analyzer/bbox_aligned.rst b/docs/nodes/analyzer/bbox_aligned.rst index ccd8504eef..8e8d41b46a 100644 --- a/docs/nodes/analyzer/bbox_aligned.rst +++ b/docs/nodes/analyzer/bbox_aligned.rst @@ -1,15 +1,64 @@ Aligned Bounding Box ==================== +.. image:: https://github.com/nortikin/sverchok/assets/14288520/9361e542-2804-49ff-8d5e-9b1137f2a102 + :target: https://github.com/nortikin/sverchok/assets/14288520/9361e542-2804-49ff-8d5e-9b1137f2a102 + Functionality ------------- -Generates a special ordered *bounding box* from incoming Vertices. +Generates an *aligned bounding box* from incoming Vertices. + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/e7db92a4-68df-4632-aba9-1555fd36ebb4 + :target: https://github.com/nortikin/sverchok/assets/14288520/e7db92a4-68df-4632-aba9-1555fd36ebb4 Inputs ------ -**Vertices**, or a nested list of vertices that represent separate objects. +- **Vertices** - or a nested list of vertices that represent separate objects. +- **Matrix** - Matrix to align bounding box to (Rotation only. Scale and translation are not used). +- **Factor** - Interpolation between calculated aligned bounding box and bounding box position than set by **Matrix**. + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/f5fea1b7-3f48-4065-8f73-e24b516b5f02 + :target: https://github.com/nortikin/sverchok/assets/14288520/f5fea1b7-3f48-4065-8f73-e24b516b5f02 Parameters ---------- + +- **marge** - If bbox calculated for several meshes then its merge into one mesh. Output sockets for Matrix, Length, Width and Height has unmerged params. + +Output +------ + +- **Vertices** - Vertices of aligned bounding bpxes +- **Edges** - Edges of aligned bounding bpxes +- **Faces** - Faces of aligned bounding bpxes +- **Matrix** - Result matrix of calculated bounding box +- **Length**, **Width**, **Height** - Size of aligned bounding box. Axises can be changed unpredictable. + +Examples +-------- + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/e46277f3-74b9-4432-b58f-6d4a56c8ca12 + :target: https://github.com/nortikin/sverchok/assets/14288520/e46277f3-74b9-4432-b58f-6d4a56c8ca12 + +* Generator-> :doc:`Cricket ` +* Matrix-> :doc:`Matrix In ` +* Viz-> :doc:`Viewer Draw ` + +-------- + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/84588685-1360-4029-b8de-5346008b982b + :target: https://github.com/nortikin/sverchok/assets/14288520/84588685-1360-4029-b8de-5346008b982b + +* Spatial-> :doc:`Populate Mesh ` +* Spatial-> :doc:`Voronoi on Mesh ` +* Viz-> :doc:`Viewer Draw ` +* Scene-> :doc:`Get Objects Data ` + +.. raw:: html + + \ No newline at end of file diff --git a/nodes/analyzer/bbox_aligned.py b/nodes/analyzer/bbox_aligned.py index 00105aeccb..35eee5a9b9 100644 --- a/nodes/analyzer/bbox_aligned.py +++ b/nodes/analyzer/bbox_aligned.py @@ -42,13 +42,13 @@ class SvAlignedBBoxNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): mesh_join : BoolProperty( name = "merge", - description = "If checked, join mesh elements into one object", + description = "If checked then join multiple mesh elements into one object", default = False, update = updateNode) factor : FloatProperty( name = "Factor", - description = "Matrix interpolation", + description = "Matrix interpolation to automatic calculated bounding box", default = 1.0, min=0.0, max=1.0, update = updateNode) @@ -65,12 +65,10 @@ def draw_buttons(self, context, layout): def sv_init(self, context): son = self.outputs.new self.inputs.new('SvVerticesSocket', 'Vertices') - #self.inputs.new('SvStringsSocket', 'Faces') self.inputs.new('SvMatrixSocket', 'Matrix') self.inputs.new('SvStringsSocket', 'Factor').prop_name = 'factor' son('SvVerticesSocket', 'Vertices') - #son('SvVerticesSocket', 'Vertices_tests') son('SvStringsSocket', 'Edges') son('SvStringsSocket', 'Faces') son('SvMatrixSocket', "Matrix") @@ -85,7 +83,6 @@ def sv_init(self, context): def process(self): inputs = self.inputs Vertices = inputs["Vertices"].sv_get(default=None) - Faces = [[]] #inputs["Faces"].sv_get(default=[[]]) Matrixes = inputs["Matrix"].sv_get(default=-1) if Matrixes==-1: Matrixes = [None] @@ -102,164 +99,9 @@ def process(self): lst_bba_Length = [] lst_bba_Width = [] lst_bba_Height = [] - m = None - for verts, faces, matrix, factor in zip_long_repeat(Vertices, Faces, Matrixes, Factors[0]): - # align verts to Oxy plane. Find first vectors that are not complanar and use then to align to Oxy - - - + for verts, matrix, factor in zip_long_repeat(Vertices, Matrixes, Factors[0]): np_verts = np.array(verts) - - evec_external = None - # T, R, S = None, None, None - # if matrix is not None: - # T, R, S = matrix.decompose() - # R = np.array(R.to_matrix()) - # if faces: - # # calc faces normals: - # edges_vectors = np.empty((0,2), dtype=np.int32) - # for face in faces: - # np_faces = np.array(face) - # edges = np.dstack( (np_faces, np.roll(np_faces,1) ) ) - # edges_vectors = np.vstack( (edges_vectors, edges[0]) ) if edges_vectors.size else edges[0] - # #edges_vectors.extend( edges ) - # unique_edges_indexes = np.unique( np.sort( edges_vectors ).reshape(-1,2), axis=0 ) - # edges_vectors = np_verts[ unique_edges_indexes ][:,1] - np_verts[ unique_edges_indexes ][:,0] - # edges_vectors_unique, edges_vectors_count = np.unique( edges_vectors, axis=0, return_counts=True) - # # np_unique_vectors = np.empty((0,3), dtype=np.float64) - # # np_unique_vectors = np.vstack( (np_unique_vectors, edges_vectors_unique[0]) ) - # np_unique_vectors = np.array( [ edges_vectors[0] ]) - # # np_unique_counts = np.empty((0), dtype=np.int32) - # # np_unique_counts = np.dstack( (np_unique_counts, edges_vectors_count[0]) ) - # np_counts = np.array( 0 ) - # for i, vec in enumerate(edges_vectors[1:]): # skip first element. It is used - # exists_vectors = ( abs(np.linalg.norm(np.cross( np_unique_vectors, np.repeat([vec], np_unique_vectors.shape[0], axis=0)), axis=1))>1e-6 ) - # #print(f'{i}. exists_vectors={exists_vectors.tolist()}') - # if np.all(exists_vectors): - # np_unique_vectors = np.vstack( (np_unique_vectors, vec) ) - # np_counts = np.dstack( ( np_counts, 1) ) - # else: - # np_counts = np_counts+np.invert(exists_vectors)*1 - - # max_vectors = np.argsort( np_counts ).reshape(-1) - # evec_external = np_unique_vectors[[max_vectors[-3:]]][0] - # #evec_external = evec_external/np.linalg.norm(evec_external, axis=0) - # evec_external = [ex/np.linalg.norm(ex, axis=0) for ex in evec_external] - # #print(f'max_vectors={max_vectors.tolist()}, {np_counts.tolist()}') - - # X = evec_external[-1] - # Y = evec_external[-2] - # Z = np.cross(X,Y) - # Y = np.cross(Z,X) - # X = X/np.linalg.norm(X) - # Y = Y/np.linalg.norm(Y) - # Z = Z/np.linalg.norm(Z) - # m = np.matrix( - # [[X[0], Y[0], Z[0], 0.0], - # [X[1], Y[1], Z[1], 0.0], - # [X[2], Y[2], Z[2], 0.0], - # [ 0.0, 0.0, 0.0, 1.0]] ) - # np_verts = matrix_apply_np(np_verts, np.linalg.inv(m) ) - # pass - # else: - # vb0 = np_verts[0] - # vbc0 = None - # vbc1 = None - # for v in np_verts: - # if np.linalg.norm(vb0-v)>1e-5: - # vbc0 = v - vb0 - # break - - # if vbc0 is not None: - # for v in np_verts: - # if abs(np.linalg.norm(np.cross(vb0-v, vbc0)))>1e-5: - # vbc1 = v - vb0 - # break - - # if vbc0 is not None and vbc1 is not None: - # # vbc0 and vbc1 has to reorient all vertices to Oxy (vbc0 to Ox) - # X = vbc0 - # Y = vbc1 - # Z = np.cross(X,Y) - # Y = np.cross(Z,X) - # X = X/np.linalg.norm(X) - # Y = Y/np.linalg.norm(Y) - # Z = Z/np.linalg.norm(Z) - # m = np.matrix( - # [[X[0], Y[0], Z[0], 0.0], - # [X[1], Y[1], Z[1], 0.0], - # [X[2], Y[2], Z[2], 0.0], - # [ 0.0, 0.0, 0.0, 1.0]] ) - # np_verts = matrix_apply_np(np_verts, np.linalg.inv(m) ) - # # np_verts_min = np.min(np_verts, axis=0) - # # np_verts_max = np.max(np_verts, axis=0) - # # np_verts = np.vstack( (np_verts, - # # [np_verts_min[2]-.01, 0.0, 0.0], - # # [np_verts_max[2]+.01, 0.0, 0.0], - # # [0.0, np_verts_min[2]-.02, 0.0], - # # [0.0, np_verts_max[2]+.02, 0.0], - # # [0.0, 0.0, np_verts_min[2]-.03], - # # [0.0, 0.0, np_verts_max[2]+.03], - # # ) ) - # pass - - # if m is not None: - # x_min, x_max = np.min(np_verts[:,0]), np.max(np_verts[:,0]) - # y_min, y_max = np.min(np_verts[:,1]), np.max(np_verts[:,1]) - # z_min, z_max = np.min(np_verts[:,2]), np.max(np_verts[:,2]) - # x_size = x_max - x_min - # y_size = y_max - y_min - # z_size = z_max - z_min - - # if abs(x_size-z_size)<1e-6 and abs(y_size-z_size)<1e-6: - # # object is symmetrical all axis - # print(f'symmetrical 3d: {x_size}, {y_size}, {z_size}') - # np_verts = np.vstack( (np_verts, - # [x_min-.01, 0.0, 0.0], - # [x_max+.01, 0.0, 0.0], - # [0.0, y_min-.02, 0.0], - # [0.0, y_max+.02, 0.0], - # [0.0, 0.0, z_min-.03], - # [0.0, 0.0, z_max+.03], - # ) ) - # pass - # elif abs(x_size-z_size)<1e-6 or abs(y_size-z_size)<1e-6 or abs(x_size-y_size)<1e-6: - # print(f'symmetrical 2d: {x_size}, {y_size}, {z_size}') - # if abs(x_size-z_size)<1e-6: - # np_verts = np.vstack( (np_verts, - # [x_min-.01, 0.0, 0.0], - # [x_max+.01, 0.0, 0.0], - # [0.0, 0.0, z_min-.03], - # [0.0, 0.0, z_max+.03], - # ) ) - # pass - # elif abs(y_size-z_size)<1e-6: - # np_verts = np.vstack( (np_verts, - # [0.0, y_min-.02, 0.0], - # [0.0, y_max+.02, 0.0], - # [0.0, 0.0, z_min-.03], - # [0.0, 0.0, z_max+.03], - # ) ) - # pass - # else: - # np_verts = np.vstack( (np_verts, - # [x_min-.01, 0.0, 0.0], - # [x_max+.01, 0.0, 0.0], - # [0.0, y_min-.02, 0.0], - # [0.0, y_max+.02, 0.0], - # ) ) - # pass - # else: - # print(f'symmetrical no: {x_size}, {y_size}, {z_size}') - # pass - - bba, mat, bbox_size = bounding_box_aligned(np_verts, evec_external=matrix, factor=factor) - #print(f'bbox_size={bbox_size}') - # if m is not None: - # bba = matrix_apply_np(bba, m ) - # mat = m - # pass lst_bba_vertices.append( bba.tolist() ) lst_bba_edges .append( [[0,1], [1,2], [2,3], [3,0], [0,4], [1,5], [2,6], [3,7], [4,5], [5,6], [6,7], [7,4]]) lst_bba_faces .append( [[0,3,2,1], [0,1,5,4], [1,2,6,5], [2,3,7,6], [3,0,4,7], [4,5,6,7]]) @@ -271,11 +113,10 @@ def process(self): if self.mesh_join: lst_bba_vertices, lst_bba_edges, lst_bba_faces = mesh_join(lst_bba_vertices, lst_bba_edges, lst_bba_faces) lst_bba_vertices, lst_bba_edges, lst_bba_faces = [lst_bba_vertices], [lst_bba_edges], [lst_bba_faces] - # calc length, width, height + # do not recalc length, width, height pass outputs['Vertices'].sv_set(lst_bba_vertices) - #outputs['Vertices_tests'].sv_set([np_verts.tolist()]) outputs['Edges'].sv_set(lst_bba_edges) outputs['Faces'].sv_set(lst_bba_faces) outputs['Matrix'].sv_set(lst_bba_matrix) diff --git a/utils/geom.py b/utils/geom.py index cf93fc2daf..ede0bae5e5 100644 --- a/utils/geom.py +++ b/utils/geom.py @@ -46,7 +46,16 @@ from sverchok.utils.decorators_compilation import njit def bounding_box_aligned(verts, evec_external=None, factor=1.0): - '''res=[[0,0,0], [0,1,0], [1,1,0], [1,0,0], [0,0,1], [0,1,1], [1,1,1], [1,0,1],]; 1-used axis''' + ''' Build bounding box around vectors. If evec_external is not none then it can be used with factor. + if evec_external is none then factor is not using. + Function calc bounding box around vertexes. If evec_external is not none then function calc + bounding box aligned with evec_external. If factor==0 then used exec. If factor==1 then used + evec_external. Else used factor as interpolation beetwing evec and evec_external. + res=[[0,0,0], [0,1,0], [1,1,0], [1,0,0], [0,0,1], [0,1,1], [1,1,1], [1,0,1],]; 1-used axis + rrc - vertices of aligned bounding box + max - matrix of transformation of box with size 1,1,1 to evec_target + abbox_size - is a vector of sizes by axis (an order of XYZ can be differ of source verts) + ''' def realign_evec(evec): # make evecs orthogonals each other: @@ -87,25 +96,6 @@ def realign_evec(evec): else: cov = np.cov(data) evalue, evec = np.linalg.eig(cov) # some times evec vectors are not perpendicular each other. What to do for this? - - # # make evecs orthogonals each other: - # vecs = [[0,1,2], [1,2,0], [2,0,1]] - # evec_dots = np.array( [abs(np.dot( evec.T[ivect[0]], evec.T[ivect[1]] )) for ivect in vecs] ) # get dots product vectors each other - # if np.all(evec_dots<1e-8): # if all vectors are very close to orthonormals each other. May be replased by a future algorithm - # evec_dots_sort = [0] - # else: - # evec_dots_sort = np.argsort(evec_dots) - # #print(f'sort: {vecs[evec_dots_sort[0]]}') - # v0 = evec.T[ vecs[evec_dots_sort[0]][0] ] # main vector - # v1 = evec.T[ vecs[evec_dots_sort[0]][1] ] # closest by dot product - # v2 = evec.T[ vecs[evec_dots_sort[0]][2] ] # get last vector - # v0_v1_cross = np.cross( v0, v1 ) - # v1 = np.cross( v0, v0_v1_cross ) # orthogonal v1 to v0 from v1 source position - # if np.dot(v0_v1_cross, v2)<0: # build last vector as orthogonal to v0 and v1 - # v2 = - v0_v1_cross - # else: - # v2 = v0_v1_cross - # evec_target = np.dstack( (v0, v1, v2) )[0] evec_target = realign_evec(evec) centered_data = data - means[:,np.newaxis] @@ -122,8 +112,6 @@ def realign_evec(evec): rrc += means[:, np.newaxis] rrc = rrc.transpose() abbox_center = np.mean( rrc, axis=0 ) - #mat = mathutils.Matrix.LocRotScale( [ abbox_center[i] for i in vecs[evec_dots_sort[0]] ], Matrix(evec_new).to_euler(), [ abbox_size[i] for i in vecs[evec_dots_sort[0]] ] ) - # mat = mathutils.Matrix.LocRotScale( [ abbox_center[i] for i in vecs[evec_dots_sort[0]] ], Matrix(evec_new).to_euler([ 'XYZ'[i] for i in vecs[evec_dots_sort[0]] ]), [ abbox_size[i] for i in vecs[evec_dots_sort[0]] ] ) mat_scale = Matrix() mat_scale[0][0], mat_scale[1][1], mat_scale[2][2] = abbox_size mat = mathutils.Matrix.Translation(abbox_center) @ Matrix(evec_target).to_euler().to_matrix().to_4x4() @ mat_scale From 2adbb03c8babbbd2526648d3a50d4fdafc7c1882 Mon Sep 17 00:00:00 2001 From: satabol Date: Thu, 26 Oct 2023 23:13:00 +0300 Subject: [PATCH 8/8] fix #5030. Append docs to new node Analyzer Aligned Bounding Box. v002. --- docs/nodes/analyzer/bbox_aligned.rst | 8 ++++---- docs/nodes/analyzer/diameter.rst | 1 + nodes/analyzer/bbox_aligned.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/nodes/analyzer/bbox_aligned.rst b/docs/nodes/analyzer/bbox_aligned.rst index 8e8d41b46a..2c0cd14ae6 100644 --- a/docs/nodes/analyzer/bbox_aligned.rst +++ b/docs/nodes/analyzer/bbox_aligned.rst @@ -1,5 +1,5 @@ -Aligned Bounding Box -==================== +Aligned Bounding Box (Alpha) +============================ .. image:: https://github.com/nortikin/sverchok/assets/14288520/9361e542-2804-49ff-8d5e-9b1137f2a102 :target: https://github.com/nortikin/sverchok/assets/14288520/9361e542-2804-49ff-8d5e-9b1137f2a102 @@ -9,8 +9,8 @@ Functionality Generates an *aligned bounding box* from incoming Vertices. -.. image:: https://github.com/nortikin/sverchok/assets/14288520/e7db92a4-68df-4632-aba9-1555fd36ebb4 - :target: https://github.com/nortikin/sverchok/assets/14288520/e7db92a4-68df-4632-aba9-1555fd36ebb4 +.. image:: https://github.com/nortikin/sverchok/assets/14288520/e8aeec1c-d631-409b-b695-e51e9b552226 + :target: https://github.com/nortikin/sverchok/assets/14288520/e8aeec1c-d631-409b-b695-e51e9b552226 Inputs ------ diff --git a/docs/nodes/analyzer/diameter.rst b/docs/nodes/analyzer/diameter.rst index 4fec05dde5..a40beebe51 100644 --- a/docs/nodes/analyzer/diameter.rst +++ b/docs/nodes/analyzer/diameter.rst @@ -36,6 +36,7 @@ See also -------- * Analyzers-> :doc:`Bounding Box ` +* Analyzers-> :doc:`Aligned Bounding Box ` Examples of usage ----------------- diff --git a/nodes/analyzer/bbox_aligned.py b/nodes/analyzer/bbox_aligned.py index 35eee5a9b9..ff35b8558a 100644 --- a/nodes/analyzer/bbox_aligned.py +++ b/nodes/analyzer/bbox_aligned.py @@ -36,7 +36,7 @@ class SvAlignedBBoxNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): Tooltip: Get vertices bounding box (vertices, sizes, center) """ bl_idname = 'SvAlignedBBoxNode' - bl_label = 'Aligned Bounding Box' + bl_label = 'Aligned Bounding Box (Alpha)' bl_icon = 'SHADING_BBOX' sv_icon = 'SV_BOUNDING_BOX'