Skip to content

Commit

Permalink
Merge pull request #5031 from nortikin/fix_5030_create_new_node_Align…
Browse files Browse the repository at this point in the history
…ed_Bounding_Box

fix #5030. New node "Aligned Bounding Box"
  • Loading branch information
satabol authored Oct 26, 2023
2 parents 3e0c6ad + 2adbb03 commit 66d2d34
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 16 deletions.
64 changes: 64 additions & 0 deletions docs/nodes/analyzer/bbox_aligned.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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

Functionality
-------------

Generates an *aligned bounding box* from incoming Vertices.

.. 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
------

- **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 </nodes/generator/cricket>`
* Matrix-> :doc:`Matrix In </nodes/matrix/matrix_in_mk4>`
* Viz-> :doc:`Viewer Draw </nodes/viz/viewer_draw_mk4>`

--------

.. 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 </nodes/spatial/random_points_on_mesh>`
* Spatial-> :doc:`Voronoi on Mesh </nodes/spatial/voronoi_on_mesh_mk2>`
* Viz-> :doc:`Viewer Draw </nodes/viz/viewer_draw_mk4>`
* Scene-> :doc:`Get Objects Data </nodes/scene/get_objects_data>`

.. raw:: html

<video width="700" controls>
<source src="https://github.com/nortikin/sverchok/assets/14288520/0b14d7d1-6b8b-4901-b8f5-a55177658e09" type="video/mp4">
Your browser does not support the video tag.
</video>
1 change: 1 addition & 0 deletions docs/nodes/analyzer/diameter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ See also
--------

* Analyzers-> :doc:`Bounding Box </nodes/analyzer/bbox_mk3>`
* Analyzers-> :doc:`Aligned Bounding Box </nodes/analyzer/bbox_aligned>`

Examples of usage
-----------------
Expand Down
1 change: 1 addition & 0 deletions index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@
- icon_name: VIEWZOOM
- extra_menu: MeshPartialMenu
- SvBBoxNodeMk3
- SvAlignedBBoxNode
- SvComponentAnalyzerNode
- SvDiameterNode
- SvVolumeNodeMK2
Expand Down
1 change: 1 addition & 0 deletions menus/full_by_data_type.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions menus/full_nortikin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@
- extra_menu: MeshPartialMenu
- icon_name: OBJECT_DATAMODE # icon name to show
- SvBBoxNodeMk3
- SvAlignedBBoxNode
- SvComponentAnalyzerNode
- SvDiameterNode
- SvVolumeNodeMK2
Expand Down
134 changes: 134 additions & 0 deletions nodes/analyzer/bbox_aligned.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# ##### 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, 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, zip_long_repeat
from sverchok.utils.geom import bounding_box_aligned

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 (Alpha)'
bl_icon = 'SHADING_BBOX'
sv_icon = 'SV_BOUNDING_BOX'

mesh_join : BoolProperty(
name = "merge",
description = "If checked then join multiple mesh elements into one object",
default = False,
update = updateNode)

factor : FloatProperty(
name = "Factor",
description = "Matrix interpolation to automatic calculated bounding box",
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('SvMatrixSocket', 'Matrix')
self.inputs.new('SvStringsSocket', 'Factor').prop_name = 'factor'

son('SvVerticesSocket', 'Vertices')
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)
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,]):
return

lst_bba_vertices = []
lst_bba_edges = []
lst_bba_faces = []
lst_bba_matrix = []
lst_bba_Length = []
lst_bba_Width = []
lst_bba_Height = []
for verts, matrix, factor in zip_long_repeat(Vertices, Matrixes, Factors[0]):
np_verts = np.array(verts)
bba, mat, bbox_size = bounding_box_aligned(np_verts, evec_external=matrix, factor=factor)
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]
# do not recalc length, width, height
pass

outputs['Vertices'].sv_set(lst_bba_vertices)
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)



def register():
bpy.utils.register_class(SvAlignedBBoxNode)


def unregister():
bpy.utils.unregister_class(SvAlignedBBoxNode)
2 changes: 1 addition & 1 deletion nodes/spatial/delaunay_3d_mk2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
75 changes: 61 additions & 14 deletions utils/geom.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,77 @@
from sverchok.dependencies import numba # not strictly needed i think...
from sverchok.utils.decorators_compilation import njit

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'''
def bounding_box_aligned(verts, evec_external=None, factor=1.0):
''' 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:
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).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)
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:
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_target = realign_evec(evec)

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)
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]])

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 = 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_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()

Expand Down
2 changes: 1 addition & 1 deletion utils/voronoi3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down

0 comments on commit 66d2d34

Please sign in to comment.