From 5db0277a22e991a7d620cccc08d3af857f754e27 Mon Sep 17 00:00:00 2001 From: satabol Date: Sat, 23 Mar 2024 21:32:25 +0300 Subject: [PATCH 1/8] - append new node to calculate center of mass for several meshes and total center - append docs for this node (without example) --- docs/nodes/analyzer/weighted_vector_sum.rst | 79 ++++ index.yaml | 1 + menus/full_by_data_type.yaml | 1 + menus/full_nortikin.yaml | 1 + nodes/analyzer/weighted_vector_sum.py | 453 ++++++++++++++++++++ ui/icons/sv_centroid.png | Bin 0 -> 2416 bytes 6 files changed, 535 insertions(+) create mode 100644 docs/nodes/analyzer/weighted_vector_sum.rst create mode 100644 nodes/analyzer/weighted_vector_sum.py create mode 100644 ui/icons/sv_centroid.png diff --git a/docs/nodes/analyzer/weighted_vector_sum.rst b/docs/nodes/analyzer/weighted_vector_sum.rst new file mode 100644 index 0000000000..8fd2454eee --- /dev/null +++ b/docs/nodes/analyzer/weighted_vector_sum.rst @@ -0,0 +1,79 @@ +Weighted Vector Sum (Alpha) +=========================== + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/d4ea8073-e205-4a93-867e-55c77ac81f4d + :target: https://github.com/nortikin/sverchok/assets/14288520/d4ea8073-e205-4a93-867e-55c77ac81f4d + +Functionality +------------- + +Calculate centers of mass every input mesh (with mass verts or density of materials) and calculate total center of mass: + +By edges and density: + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/23b5b892-ba5d-491a-b900-503d09242e97 + :target: https://github.com/nortikin/sverchok/assets/14288520/23b5b892-ba5d-491a-b900-503d09242e97 + +Extrapolates lists of mass of vertices: + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/bde8dfeb-ace0-4eac-b5b8-195f1700ac99 + :target: https://github.com/nortikin/sverchok/assets/14288520/bde8dfeb-ace0-4eac-b5b8-195f1700ac99 + +Using of modifier stack: + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/027a572b-e764-4009-b08b-b1acff23bb27 + :target: https://github.com/nortikin/sverchok/assets/14288520/027a572b-e764-4009-b08b-b1acff23bb27 + +Inputs +------ + +- **Vertices** - or a nested list of vertices that represent separate objects. +- **Edges** - Edges of meshes. +- **Polygons** - Polygons of meshes. +- **Mass of vertices** - Calculate of centers of mass only with vertices (ignore edges and faces). Default value is 1 mass (ex. kg, ton etc.). +- **Density** - Calculate of centers of mass only with edges or faces and ignore vertices. Default value is 1 mass/volume. + +Parameters +---------- + +- **Skip unmanifold centers** - True: If some meshes cannot be used to calc centers of mass then skip them. False - show exception + + .. image:: https://github.com/nortikin/sverchok/assets/14288520/c23429a9-44c3-47b6-bd8e-817d1cf6e4eb + :target: https://github.com/nortikin/sverchok/assets/14288520/c23429a9-44c3-47b6-bd8e-817d1cf6e4eb + +- **Center mode** - select mode to calculate center of mass (Vertices, edges, faces or volumes) + + .. image:: https://github.com/nortikin/sverchok/assets/14288520/c5bc5545-9d2a-42f0-bc59-c48caee94165 + :target: https://github.com/nortikin/sverchok/assets/14288520/c5bc5545-9d2a-42f0-bc59-c48caee94165 + + for modes **Vertices** and **Edges** triangulations are disabled (not used): + + .. image:: https://github.com/nortikin/sverchok/assets/14288520/a9136534-a9b2-4fbe-982e-45d56a437a0b + :target: https://github.com/nortikin/sverchok/assets/14288520/a9136534-a9b2-4fbe-982e-45d56a437a0b + +- **Triangulation** - used only for **Faces** or **Volumes** mode. Calculations of center of mass do triangulations before calculations. + + .. image:: https://github.com/nortikin/sverchok/assets/14288520/d3853de6-7a49-4658-87ec-c9e3b84fc3b3 + :target: https://github.com/nortikin/sverchok/assets/14288520/d3853de6-7a49-4658-87ec-c9e3b84fc3b3 + +Output +------ + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/7de47244-453c-4029-bd6d-916365fd1197 + :target: https://github.com/nortikin/sverchok/assets/14288520/7de47244-453c-4029-bd6d-916365fd1197 + +- **Vertices** - Vertices of result meshes +- **Edges** - Edges of result meshes (Triangulated after **Face** or **Volume** mode) +- **Faces** - Faces of result meshes (Triangulated after **Face** or **Volume** mode) +- **Center of mass of objects** - Center of mass of every input meshes that can be calculated +- **Total center** - If there are any center of mass then this parameter contains center of mass of all meshes (depends of input params **Mass of vertices** or **Density**) +- **Counts**, **Lengths**, **Areas**, **Volumes** - params of meshes +- **Total Counts**, **Total Lengths**, **Total Areas**, **Total Volumes** - Total params of meshes +- **Masses** - List of mass of every input meshes (**Vertices** mode: total summa of vertices mass, **Edges** mode: multiply length*density, **Faces** mode: multiply areas*density, **Volumes** mode: multiply volumes*density) +- **Mass** - Summary mass of all calculated meshes +- **Mask** - Mask of calculated objects. Example: if Mode is Volume and mashes are [cube1, plane1, vertex1, cube2] then mask is [ True, False, False, True] + +Examples +-------- + +TODO \ No newline at end of file diff --git a/index.yaml b/index.yaml index e6edaf5752..fb74a635e8 100644 --- a/index.yaml +++ b/index.yaml @@ -361,6 +361,7 @@ - SvVolumeNodeMK2 - SvAreaNode - DistancePPNode + - SvWeightedVectorSumNode - SvDistancePointLineNode - SvDistancePointPlaneNode - SvDistancetLineLineNode diff --git a/menus/full_by_data_type.yaml b/menus/full_by_data_type.yaml index e905e2ae10..2f99f06873 100644 --- a/menus/full_by_data_type.yaml +++ b/menus/full_by_data_type.yaml @@ -187,6 +187,7 @@ - SvVolumeNodeMK2 - SvAreaNode - DistancePPNode + - SvWeightedVectorSumNode - SvDistancePointLineNode - SvDistancePointPlaneNode - SvDistancetLineLineNode diff --git a/menus/full_nortikin.yaml b/menus/full_nortikin.yaml index f83e340cec..a87f3e41db 100644 --- a/menus/full_nortikin.yaml +++ b/menus/full_nortikin.yaml @@ -413,6 +413,7 @@ - SvVolumeNodeMK2 - SvAreaNode - DistancePPNode + - SvWeightedVectorSumNode - SvDistancePointLineNode - SvDistancePointPlaneNode - SvDistancetLineLineNode diff --git a/nodes/analyzer/weighted_vector_sum.py b/nodes/analyzer/weighted_vector_sum.py new file mode 100644 index 0000000000..76b219fb67 --- /dev/null +++ b/nodes/analyzer/weighted_vector_sum.py @@ -0,0 +1,453 @@ +# ##### 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 +import bmesh.ops +from mathutils import Matrix + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat, repeat_last_for_length, ensure_nesting_level +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh +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, match_long_repeat +from sverchok.utils.math import np_dot + +class SvWeightedVectorSumNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): + """ + Triggers: Bbox 2D or 3D + Tooltip: Get vertices bounding box (vertices, sizes, center) + """ + bl_idname = 'SvWeightedVectorSumNode' + bl_label = 'Weighted Vector Sum (Alpha)' + bl_icon = 'SNAP_FACE_CENTER' + sv_icon = 'SV_CENTROID' + + + def updateSizeSocket(self, context): + if self.center_mode == 'VERTICES': + self.outputs['output_sizes'].label = 'Count of verts' + self.outputs['output_size' ].label = 'Total Verts' + elif self.center_mode == 'EDGES': + self.outputs['output_sizes'].label = 'Lengths' + self.outputs['output_size' ].label = 'Total Length' + elif self.center_mode == 'FACES': + self.outputs['output_sizes'].label = 'Areas' + self.outputs['output_size' ].label = 'Total Area' + elif self.center_mode == 'VOLUMES': + self.outputs['output_sizes'].label = 'Volumes' + self.outputs['output_size' ].label = 'Total Volume' + + updateNode(self, context) + + center_modes = [ + ("VERTICES", "Vertices", "Calc center of mass by vertices and ignore edges, faces and volumes", 0), + ("EDGES", "Edges", "Calc center of mass by length of edges and ignore vertices, faces and volumes", 1), + ("FACES", "Faces", "Calc center of mass by surfaces of objects and ignore vertices, edges and volumes", 2), + ("VOLUMES", "Volumes", "Calc center of mass by volume of objects and ignore vertices, edges and faces", 3) + ] + + quad_modes = [ + ("BEAUTY", "Beauty", "Split the quads in nice triangles, slower method", 1), + ("FIXED", "Fixed", "Split the quads on the 1st and 3rd vertices", 2), + ("ALTERNATE", "Fixed Alternate", "Split the quads on the 2nd and 4th vertices", 3), + ("SHORT_EDGE", "Shortest Diagonal", "Split the quads based on the distance between the vertices", 4) + ] + + ngon_modes = [ + ("BEAUTY", "Beauty", "Arrange the new triangles nicely, slower method", 1), + ("EAR_CLIP", "Clip", "Split the ngons using a scanfill algorithm", 2) + ] + + skip_unmanifold_centers: BoolProperty( + name='Skip unmanifold centers', + description='If True then skip unmanifold centers else show Exception\n\nExample:\n True: if object has only vertices and \'Center mode\'=\'Volumes\', \'Faces\' or \'Edges\' then skip this object without exception', + default=True, + update=updateNode) # type: ignore + + center_mode: EnumProperty( + name='Center mode', + description="Calc center mode", + items=center_modes, + default="VOLUMES", + update=updateSizeSocket) # type: ignore + + quad_mode: EnumProperty( + name='Quads mode', + description="Quads processing mode", + items=quad_modes, + default="BEAUTY", + update=updateNode) # type: ignore + + ngon_mode: EnumProperty( + name="Polygons mode", + description="Polygons processing mode", + items=ngon_modes, + default="BEAUTY", + update=updateNode) # type: ignore + + def update_sockets(self, context): + updateNode(self, context) + + def draw_buttons(self, context, layout): + col = layout.column() + + row = col.row() + row.prop(self, 'skip_unmanifold_centers') + + row = col.row() + split = row.split(factor=0.4) + c1 = split.column() + c1.label(text="Center mode:") + c2 = split.column().row(align=True) + c2.prop(self, "center_mode", text='') + + row = col.row() + row.active = False + row.label(text='Triangulation (only Faces or Volumes):') + if self.center_mode in ['FACES', 'VOLUMES']: + row.active = True + + row = col.row() + row.active = False + split = row.split(factor=0.4) + c1 = split.column() + c1.label(text="Quads mode:") + c2 = split.column().row(align=True) + c2.prop(self, "quad_mode", text='') + if self.center_mode in ['FACES', 'VOLUMES']: + row.active = True + + row = col.row() + row.active = False + split = row.split(factor=0.5) + c1 = split.column() + c1.label(text="Polygons mode:") + c2 = split.column().row(align=True) + c2.prop(self, "ngon_mode", text='') + if self.center_mode in ['FACES', 'VOLUMES']: + row.active = True + + # layout.prop(self, "quad_mode") + # layout.prop(self, "ngon_mode") + pass + + def sv_init(self, context): + self.width=220 + self.inputs.new('SvVerticesSocket', 'input_vertices') + self.inputs.new('SvStringsSocket', 'input_edges') + self.inputs.new('SvStringsSocket', 'input_polygons') + self.inputs.new('SvStringsSocket', 'input_mass_vert') + self.inputs.new('SvStringsSocket', 'input_density') + + self.inputs["input_vertices"] .label = 'Vertices' + self.inputs["input_edges"] .label = 'Edges' + self.inputs["input_polygons"] .label = 'Polygons' + self.inputs["input_mass_vert"].label = 'Mass of vertiсes' + self.inputs["input_density"] .label = 'Density' + + + self.outputs.new('SvVerticesSocket', 'output_vertices') + self.outputs.new('SvStringsSocket', 'output_edges') + self.outputs.new('SvStringsSocket', 'output_polygons') + + self.outputs.new('SvVerticesSocket', 'output_centers_of_mass') + self.outputs.new('SvVerticesSocket', 'output_total_center') + self.outputs.new('SvStringsSocket', 'output_sizes') + self.outputs.new('SvStringsSocket', 'output_size') + self.outputs.new('SvStringsSocket', 'output_masses') + self.outputs.new('SvStringsSocket', 'output_mass') + self.outputs.new('SvStringsSocket', 'output_mask') + + self.outputs['output_vertices'] .label = 'Vertices' + self.outputs['output_edges'] .label = 'Edges' + self.outputs['output_polygons'] .label = 'Polygons' + self.outputs['output_centers_of_mass'].label = 'Centers mass of objects' + self.outputs['output_total_center'].label = 'Total center' + self.outputs['output_sizes'].label = '' + self.outputs['output_size'] .label = '' + self.outputs['output_masses'] .label = 'Masses' + self.outputs['output_mass'] .label = 'Mass' + self.outputs['output_mask'] .label = 'Mask' + + self.updateSizeSocket(context) + self.update_sockets(context) + + def process(self): + outputs = self.outputs + if not any( [o.is_linked for o in outputs]): + return + + if not (self.inputs['input_vertices'].is_linked): + return + if not (any(self.outputs[name].is_linked for name in [ + 'output_vertices', 'output_edges', 'output_polygons', 'output_centers_of_mass', 'output_total_center', 'output_sizes', 'output_size', 'output_masses', 'output_mass', 'output_mask',])): + return + + input_vertices_s = self.inputs['input_vertices'].sv_get(default=[[]], deepcopy=False) + input_vertices_s_3 = ensure_nesting_level(input_vertices_s, 3) + input_edges_s = self.inputs['input_edges'].sv_get(default=[[]], deepcopy=False) + input_edges_s_3 = ensure_nesting_level(input_edges_s, 3) + input_polygons_s = self.inputs['input_polygons'].sv_get(default=[[]], deepcopy=False) + input_polygons_s_3 = ensure_nesting_level(input_polygons_s, 3) + input_mass_vert_s = self.inputs['input_mass_vert'].sv_get(default=[[1]], deepcopy=False) + input_mass_vert_s_2 = ensure_nesting_level(input_mass_vert_s, 2) + input_density_s = self.inputs['input_density'].sv_get(default=[[1]], deepcopy=False) + input_density_s_2 = ensure_nesting_level(input_density_s, 2) + + result_vertices = [] + result_edges = [] + result_polygons = [] + + + center_mass_mesh_list_out = [] + result_masses = [] + result_mask = [] # what object has result + + center_mass_mesh_list = [] + mass_mesh_list = [] + size_mesh_list = [] + + mass_center_general = None + if self.center_mode=='VERTICES': + _, _input_mass_vert_s_2, _input_density_s_2 = match_long_repeat( [input_vertices_s_3, input_mass_vert_s_2[:len(input_vertices_s_3)], input_density_s_2[:len(input_vertices_s_3)] ]) + meshes = match_long_repeat([input_vertices_s_3, input_edges_s_3, input_polygons_s_3, _input_mass_vert_s_2, _input_density_s_2]) + + for vertices_I, edges_I, faces_I, mass_of_vertices_I, density_I in zip(*meshes): + if len(vertices_I)==0: + result_mask.append(False) + else: + result_mask.append(True) + result_vertices.append(vertices_I) + result_edges.append(edges_I) + result_polygons.append(faces_I) + + # shrink or extend mass if list of mass is not equals list of verts: + v, m = match_long_repeat( [vertices_I, mass_of_vertices_I[:len(vertices_I)] ]) + np_verts = np.array(v) + np_mass = np.array([m]) + mass_I = np_mass.sum() + center_mass_mesh_I = (np_verts * np_mass.T).sum(axis=0) / mass_I + + center_mass_mesh_list.append( center_mass_mesh_I.tolist() ) + mass_mesh_list.append(mass_I) + size_mesh_list.append(np_verts.shape[0]) # Count of vertices + + elif self.center_mode=='EDGES': + # If density is one list and objects are many then + # spead list of density for every mesh. ex.: [[mesh1],[mesh2],[mesh3]], [density:d1,d2,d3] ] => [[mesh1],[mesh2],[mesh3]], [density:[d1],[d2],[d3]] ] + if len(input_density_s_2)==1 and len(input_vertices_s_3)>1: + input_density_s_2 = [[d] for d in input_density_s_2[0]] + + # if lists of edges or faces is less vertices then append at the end of edges and faces empty list to extend it later with empty items. + # verts=[[v1,v2,v3],[v4,v5,v6]] + # edges=[[[0,1],[1,2]]]=>[[[0,1], [1,2]],[]] + # faces=[[[0,1,2]]]=>[[[0,1,2]]],[]] + # this allow skip unmanifold centers + if len(input_edges_s_3) [[mesh1],[mesh2],[mesh3]], [density:[d1],[d2],[d3]] ] + if len(input_density_s_2)==1 and len(input_vertices_s_3)>1: + input_density_s_2 = [[d] for d in input_density_s_2[0]] + + # if lists of edges or faces is less vertices then append at the end of edges and faces empty list to extend it later with empty items. + # verts=[[v1,v2,v3],[v4,v5,v6]] + # edges=[[[0,1],[1,2]]]=>[[[0,1], [1,2]],[]] + # faces=[[[0,1,2]]]=>[[[0,1,2]]],[]] + # this allow skip unmanifold centers + if len(input_edges_s_3) [[mesh1],[mesh2],[mesh3]], [density:[d1],[d2],[d3]] ] + if len(input_density_s_2)==1 and len(input_vertices_s_3)>1: + input_density_s_2 = [[d] for d in input_density_s_2[0]] + + _, _input_mass_vert_s_2, _input_density_s_2 = match_long_repeat( [input_vertices_s_3, input_mass_vert_s_2[:len(input_vertices_s_3)], input_density_s_2[:len(input_vertices_s_3)]]) + meshes = match_long_repeat([input_vertices_s_3, input_edges_s_3, input_polygons_s_3, _input_mass_vert_s_2, _input_density_s_2]) + + for vertices_I, edges_I, faces_I, mass_of_vertices_I, density_I in zip(*meshes): + faces_I_np = np.array(faces_I) + if faces_I_np.size==0: + # if no faces at all: + result_mask.append(False) + else: + + # triangulate mesh + bm_I = bmesh_from_pydata(vertices_I, edges_I, faces_I, markup_face_data=True, normal_update=True) + # test if all edges are contiguous (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMEdge.is_contiguous) + # then volume is closed: + for edge in bm_I.edges: + if edge.is_contiguous==False: + result_mask.append(False) + bm_I.free() + break + else: + result_mask.append(True) + b_faces = [] + for face in bm_I.faces: + b_faces.append(face) + + res = bmesh.ops.triangulate( + bm_I, faces=b_faces, quad_method=self.quad_mode, ngon_method=self.ngon_mode + ) + new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I) + bm_I.free() + + result_vertices.append(new_vertices_I) + result_edges.append(new_edges_I) + result_polygons.append(new_faces_I) + + verts_I = np.array(new_vertices_I) + faces_I = np.array(new_faces_I) + tris_I = verts_I[faces_I] + signed_volumes_I = np_dot(tris_I[:,0], np.cross(tris_I[:,1], tris_I[:,2])) + volume_I = signed_volumes_I.sum() + tetra_centers_I = tris_I.sum(axis=1) / 4.0 + center_mass_mesh_I = (tetra_centers_I * signed_volumes_I[np.newaxis].T).sum(axis=0) / volume_I + mass_I = volume_I * density_I[0] + + center_mass_mesh_list.append( center_mass_mesh_I.tolist() ) + mass_mesh_list.append(mass_I) + size_mesh_list.append(volume_I) + + # calc center of mass of all meshes if any mesh has center: + if center_mass_mesh_list: + center_mass_mesh_list_np = np.array(center_mass_mesh_list) + mass_mesh_list_np = np.array([mass_mesh_list]) + mass_center_general_np = (center_mass_mesh_list_np*mass_mesh_list_np.T).sum(axis=0) / mass_mesh_list_np.sum() + mass_center_general = [[ mass_center_general_np.tolist() ]] + center_mass_mesh_list_out = [ [v] for v in center_mass_mesh_list ] + result_masses = [ [m] for m in mass_mesh_list ] + result_mass = mass_mesh_list_np.sum() + result_sizes = [ [s] for s in size_mesh_list ] + result_size = np.array(size_mesh_list).sum() + else: + # if there is no any centers of mass + mass_center_general = [ [] ] + center_mass_mesh_list_out = [ [] ] + result_masses = [ [] ] + result_mass = 0 + result_sizes = [ [] ] + result_size = 0 + + self.outputs['output_vertices'].sv_set(result_vertices) + self.outputs['output_edges'].sv_set(result_edges) + self.outputs['output_polygons'].sv_set(result_polygons) + + self.outputs['output_centers_of_mass'].sv_set(center_mass_mesh_list_out) + self.outputs['output_total_center'].sv_set(mass_center_general) + self.outputs['output_sizes'].sv_set(result_sizes) + self.outputs['output_size'].sv_set([[result_size]]) + self.outputs['output_masses'].sv_set(result_masses) + self.outputs['output_mass'].sv_set([[result_mass]]) + self.outputs['output_mask'].sv_set(result_mask) + + unmanifold_centers_indices = [i for i, x in enumerate(result_mask) if not(x)] + if self.skip_unmanifold_centers==False: + if unmanifold_centers_indices: + str_error = f"Unmanifold centers are: [{','.join( map(str,unmanifold_centers_indices))}]" + raise Exception(str_error) + pass + + + +def register(): + bpy.utils.register_class(SvWeightedVectorSumNode) + + +def unregister(): + bpy.utils.unregister_class(SvWeightedVectorSumNode) diff --git a/ui/icons/sv_centroid.png b/ui/icons/sv_centroid.png new file mode 100644 index 0000000000000000000000000000000000000000..802b708bcc855443e8022fdf4f8f4cd203d877a8 GIT binary patch literal 2416 zcmV-$36J)PP)yTs{(PEFrqos``)>coXJvDetj_(H1Mma5t zC@k#s-eE?DVHS3+2?+bg_YBU?%+CGX-@U)@{B~5ZEUOsIQn&!P0Js3S0Js3S0Js3S z01N~f3=A=70VtKqc#5KKf^L8^2Mqv&!9YU5;h+Z~mRHlD01yxm;0eJNf~=z8QI!a0 zzyc6u{Q?H;9hS(BS$&lq5uGgnNPuIg0SQ2wbvG{`c3klTHpI+w;sN9gxDfyW5#p>1 z8X29{A_PCC;G(djw-CfT+zU`1mY?KM`feNkNNq}%CV^50}b)pVSL**X}C-4!F54UAme{ zQUAVV$?&s#=T1Ml>sPY?2(8yMqzHiJQTN}@7Itfp(Da=30)3TrLaxK>`E1qfOPO`G zJaI9U#qo9~vHjnBe>#`7_9NLYMmxZ2SVR~ zb^_2M40^`4%INnt?~AP;HA-ov=}@@+Xz_Uf;1U-SuXcr^y*Dq=FS0HxM(c%5(Yj{p zzYlC;!s3ed|2$^kx3AB`+(aGB+deaz?W5;SjImVgQE-;`;YG3p~lJ z;~Wp_I98z;#sxak^C(M`brD53?~E(E&rT`1eCX1v6U^cu?0O&324FBXN+zcON7^ocd706mO z=iPtDdt$0f3F7nyZ&Co?exc_Dadh<;4^)Vlr zH!K^{Ltm15fyW0Rw;~I}fYp%XY0SDP({)0S_nseB{nzXTk#!l#Wv7>JsHx@bB-8aR@VDB?0|Ae~1yg_U7wc+vCr<+)nKziXl#&r3 zT^DeE1i*-C(rU?LvBT>E-K{808Q4gmzMCbDjtv9}!QWOW z8Wxdc2JH9aYnIAqj3914r^PnaSz*RQ%(jL{08DJG#4b}1xK6Z&z*?E8cSXq)uM3=i zEFa+J=JrP^0Mslf1E22uZzE~YD$q=KclB_FG0-sm(Ex*T_QeGVdf0eV>spPu4Ix~U zJLa_EeH8>=#jPK#j`}ZbgC55V^wtCXKyetDTJ2s1P#Wc+*wMH6Gc`zuP&_<5yq$qE zW#^yvByQgCkG;U&GyOv*+S_W3vRvDhko4J(Ux${i&-K#rfja61dISJBOTkT3GEfq5 zW>BfsUnrHzCMTd=1XW;X21R#=Vj!^DEnHvvOk8or8(C{Mc{@648Vs-8Fp?vPy%=;TU0958GzF41QeWQ%JH=pP{i0x4GSlmYxl#G-}`)5 zryW^g#_iqG=@w{+%>bdq(RG3U`$-UOE&w4>5-4LqNeIPFrK*8iY;gn#zUY9BFSzi# zdqV;uuzbS2y@$DrMoQ;HlVF-~*fUzKxG8Rk3HVgv1?V4aMTp@>N>?aw6Zpf01~eb% zX6PsXl!M}%u!#fk;(`O>{b<7e0ouvW@ftrrU!?#AzlZY&jMgFmI6f!jsKg6UeYz^# zQ9gz)<->RDFtpPc96+h1Xu8e8(!rOazQo{osG53a<}*+N)pysvnaiYxe&~o7=+mFY z7@3~JLkZ`5LODSB+KGO04|Gzgq>CnpJhu<`sgyM9?xsBd^!z;D2}N8!_?tom52XT{ z>{UmA_;{+=V{<~--Em{u?Y&6(bi5xV4`momrTmnNQ0k?@U(A@u^@9hxDJ*Y>$bs>i zoSi8d4G)?w-h5SVo%WL!4F02p;Kl*}s%$3|10|i32kqP9So0uq&;oG%e*qT&7XTLk i7XTLk7XTN4p7cL0TM>L1KSU}30000 Date: Sun, 24 Mar 2024 19:14:27 +0300 Subject: [PATCH 2/8] - fixed by code review: - rename node name in menu - append a param skip_test_volume_are_closed (only for volume mode) - append test triangulation before call bmesh utils for Face and Volume modes --- docs/nodes/analyzer/weighted_vector_sum.rst | 4 +- docs/nodes/solid/center_of_mass.rst | 4 +- nodes/analyzer/weighted_vector_sum.py | 168 +++++++++++--------- nodes/solid/center_of_mass.py | 4 +- 4 files changed, 102 insertions(+), 78 deletions(-) diff --git a/docs/nodes/analyzer/weighted_vector_sum.rst b/docs/nodes/analyzer/weighted_vector_sum.rst index 8fd2454eee..d8fe681729 100644 --- a/docs/nodes/analyzer/weighted_vector_sum.rst +++ b/docs/nodes/analyzer/weighted_vector_sum.rst @@ -1,5 +1,5 @@ -Weighted Vector Sum (Alpha) -=========================== +Center of Mass (Mesh) (Alpha) +============================= .. image:: https://github.com/nortikin/sverchok/assets/14288520/d4ea8073-e205-4a93-867e-55c77ac81f4d :target: https://github.com/nortikin/sverchok/assets/14288520/d4ea8073-e205-4a93-867e-55c77ac81f4d diff --git a/docs/nodes/solid/center_of_mass.rst b/docs/nodes/solid/center_of_mass.rst index 7e9dff6c39..f149f1b533 100644 --- a/docs/nodes/solid/center_of_mass.rst +++ b/docs/nodes/solid/center_of_mass.rst @@ -1,5 +1,5 @@ -Center of Mass -============== +Center of Mass (Solid) +====================== Dependencies ------------ diff --git a/nodes/analyzer/weighted_vector_sum.py b/nodes/analyzer/weighted_vector_sum.py index 76b219fb67..84c4ef6a4a 100644 --- a/nodes/analyzer/weighted_vector_sum.py +++ b/nodes/analyzer/weighted_vector_sum.py @@ -22,6 +22,7 @@ from bpy.props import BoolVectorProperty, EnumProperty, BoolProperty, FloatProperty import bmesh.ops from mathutils import Matrix +from datetime import datetime from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode, match_long_repeat, repeat_last_for_length, ensure_nesting_level @@ -35,11 +36,11 @@ class SvWeightedVectorSumNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): """ - Triggers: Bbox 2D or 3D - Tooltip: Get vertices bounding box (vertices, sizes, center) + Triggers: Center of mass (Mesh) + Tooltip: Calculate center of mass (barycenter) of mesh per vertices, edges, faces or volume. (Volume is only for closed mesh) """ bl_idname = 'SvWeightedVectorSumNode' - bl_label = 'Weighted Vector Sum (Alpha)' + bl_label = 'Center of mass (Mesh) (Alpha)' bl_icon = 'SNAP_FACE_CENTER' sv_icon = 'SV_CENTROID' @@ -62,21 +63,21 @@ def updateSizeSocket(self, context): center_modes = [ ("VERTICES", "Vertices", "Calc center of mass by vertices and ignore edges, faces and volumes", 0), - ("EDGES", "Edges", "Calc center of mass by length of edges and ignore vertices, faces and volumes", 1), - ("FACES", "Faces", "Calc center of mass by surfaces of objects and ignore vertices, edges and volumes", 2), - ("VOLUMES", "Volumes", "Calc center of mass by volume of objects and ignore vertices, edges and faces", 3) + ( "EDGES", "Edges", "Calc center of mass by length of edges and ignore vertices, faces and volumes", 1), + ( "FACES", "Faces", "Calc center of mass by surfaces of objects and ignore vertices, edges and volumes", 2), + ( "VOLUMES", "Volumes", "Calc center of mass by volume of objects and ignore vertices, edges and faces", 3) ] quad_modes = [ - ("BEAUTY", "Beauty", "Split the quads in nice triangles, slower method", 1), - ("FIXED", "Fixed", "Split the quads on the 1st and 3rd vertices", 2), - ("ALTERNATE", "Fixed Alternate", "Split the quads on the 2nd and 4th vertices", 3), + ( "BEAUTY", "Beauty", "Split the quads in nice triangles, slower method", 1), + ( "FIXED", "Fixed", "Split the quads on the 1st and 3rd vertices", 2), + ( "ALTERNATE", "Fixed Alternate", "Split the quads on the 2nd and 4th vertices", 3), ("SHORT_EDGE", "Shortest Diagonal", "Split the quads based on the distance between the vertices", 4) ] ngon_modes = [ - ("BEAUTY", "Beauty", "Arrange the new triangles nicely, slower method", 1), - ("EAR_CLIP", "Clip", "Split the ngons using a scanfill algorithm", 2) + ( "BEAUTY", "Beauty", "Arrange the new triangles nicely, slower method", 1), + ("EAR_CLIP", "Clip", "Split the ngons using a scanfill algorithm", 2) ] skip_unmanifold_centers: BoolProperty( @@ -85,6 +86,12 @@ def updateSizeSocket(self, context): default=True, update=updateNode) # type: ignore + skip_test_volume_are_closed: BoolProperty( + name='Skip test meshes for holes', + description='If True then skip test meshes for holes. (Only for \'Volumes\' mode)', + default=True, + update=updateNode) # type: ignore + center_mode: EnumProperty( name='Center mode', description="Calc center mode", @@ -114,13 +121,17 @@ def draw_buttons(self, context, layout): row = col.row() row.prop(self, 'skip_unmanifold_centers') + + row = col.row() + row.active = False + row.prop(self, 'skip_test_volume_are_closed') + if self.center_mode in ['VOLUMES']: + row.active = True row = col.row() split = row.split(factor=0.4) - c1 = split.column() - c1.label(text="Center mode:") - c2 = split.column().row(align=True) - c2.prop(self, "center_mode", text='') + split.column().label(text="Center mode:") + split.column().row(align=True).prop(self, "center_mode", text='') row = col.row() row.active = False @@ -131,25 +142,19 @@ def draw_buttons(self, context, layout): row = col.row() row.active = False split = row.split(factor=0.4) - c1 = split.column() - c1.label(text="Quads mode:") - c2 = split.column().row(align=True) - c2.prop(self, "quad_mode", text='') + split.column().label(text="Quads mode:") + split.column().row(align=True).prop(self, "quad_mode", text='') if self.center_mode in ['FACES', 'VOLUMES']: row.active = True row = col.row() row.active = False split = row.split(factor=0.5) - c1 = split.column() - c1.label(text="Polygons mode:") - c2 = split.column().row(align=True) - c2.prop(self, "ngon_mode", text='') + split.column().label(text="Polygons mode:") + split.column().row(align=True).prop(self, "ngon_mode", text='') if self.center_mode in ['FACES', 'VOLUMES']: row.active = True - # layout.prop(self, "quad_mode") - # layout.prop(self, "ngon_mode") pass def sv_init(self, context): @@ -182,7 +187,7 @@ def sv_init(self, context): self.outputs['output_vertices'] .label = 'Vertices' self.outputs['output_edges'] .label = 'Edges' self.outputs['output_polygons'] .label = 'Polygons' - self.outputs['output_centers_of_mass'].label = 'Centers mass of objects' + self.outputs['output_centers_of_mass'].label = 'Centers of mass of objects' self.outputs['output_total_center'].label = 'Total center' self.outputs['output_sizes'].label = '' self.outputs['output_size'] .label = '' @@ -243,15 +248,17 @@ def process(self): result_polygons.append(faces_I) # shrink or extend mass if list of mass is not equals list of verts: - v, m = match_long_repeat( [vertices_I, mass_of_vertices_I[:len(vertices_I)] ]) - np_verts = np.array(v) - np_mass = np.array([m]) - mass_I = np_mass.sum() - center_mass_mesh_I = (np_verts * np_mass.T).sum(axis=0) / mass_I + mass_of_vertices_I_shrinked = mass_of_vertices_I[:len(vertices_I)] + mass_of_vertices_I_np1 = np.append( mass_of_vertices_I_shrinked, np.full( (len(vertices_I)-len(mass_of_vertices_I_shrinked)), mass_of_vertices_I_shrinked[-1]) ) + #v, m = match_long_repeat( [vertices_I, mass_of_vertices_I_shrinked[:len(vertices_I)] ]) + vertices_I_np = np.array(vertices_I) + mass_of_vertices_I_np2 = np.array([mass_of_vertices_I_np1]) + mass_I = mass_of_vertices_I_np2.sum() + center_mass_mesh_I = (vertices_I_np * mass_of_vertices_I_np2.T).sum(axis=0) / mass_I center_mass_mesh_list.append( center_mass_mesh_I.tolist() ) mass_mesh_list.append(mass_I) - size_mesh_list.append(np_verts.shape[0]) # Count of vertices + size_mesh_list.append(vertices_I_np.shape[0]) # Count of vertices elif self.center_mode=='EDGES': # If density is one list and objects are many then @@ -312,8 +319,7 @@ def process(self): if len(input_polygons_s_3)3: + # triangulate mesh for 'FACES' mode + bm_I = bmesh_from_pydata(vertices_I, edges_I, faces_I, markup_face_data=True, normal_update=True) + b_faces = [] + for face in bm_I.faces: + b_faces.append(face) + res = bmesh.ops.triangulate( + bm_I, faces=b_faces, quad_method=self.quad_mode, ngon_method=self.ngon_mode + ) + new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I) + bm_I.free() + else: + new_vertices_I,new_edges_I,new_faces_I = vertices_I, edges_I, faces_I result_vertices.append(new_vertices_I) result_edges.append(new_edges_I) result_polygons.append(new_faces_I) - verts_I = np.array(new_vertices_I) - faces_I = np.array(new_faces_I) - tris_I = verts_I[faces_I] - areases_I = np.linalg.norm(np.cross(tris_I[:,1]-tris_I[:,0], tris_I[:,2]-tris_I[:,0]) / 2.0, axis=1) + verts_I_np = np.array(new_vertices_I) + faces_I_np = np.array(new_faces_I) + tris_I_np = verts_I_np[faces_I_np] + areases_I = np.linalg.norm(np.cross(tris_I_np[:,1]-tris_I_np[:,0], tris_I_np[:,2]-tris_I_np[:,0]) / 2.0, axis=1) area_I = areases_I.sum() - tris_centers_I = tris_I.sum(axis=1) / 3.0 + tris_centers_I = tris_I_np.sum(axis=1) / 3.0 center_mass_mesh_I = (tris_centers_I * areases_I[np.newaxis].T).sum(axis=0) / area_I mass_I = area_I * density_I[0] @@ -365,39 +375,53 @@ def process(self): # if no faces at all: result_mask.append(False) else: - - # triangulate mesh - bm_I = bmesh_from_pydata(vertices_I, edges_I, faces_I, markup_face_data=True, normal_update=True) - # test if all edges are contiguous (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMEdge.is_contiguous) - # then volume is closed: - for edge in bm_I.edges: - if edge.is_contiguous==False: - result_mask.append(False) - bm_I.free() - break + do_calc = False + if max(map(len, faces_I_np))==3 and self.skip_test_volume_are_closed==True: + new_vertices_I, new_edges_I, new_faces_I = vertices_I, edges_I, faces_I + do_calc = True else: - result_mask.append(True) - b_faces = [] - for face in bm_I.faces: - b_faces.append(face) - - res = bmesh.ops.triangulate( - bm_I, faces=b_faces, quad_method=self.quad_mode, ngon_method=self.ngon_mode - ) - new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I) + # triangulate mesh + bm_I = bmesh_from_pydata(vertices_I, edges_I, faces_I, markup_face_data=True, normal_update=True) + # test if all edges are contiguous (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMEdge.is_contiguous) + # then volume is closed: + for edge in bm_I.edges: + if edge.is_contiguous==False: + do_calc = False + break + else: + do_calc = True + # test if some polygons are not tris: + if max(map(len, faces_I_np))>3: + b_faces = [] + for face in bm_I.faces: + b_faces.append(face) + + res = bmesh.ops.triangulate( + bm_I, faces=b_faces, quad_method=self.quad_mode, ngon_method=self.ngon_mode + ) + new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I) + else: + new_vertices_I, new_edges_I, new_faces_I = vertices_I, edges_I, faces_I bm_I.free() + if do_calc==False: + result_mask.append(False) + else: + result_mask.append(True) result_vertices.append(new_vertices_I) - result_edges.append(new_edges_I) + result_edges .append(new_edges_I) result_polygons.append(new_faces_I) verts_I = np.array(new_vertices_I) faces_I = np.array(new_faces_I) tris_I = verts_I[faces_I] - signed_volumes_I = np_dot(tris_I[:,0], np.cross(tris_I[:,1], tris_I[:,2])) + # to precise calc move all mesh to median point + tris_I_median = np.median(tris_I, axis=(0,1)) + tris_I_delta = tris_I-tris_I_median + signed_volumes_I = np_dot(tris_I_delta[:,0], np.cross(tris_I_delta[:,1], tris_I_delta[:,2])) volume_I = signed_volumes_I.sum() - tetra_centers_I = tris_I.sum(axis=1) / 4.0 - center_mass_mesh_I = (tetra_centers_I * signed_volumes_I[np.newaxis].T).sum(axis=0) / volume_I + tetra_centers_I = tris_I_delta.sum(axis=1) / 4.0 + center_mass_mesh_I = (tetra_centers_I * signed_volumes_I[np.newaxis].T).sum(axis=0) / volume_I + tris_I_median mass_I = volume_I * density_I[0] center_mass_mesh_list.append( center_mass_mesh_I.tolist() ) diff --git a/nodes/solid/center_of_mass.py b/nodes/solid/center_of_mass.py index 8009b738c9..9d512f17df 100644 --- a/nodes/solid/center_of_mass.py +++ b/nodes/solid/center_of_mass.py @@ -17,11 +17,11 @@ class SvSolidCenterOfMassNode(SverchCustomTreeNode, bpy.types.Node): """ - Triggers: Center of Mass + Triggers: Center of Mass (Solid) Tooltip: Calculate center of mass (barycenter) of a Solid object """ bl_idname = 'SvSolidCenterOfMassNode' - bl_label = 'Center of Mass' + bl_label = 'Center of Mass (Solid)' sv_category = "Solid Operators" sv_dependencies = {'FreeCAD'} From 1297a0be1b82bae7878c84e26b278e7abd36e683 Mon Sep 17 00:00:00 2001 From: satabol Date: Sun, 24 Mar 2024 20:24:03 +0300 Subject: [PATCH 3/8] optimize for loop --- nodes/analyzer/weighted_vector_sum.py | 91 +++++++++++---------------- 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/nodes/analyzer/weighted_vector_sum.py b/nodes/analyzer/weighted_vector_sum.py index 84c4ef6a4a..874af10cef 100644 --- a/nodes/analyzer/weighted_vector_sum.py +++ b/nodes/analyzer/weighted_vector_sum.py @@ -234,11 +234,32 @@ def process(self): size_mesh_list = [] mass_center_general = None - if self.center_mode=='VERTICES': - _, _input_mass_vert_s_2, _input_density_s_2 = match_long_repeat( [input_vertices_s_3, input_mass_vert_s_2[:len(input_vertices_s_3)], input_density_s_2[:len(input_vertices_s_3)] ]) - meshes = match_long_repeat([input_vertices_s_3, input_edges_s_3, input_polygons_s_3, _input_mass_vert_s_2, _input_density_s_2]) - for vertices_I, edges_I, faces_I, mass_of_vertices_I, density_I in zip(*meshes): + # If density is one level list and objects are many then + # spead list of density for every mesh. ex.: [[mesh1],[mesh2],[mesh3]], [density:d1,d2,d3] ] => [[mesh1],[mesh2],[mesh3]], [density:[d1],[d2],[d3]] ] + if len(input_density_s_2)==1 and len(input_vertices_s_3)>1: + input_density_s_2 = [[d] for d in input_density_s_2[0]] + + # if lists of edges or faces is less vertices then append at the end of edges and faces empty list to extend it later with empty items. + # verts=[[v1,v2,v3],[v4,v5,v6]] + # edges=[[[0,1],[1,2]]]=>[[[0,1], [1,2]],[]] + # faces=[[[0,1,2]]]=>[[[0,1,2]]],[]] + # this allow skip unmanifold centers + if len(input_edges_s_3) [[mesh1],[mesh2],[mesh3]], [density:[d1],[d2],[d3]] ] - if len(input_density_s_2)==1 and len(input_vertices_s_3)>1: - input_density_s_2 = [[d] for d in input_density_s_2[0]] - - # if lists of edges or faces is less vertices then append at the end of edges and faces empty list to extend it later with empty items. - # verts=[[v1,v2,v3],[v4,v5,v6]] - # edges=[[[0,1],[1,2]]]=>[[[0,1], [1,2]],[]] - # faces=[[[0,1,2]]]=>[[[0,1,2]]],[]] - # this allow skip unmanifold centers - if len(input_edges_s_3) [[mesh1],[mesh2],[mesh3]], [density:[d1],[d2],[d3]] ] - if len(input_density_s_2)==1 and len(input_vertices_s_3)>1: - input_density_s_2 = [[d] for d in input_density_s_2[0]] - - # if lists of edges or faces is less vertices then append at the end of edges and faces empty list to extend it later with empty items. - # verts=[[v1,v2,v3],[v4,v5,v6]] - # edges=[[[0,1],[1,2]]]=>[[[0,1], [1,2]],[]] - # faces=[[[0,1,2]]]=>[[[0,1,2]]],[]] - # this allow skip unmanifold centers - if len(input_edges_s_3) [[mesh1],[mesh2],[mesh3]], [density:[d1],[d2],[d3]] ] - if len(input_density_s_2)==1 and len(input_vertices_s_3)>1: - input_density_s_2 = [[d] for d in input_density_s_2[0]] - - _, _input_mass_vert_s_2, _input_density_s_2 = match_long_repeat( [input_vertices_s_3, input_mass_vert_s_2[:len(input_vertices_s_3)], input_density_s_2[:len(input_vertices_s_3)]]) - meshes = match_long_repeat([input_vertices_s_3, input_edges_s_3, input_polygons_s_3, _input_mass_vert_s_2, _input_density_s_2]) - - for vertices_I, edges_I, faces_I, mass_of_vertices_I, density_I in zip(*meshes): + elif center_mode=='VOLUMES': + faces_I_np = np.array(faces_I) if faces_I_np.size==0: - # if no faces at all: + # skip if no faces at all: result_mask.append(False) else: do_calc = False From e0aabc6e2cd9fac16e89e9b1ae05d8e0cc7eaa85 Mon Sep 17 00:00:00 2001 From: satabol Date: Sun, 24 Mar 2024 22:15:29 +0300 Subject: [PATCH 4/8] - refactor calculate function to module utils\sv_bmesh_utils->calc_center_mass_bmesh --- nodes/analyzer/weighted_vector_sum.py | 248 +++++++------------------- utils/sv_bmesh_utils.py | 184 +++++++++++++++++++ 2 files changed, 244 insertions(+), 188 deletions(-) diff --git a/nodes/analyzer/weighted_vector_sum.py b/nodes/analyzer/weighted_vector_sum.py index 874af10cef..1ceca07e3a 100644 --- a/nodes/analyzer/weighted_vector_sum.py +++ b/nodes/analyzer/weighted_vector_sum.py @@ -29,10 +29,10 @@ from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh from sverchok.utils.nodes_mixins.recursive_nodes import SvRecursiveNode from sverchok.utils.sv_mesh_utils import mesh_join +from sverchok.utils.sv_bmesh_utils import calc_center_mass_bmesh from sverchok.utils.modules.matrix_utils import matrix_apply_np from sverchok.data_structure import dataCorrect, updateNode, zip_long_repeat, match_long_repeat -from sverchok.utils.math import np_dot class SvWeightedVectorSumNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode): """ @@ -220,16 +220,16 @@ def process(self): input_density_s = self.inputs['input_density'].sv_get(default=[[1]], deepcopy=False) input_density_s_2 = ensure_nesting_level(input_density_s, 2) - result_vertices = [] - result_edges = [] - result_polygons = [] + result_vertices_list = [] + result_edges_list = [] + result_polygons_list = [] center_mass_mesh_list_out = [] result_masses = [] - result_mask = [] # what object has result + result_mask_list = [] # what object has result - center_mass_mesh_list = [] + result_center_mass_mesh_list = [] mass_mesh_list = [] size_mesh_list = [] @@ -254,192 +254,64 @@ def process(self): center_mode = self.center_mode for vertices_I, edges_I, faces_I, mass_of_vertices_I, density_I in zip(*meshes): - if len(vertices_I)==0: - # skip if no vertices at all: - result_mask.append(False) - - if center_mode=='VERTICES': - - if len(vertices_I)==0: - result_mask.append(False) - else: - result_mask.append(True) - result_vertices.append(vertices_I) - result_edges.append(edges_I) - result_polygons.append(faces_I) - - # shrink or extend mass if list of mass is not equals list of verts: - mass_of_vertices_I_shrinked = mass_of_vertices_I[:len(vertices_I)] - mass_of_vertices_I_np1 = np.append( mass_of_vertices_I_shrinked, np.full( (len(vertices_I)-len(mass_of_vertices_I_shrinked)), mass_of_vertices_I_shrinked[-1]) ) - vertices_I_np = np.array(vertices_I) - mass_of_vertices_I_np2 = np.array([mass_of_vertices_I_np1]) - mass_I = mass_of_vertices_I_np2.sum() - center_mass_mesh_I = (vertices_I_np * mass_of_vertices_I_np2.T).sum(axis=0) / mass_I - - center_mass_mesh_list.append( center_mass_mesh_I.tolist() ) - mass_mesh_list.append(mass_I) - size_mesh_list.append(vertices_I_np.shape[0]) # Count of vertices - - elif center_mode=='EDGES': - - old_edges = np.array(edges_I) - if old_edges.size==0: - # skip if no edges at all: - result_mask.append(False) - else: - result_mask.append(True) - verts_I = np.array(vertices_I) - - # Do not triangulate mesh for 'EDGE' mode - result_vertices.append(vertices_I) - result_edges.append(edges_I) - result_polygons.append(faces_I) - - segment_I = verts_I[old_edges] - distances_I = np.linalg.norm(segment_I[:,1]-segment_I[:,0], axis=1) - length_I = distances_I.sum() - segment_center_I = segment_I.sum(axis=1) / 2.0 - center_mass_mesh_I = (segment_center_I * distances_I[np.newaxis].T).sum(axis=0) / length_I - mass_I = length_I * density_I[0] - - center_mass_mesh_list.append( center_mass_mesh_I.tolist() ) - mass_mesh_list.append(mass_I) - size_mesh_list.append(length_I) - - elif center_mode=='FACES': - - faces_I_np = np.array(faces_I) - if faces_I_np.size==0: - # skip if no faces at all: - result_mask.append(False) - else: - result_mask.append(True) - - # test if some polygons are not tris: - if max(map(len, faces_I_np))>3: - # triangulate mesh for 'FACES' mode - bm_I = bmesh_from_pydata(vertices_I, edges_I, faces_I, markup_face_data=True, normal_update=True) - b_faces = [] - for face in bm_I.faces: - b_faces.append(face) - res = bmesh.ops.triangulate( - bm_I, faces=b_faces, quad_method=self.quad_mode, ngon_method=self.ngon_mode - ) - new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I) - bm_I.free() - else: - new_vertices_I,new_edges_I,new_faces_I = vertices_I, edges_I, faces_I - - result_vertices.append(new_vertices_I) - result_edges.append(new_edges_I) - result_polygons.append(new_faces_I) - - verts_I_np = np.array(new_vertices_I) - faces_I_np = np.array(new_faces_I) - tris_I_np = verts_I_np[faces_I_np] - areases_I = np.linalg.norm(np.cross(tris_I_np[:,1]-tris_I_np[:,0], tris_I_np[:,2]-tris_I_np[:,0]) / 2.0, axis=1) - area_I = areases_I.sum() - tris_centers_I = tris_I_np.sum(axis=1) / 3.0 - center_mass_mesh_I = (tris_centers_I * areases_I[np.newaxis].T).sum(axis=0) / area_I - mass_I = area_I * density_I[0] - - center_mass_mesh_list.append( center_mass_mesh_I.tolist() ) - mass_mesh_list.append(mass_I) - size_mesh_list.append(area_I) - - elif center_mode=='VOLUMES': - - faces_I_np = np.array(faces_I) - if faces_I_np.size==0: - # skip if no faces at all: - result_mask.append(False) - else: - do_calc = False - if max(map(len, faces_I_np))==3 and self.skip_test_volume_are_closed==True: - new_vertices_I, new_edges_I, new_faces_I = vertices_I, edges_I, faces_I - do_calc = True - else: - # triangulate mesh - bm_I = bmesh_from_pydata(vertices_I, edges_I, faces_I, markup_face_data=True, normal_update=True) - # test if all edges are contiguous (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMEdge.is_contiguous) - # then volume is closed: - for edge in bm_I.edges: - if edge.is_contiguous==False: - do_calc = False - break - else: - do_calc = True - # test if some polygons are not tris: - if max(map(len, faces_I_np))>3: - b_faces = [] - for face in bm_I.faces: - b_faces.append(face) - - res = bmesh.ops.triangulate( - bm_I, faces=b_faces, quad_method=self.quad_mode, ngon_method=self.ngon_mode - ) - new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I) - else: - new_vertices_I, new_edges_I, new_faces_I = vertices_I, edges_I, faces_I - bm_I.free() - - if do_calc==False: - result_mask.append(False) - else: - result_mask.append(True) - result_vertices.append(new_vertices_I) - result_edges .append(new_edges_I) - result_polygons.append(new_faces_I) - - verts_I = np.array(new_vertices_I) - faces_I = np.array(new_faces_I) - tris_I = verts_I[faces_I] - # to precise calc move all mesh to median point - tris_I_median = np.median(tris_I, axis=(0,1)) - tris_I_delta = tris_I-tris_I_median - signed_volumes_I = np_dot(tris_I_delta[:,0], np.cross(tris_I_delta[:,1], tris_I_delta[:,2])) - volume_I = signed_volumes_I.sum() - tetra_centers_I = tris_I_delta.sum(axis=1) / 4.0 - center_mass_mesh_I = (tetra_centers_I * signed_volumes_I[np.newaxis].T).sum(axis=0) / volume_I + tris_I_median - mass_I = volume_I * density_I[0] - - center_mass_mesh_list.append( center_mass_mesh_I.tolist() ) - mass_mesh_list.append(mass_I) - size_mesh_list.append(volume_I) + + result_mask, \ + result_vertices_I,\ + result_edges_I,\ + result_polygons_I, \ + result_center_mass_mesh_I, \ + result_mass_mesh_I, \ + result_size_mesh_I = calc_center_mass_bmesh(center_mode, + vertices_I, + edges_I, + faces_I, + mass_of_vertices_I, + density_I, + self.skip_test_volume_are_closed, + self.quad_mode, + self.ngon_mode) + result_mask_list.append(result_mask) + if result_mask==True: + result_vertices_list.append(result_vertices_I) + result_edges_list.append(result_edges_I) + result_polygons_list.append(result_polygons_I) + result_center_mass_mesh_list.append(result_center_mass_mesh_I) + mass_mesh_list.append(result_mass_mesh_I) + size_mesh_list.append(result_size_mesh_I) # calc center of mass of all meshes if any mesh has center: - if center_mass_mesh_list: - center_mass_mesh_list_np = np.array(center_mass_mesh_list) - mass_mesh_list_np = np.array([mass_mesh_list]) - mass_center_general_np = (center_mass_mesh_list_np*mass_mesh_list_np.T).sum(axis=0) / mass_mesh_list_np.sum() - mass_center_general = [[ mass_center_general_np.tolist() ]] - center_mass_mesh_list_out = [ [v] for v in center_mass_mesh_list ] - result_masses = [ [m] for m in mass_mesh_list ] - result_mass = mass_mesh_list_np.sum() - result_sizes = [ [s] for s in size_mesh_list ] - result_size = np.array(size_mesh_list).sum() + if result_center_mass_mesh_list: + result_center_mass_mesh_list_np = np.array(result_center_mass_mesh_list) + mass_mesh_list_np = np.array([mass_mesh_list]) + mass_center_general_np = (result_center_mass_mesh_list_np*mass_mesh_list_np.T).sum(axis=0) / mass_mesh_list_np.sum() + mass_center_general_out = [[ mass_center_general_np.tolist() ]] + result_center_mass_mesh_list_out = [ [v] for v in result_center_mass_mesh_list ] + result_masses_list_out = [ [m] for m in mass_mesh_list ] + result_mass_list_out = [[mass_mesh_list_np.sum()]] + result_sizes_out = [ [s] for s in size_mesh_list ] + result_size_out = [[np.array(size_mesh_list).sum()]] else: # if there is no any centers of mass - mass_center_general = [ [] ] - center_mass_mesh_list_out = [ [] ] - result_masses = [ [] ] - result_mass = 0 - result_sizes = [ [] ] - result_size = 0 - - self.outputs['output_vertices'].sv_set(result_vertices) - self.outputs['output_edges'].sv_set(result_edges) - self.outputs['output_polygons'].sv_set(result_polygons) - - self.outputs['output_centers_of_mass'].sv_set(center_mass_mesh_list_out) - self.outputs['output_total_center'].sv_set(mass_center_general) - self.outputs['output_sizes'].sv_set(result_sizes) - self.outputs['output_size'].sv_set([[result_size]]) - self.outputs['output_masses'].sv_set(result_masses) - self.outputs['output_mass'].sv_set([[result_mass]]) - self.outputs['output_mask'].sv_set(result_mask) - - unmanifold_centers_indices = [i for i, x in enumerate(result_mask) if not(x)] + mass_center_general_out = [ [] ] + result_center_mass_mesh_list_out = [ [] ] + result_masses_list_out = [ [] ] + result_mass_list_out = [ [0]] + result_sizes_out = [ [] ] + result_size_out = [ [0]] + + self.outputs['output_vertices'].sv_set(result_vertices_list) + self.outputs['output_edges'].sv_set(result_edges_list) + self.outputs['output_polygons'].sv_set(result_polygons_list) + + self.outputs['output_centers_of_mass'].sv_set(result_center_mass_mesh_list_out) + self.outputs['output_total_center'].sv_set(mass_center_general_out) + self.outputs['output_sizes'].sv_set(result_sizes_out) + self.outputs['output_size'].sv_set(result_size_out) + self.outputs['output_masses'].sv_set(result_masses_list_out) + self.outputs['output_mass'].sv_set(result_mass_list_out) + self.outputs['output_mask'].sv_set(result_mask_list) + + unmanifold_centers_indices = [i for i, x in enumerate(result_mask_list) if not(x)] if self.skip_unmanifold_centers==False: if unmanifold_centers_indices: str_error = f"Unmanifold centers are: [{','.join( map(str,unmanifold_centers_indices))}]" diff --git a/utils/sv_bmesh_utils.py b/utils/sv_bmesh_utils.py index 0f1d4bb321..daa18481a2 100644 --- a/utils/sv_bmesh_utils.py +++ b/utils/sv_bmesh_utils.py @@ -30,6 +30,7 @@ from sverchok.data_structure import zip_long_repeat, has_element from sverchok.utils.sv_logging import sv_logger +from sverchok.utils.math import np_dot @contextmanager def empty_bmesh(use_operators=True): @@ -912,3 +913,186 @@ def recalc_normals(verts, edges, faces, loop=False): verts, edges, faces = pydata_from_bmesh(bm) bm.free() return verts, edges, faces + +def calc_center_mass_bmesh(center_mode, vertices_I, edges_I, faces_I, mass_of_vertices_I=[1], density_I=[1], skip_test_volume_are_closed=False, quad_mode="BEAUTY", ngon_mode="BEAUTY"): + ''' + Calculate center of mass for single mesh. + + Input: + - center_mode=['VERTICES', 'EDGES', 'FACES', 'VOLUMES'] + - vertices_I = [[x1,y1,z1],[x2,y2,z2],...] (float) - vertices of mesh + - edges_I = [[0,1],[1,2],[2,3],...] (int) - indixes of verts (edges) + - faces_I = [[0,1,2],[1,2,3,4,...], ...] - indixes of verts (faces) + - mass_of_vertices_I = [ [ 1.1, 1.0, 5.2, 0.2,...] ] - mass of every vert in mesh. Extrapolate a last value to the all vertices + - density_I = [1.2] - density of volume. If center_mode is EDGES or FACES then mass of objects are proportional to length or area. + - skip_test_volume_are_closed - (only for volume node) If you know that volume are close then you can speed up performance if you set this parameter to True. False - force test mesh are closed. + - quad_mode [BEAUTY, FIXED, ALTERNATE, SHORT_EDGE], ngon_mode [BEAUTY, EAR_CLIP] - modes for triangulation if mesh has faces with 4 and more vertices (for center_mode FACES of VOLUMES only) + + Output: + + - result_mask - True/False. If False then another output params are None + - result_vertices_I, result_edges_I, result_polygons_I - result mesh (source mesh or triangulated mesh) + - result_center_mass_mesh_I - center of mass of mesh + - result_mass_mesh_I - mass of mesh + - result_size_mesh_I - for VERTICES - count vertices, for EDGES - length of edges, for FACES - area of mesh, for VOLUMES - volume of mesh + + Example: + https://github.com/nortikin/sverchok/assets/14288520/e432b5c0-35e5-432b-8c9f-798f58b71f13 + ''' + result_mask = result_vertices_I = result_edges_I = result_polygons_I = result_center_mass_mesh_I = result_mass_mesh_I = result_size_mesh_I = None + if len(vertices_I)==0: + # skip mesh if no vertices at all: + result_mask = False + + if center_mode=='VERTICES': + + if len(vertices_I)==0: + result_mask = False + else: + result_mask = True + result_vertices_I = vertices_I + result_edges_I = edges_I + result_polygons_I = faces_I + + # shrink or extend list of mass if list of mass is not equals list of verts: + mass_of_vertices_I_shrinked = mass_of_vertices_I[:len(vertices_I)] + mass_of_vertices_I_np1 = np.append( mass_of_vertices_I_shrinked, np.full( (len(vertices_I)-len(mass_of_vertices_I_shrinked)), mass_of_vertices_I_shrinked[-1]) ) + vertices_I_np = np.array(vertices_I) + mass_of_vertices_I_np2 = np.array([mass_of_vertices_I_np1]) + mass_I = mass_of_vertices_I_np2.sum() + center_mass_mesh_I_np = (vertices_I_np * mass_of_vertices_I_np2.T).sum(axis=0) / mass_I + + result_center_mass_mesh_I = center_mass_mesh_I_np.tolist() + result_mass_mesh_I = mass_I + result_size_mesh_I = vertices_I_np.shape[0] # Count of vertices + + elif center_mode=='EDGES': + + old_edges = np.array(edges_I) + if old_edges.size==0: + # skip mesh if no edges at all: + result_mask = False + else: + result_mask = True + verts_I = np.array(vertices_I) + + # Do not triangulate mesh for 'EDGE' mode + result_vertices_I = vertices_I + result_edges_I = edges_I + result_polygons_I = faces_I + + segment_I = verts_I[old_edges] + distances_I = np.linalg.norm(segment_I[:,1]-segment_I[:,0], axis=1) + length_I = distances_I.sum() + segment_center_I = segment_I.sum(axis=1) / 2.0 + center_mass_mesh_I_np = (segment_center_I * distances_I[np.newaxis].T).sum(axis=0) / length_I + mass_I = length_I * density_I[0] + + result_center_mass_mesh_I = center_mass_mesh_I_np.tolist() + result_mass_mesh_I = mass_I + result_size_mesh_I = length_I + + elif center_mode=='FACES': + + faces_I_np = np.array(faces_I) + if faces_I_np.size==0: + # skip mesh if no faces at all: + result_mask = False + else: + result_mask = True + + # test if some polygons are not tris: + if max(map(len, faces_I_np))>3: + # triangulate mesh for 'FACES' mode + bm_I = bmesh_from_pydata(vertices_I, edges_I, faces_I, markup_face_data=True, normal_update=True) + b_faces = [] + for face in bm_I.faces: + b_faces.append(face) + res = bmesh.ops.triangulate( + bm_I, faces=b_faces, quad_method=quad_mode, ngon_method=ngon_mode + ) + new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I) + bm_I.free() + else: + new_vertices_I,new_edges_I,new_faces_I = vertices_I, edges_I, faces_I + + result_vertices_I = new_vertices_I + result_edges_I = new_edges_I + result_polygons_I = new_faces_I + + verts_I_np = np.array(new_vertices_I) + faces_I_np = np.array(new_faces_I) + tris_I_np = verts_I_np[faces_I_np] + areases_I = np.linalg.norm(np.cross(tris_I_np[:,1]-tris_I_np[:,0], tris_I_np[:,2]-tris_I_np[:,0]) / 2.0, axis=1) + area_I = areases_I.sum() + tris_centers_I = tris_I_np.sum(axis=1) / 3.0 + center_mass_mesh_I_np = (tris_centers_I * areases_I[np.newaxis].T).sum(axis=0) / area_I + mass_I = area_I * density_I[0] + + result_center_mass_mesh_I = center_mass_mesh_I_np.tolist() + result_mass_mesh_I = mass_I + result_size_mesh_I = area_I + + elif center_mode=='VOLUMES': + + faces_I_np = np.array(faces_I) + if faces_I_np.size==0: + # skip mesh if no faces at all: + result_mask = False + else: + do_calc = False + if max(map(len, faces_I_np))==3 and skip_test_volume_are_closed==True: + new_vertices_I, new_edges_I, new_faces_I = vertices_I, edges_I, faces_I + do_calc = True + else: + # triangulate mesh + bm_I = bmesh_from_pydata(vertices_I, edges_I, faces_I, markup_face_data=True, normal_update=True) + # test if all edges are contiguous (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMEdge.is_contiguous) + # then volume is closed: + for edge in bm_I.edges: + if edge.is_contiguous==False: + do_calc = False + break + else: + do_calc = True + # test if some polygons are not tris: + if max(map(len, faces_I_np))>3: + b_faces = [] + for face in bm_I.faces: + b_faces.append(face) + + res = bmesh.ops.triangulate( + bm_I, faces=b_faces, quad_method=quad_mode, ngon_method=ngon_mode + ) + new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I) + else: + new_vertices_I, new_edges_I, new_faces_I = vertices_I, edges_I, faces_I + bm_I.free() + + if do_calc==False: + result_mask = False + else: + result_mask = True + result_vertices_I = new_vertices_I + result_edges_I = new_edges_I + result_polygons_I = new_faces_I + + verts_I = np.array(new_vertices_I) + faces_I = np.array(new_faces_I) + tris_I = verts_I[faces_I] + # to precise calc move all mesh to median point + tris_I_median = np.median(tris_I, axis=(0,1)) + tris_I_delta = tris_I-tris_I_median + signed_volumes_I = np_dot(tris_I_delta[:,0], np.cross(tris_I_delta[:,1], tris_I_delta[:,2])) + volume_I = signed_volumes_I.sum() + tetra_centers_I = tris_I_delta.sum(axis=1) / 4.0 + center_mass_mesh_I_np = (tetra_centers_I * signed_volumes_I[np.newaxis].T).sum(axis=0) / volume_I + tris_I_median + mass_I = volume_I * density_I[0] + + result_center_mass_mesh_I = center_mass_mesh_I_np.tolist() + result_mass_mesh_I = mass_I + result_size_mesh_I = volume_I + else: + raise Exception(f'calc_center_of_mesh: param \'center_mode\'={center_mode} must be in [\'VERTICES\', \'EDGES\', \'FACES\', \'VOLUMES\']') + + return result_mask, result_vertices_I, result_edges_I, result_polygons_I, result_center_mass_mesh_I, result_mass_mesh_I, result_size_mesh_I \ No newline at end of file From a6a586ca98ebb817895fa161a00dd730558d2727 Mon Sep 17 00:00:00 2001 From: satabol Date: Mon, 25 Mar 2024 07:43:09 +0300 Subject: [PATCH 5/8] - fix mutable value as default --- utils/sv_bmesh_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/utils/sv_bmesh_utils.py b/utils/sv_bmesh_utils.py index daa18481a2..1549bd7479 100644 --- a/utils/sv_bmesh_utils.py +++ b/utils/sv_bmesh_utils.py @@ -914,7 +914,7 @@ def recalc_normals(verts, edges, faces, loop=False): bm.free() return verts, edges, faces -def calc_center_mass_bmesh(center_mode, vertices_I, edges_I, faces_I, mass_of_vertices_I=[1], density_I=[1], skip_test_volume_are_closed=False, quad_mode="BEAUTY", ngon_mode="BEAUTY"): +def calc_center_mass_bmesh(center_mode, vertices_I, edges_I, faces_I, mass_of_vertices_I=None, density_I=None, skip_test_volume_are_closed=False, quad_mode="BEAUTY", ngon_mode="BEAUTY"): ''' Calculate center of mass for single mesh. @@ -940,6 +940,12 @@ def calc_center_mass_bmesh(center_mode, vertices_I, edges_I, faces_I, mass_of_ve https://github.com/nortikin/sverchok/assets/14288520/e432b5c0-35e5-432b-8c9f-798f58b71f13 ''' result_mask = result_vertices_I = result_edges_I = result_polygons_I = result_center_mass_mesh_I = result_mass_mesh_I = result_size_mesh_I = None + + if mass_of_vertices_I is None: + mass_of_vertices_I=[1] + if density_I is None: + density_I=[1] + if len(vertices_I)==0: # skip mesh if no vertices at all: result_mask = False From 482e161f836f50d53610afffd601af07227404b1 Mon Sep 17 00:00:00 2001 From: satabol Date: Mon, 25 Mar 2024 08:46:00 +0300 Subject: [PATCH 6/8] - rename output sockets - rename names of params in library function --- nodes/analyzer/weighted_vector_sum.py | 26 +++++++++++++------------- utils/sv_bmesh_utils.py | 24 +++++++++++++----------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/nodes/analyzer/weighted_vector_sum.py b/nodes/analyzer/weighted_vector_sum.py index 1ceca07e3a..f66705a140 100644 --- a/nodes/analyzer/weighted_vector_sum.py +++ b/nodes/analyzer/weighted_vector_sum.py @@ -47,17 +47,17 @@ class SvWeightedVectorSumNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveN def updateSizeSocket(self, context): if self.center_mode == 'VERTICES': - self.outputs['output_sizes'].label = 'Count of verts' - self.outputs['output_size' ].label = 'Total Verts' + self.outputs['output_sizes'].label = 'Count of Verts of Every Object' + self.outputs['output_size' ].label = 'Sum Verts of All Objects' elif self.center_mode == 'EDGES': - self.outputs['output_sizes'].label = 'Lengths' - self.outputs['output_size' ].label = 'Total Length' + self.outputs['output_sizes'].label = 'Lengths of Edges of Every Object' + self.outputs['output_size' ].label = 'Summ Length of All Objects' elif self.center_mode == 'FACES': - self.outputs['output_sizes'].label = 'Areas' - self.outputs['output_size' ].label = 'Total Area' + self.outputs['output_sizes'].label = 'Areas of Surfaces of Every Object' + self.outputs['output_size' ].label = 'Sum Area of All Objects' elif self.center_mode == 'VOLUMES': - self.outputs['output_sizes'].label = 'Volumes' - self.outputs['output_size' ].label = 'Total Volume' + self.outputs['output_sizes'].label = 'Volumes of Every Object' + self.outputs['output_size' ].label = 'Sum Volume of All Objects' updateNode(self, context) @@ -187,13 +187,13 @@ def sv_init(self, context): self.outputs['output_vertices'] .label = 'Vertices' self.outputs['output_edges'] .label = 'Edges' self.outputs['output_polygons'] .label = 'Polygons' - self.outputs['output_centers_of_mass'].label = 'Centers of mass of objects' - self.outputs['output_total_center'].label = 'Total center' + self.outputs['output_centers_of_mass'].label = 'Center mass of Every objects' + self.outputs['output_total_center'].label = 'Center mass of All objects' self.outputs['output_sizes'].label = '' self.outputs['output_size'] .label = '' - self.outputs['output_masses'] .label = 'Masses' - self.outputs['output_mass'] .label = 'Mass' - self.outputs['output_mask'] .label = 'Mask' + self.outputs['output_masses'] .label = 'Masses of Every Object' + self.outputs['output_mass'] .label = 'Mass of All Object' + self.outputs['output_mask'] .label = 'Mask Validity Every Object' self.updateSizeSocket(context) self.update_sockets(context) diff --git a/utils/sv_bmesh_utils.py b/utils/sv_bmesh_utils.py index 1549bd7479..c633697bc3 100644 --- a/utils/sv_bmesh_utils.py +++ b/utils/sv_bmesh_utils.py @@ -914,33 +914,35 @@ def recalc_normals(verts, edges, faces, loop=False): bm.free() return verts, edges, faces -def calc_center_mass_bmesh(center_mode, vertices_I, edges_I, faces_I, mass_of_vertices_I=None, density_I=None, skip_test_volume_are_closed=False, quad_mode="BEAUTY", ngon_mode="BEAUTY"): +def calc_center_mass_bmesh(center_mode, mesh_vertices, mesh_edges, mesh_faces, mass_of_vertices=None, object_density=None, skip_test_volume_are_closed=False, quad_mode="BEAUTY", ngon_mode="BEAUTY"): ''' Calculate center of mass for single mesh. Input: - center_mode=['VERTICES', 'EDGES', 'FACES', 'VOLUMES'] - - vertices_I = [[x1,y1,z1],[x2,y2,z2],...] (float) - vertices of mesh - - edges_I = [[0,1],[1,2],[2,3],...] (int) - indixes of verts (edges) - - faces_I = [[0,1,2],[1,2,3,4,...], ...] - indixes of verts (faces) - - mass_of_vertices_I = [ [ 1.1, 1.0, 5.2, 0.2,...] ] - mass of every vert in mesh. Extrapolate a last value to the all vertices - - density_I = [1.2] - density of volume. If center_mode is EDGES or FACES then mass of objects are proportional to length or area. + - mesh_vertices = [[x1,y1,z1],[x2,y2,z2],...] (float) - vertices of mesh + - mesh_edges = [[0,1],[1,2],[2,3],...] (int) - indixes of verts (edges) + - mesh_faces = [[0,1,2],[1,2,3,4,...], ...] - indixes of verts (faces) + - mass_of_vertices = [ [ 1.1, 1.0, 5.2, 0.2,...] ] - mass of every vert in mesh. Extrapolate a last value to the all vertices + - object_density = [1.2] - density of volume. If center_mode is EDGES or FACES then mass of objects are proportional to length or area. - skip_test_volume_are_closed - (only for volume node) If you know that volume are close then you can speed up performance if you set this parameter to True. False - force test mesh are closed. - quad_mode [BEAUTY, FIXED, ALTERNATE, SHORT_EDGE], ngon_mode [BEAUTY, EAR_CLIP] - modes for triangulation if mesh has faces with 4 and more vertices (for center_mode FACES of VOLUMES only) Output: - result_mask - True/False. If False then another output params are None - - result_vertices_I, result_edges_I, result_polygons_I - result mesh (source mesh or triangulated mesh) - - result_center_mass_mesh_I - center of mass of mesh - - result_mass_mesh_I - mass of mesh - - result_size_mesh_I - for VERTICES - count vertices, for EDGES - length of edges, for FACES - area of mesh, for VOLUMES - volume of mesh + - result_vertices, result_edges, result_polygons - result mesh (source mesh or triangulated mesh) + - result_center_mass_mesh - center of mass of mesh + - result_mass_mesh - mass of mesh + - result_size_mesh - for VERTICES - count vertices, for EDGES - length of edges, for FACES - area of mesh, for VOLUMES - volume of mesh Example: https://github.com/nortikin/sverchok/assets/14288520/e432b5c0-35e5-432b-8c9f-798f58b71f13 ''' result_mask = result_vertices_I = result_edges_I = result_polygons_I = result_center_mass_mesh_I = result_mass_mesh_I = result_size_mesh_I = None - + + vertices_I, edges_I, faces_I, mass_of_vertices_I, density_I = mesh_vertices, mesh_edges, mesh_faces, mass_of_vertices, object_density + if mass_of_vertices_I is None: mass_of_vertices_I=[1] if density_I is None: From 6296e39f7ffa09048660c17d09fc4096509f0da8 Mon Sep 17 00:00:00 2001 From: satabol Date: Fri, 29 Mar 2024 00:23:06 +0300 Subject: [PATCH 7/8] test fail building_docs_test.yml --- .github/workflows/building_docs_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/building_docs_test.yml b/.github/workflows/building_docs_test.yml index 29a17d4631..c05b1cad08 100644 --- a/.github/workflows/building_docs_test.yml +++ b/.github/workflows/building_docs_test.yml @@ -20,4 +20,4 @@ jobs: # https://www.sphinx-doc.org/en/master/man/sphinx-build.html # https://www.sphinx-doc.org/en/master/usage/builders/index.html?highlight=DummyBuilder#sphinx.builders.dummy.DummyBuilder build-command: sphinx-build -W --keep-going -b dummy . _test - pre-build-command: pip install sphinx-rtd-theme + pre-build-command: pip install sphinx-rtd-theme sphinx From ccdd1ed1155c1fae622e26f84db21a921af654d8 Mon Sep 17 00:00:00 2001 From: satabol Date: Fri, 29 Mar 2024 00:24:28 +0300 Subject: [PATCH 8/8] undo building_docs_test.yml --- .github/workflows/building_docs_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/building_docs_test.yml b/.github/workflows/building_docs_test.yml index c05b1cad08..29a17d4631 100644 --- a/.github/workflows/building_docs_test.yml +++ b/.github/workflows/building_docs_test.yml @@ -20,4 +20,4 @@ jobs: # https://www.sphinx-doc.org/en/master/man/sphinx-build.html # https://www.sphinx-doc.org/en/master/usage/builders/index.html?highlight=DummyBuilder#sphinx.builders.dummy.DummyBuilder build-command: sphinx-build -W --keep-going -b dummy . _test - pre-build-command: pip install sphinx-rtd-theme sphinx + pre-build-command: pip install sphinx-rtd-theme