Коммит 7f54f40b создал по автору Ilya Portnov's avatar Ilya Portnov
Просмотр файлов

"Relax mesh" node.

владелец b875f9b5
......@@ -470,6 +470,7 @@
SvInsetFaces
SvLatheNode
SvSmoothNode
SvRelaxMeshNode
SvSmoothLines
---
CrossSectionNode
......
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE
import bpy
from bpy.props import IntProperty, FloatProperty, BoolProperty, EnumProperty
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode, zip_long_repeat, throttle_and_update_node, get_data_nesting_level, ensure_nesting_level
from sverchok.utils.relax_mesh import *
class SvRelaxMeshNode(bpy.types.Node, SverchCustomTreeNode):
"""
Triggers: Relax Mesh
Tooltip: Relax mesh
"""
bl_idname = 'SvRelaxMeshNode'
bl_label = 'Relax Mesh'
bl_icon = 'MOD_SMOOTH'
iterations: IntProperty(
name="Iterations",
min=0,
max=1000, default=1, update=updateNode)
factor: FloatProperty(
name="Factor",
description="Smoothing factor",
min=0.0,
max=1.0,
default=0.5,
update=updateNode)
@throttle_and_update_node
def update_sockets(self, context):
self.inputs['Factor'].hide_safe = self.algorithm not in {'EDGES', 'FACES'}
algorithms = [
('LLOYD', "Lloyd", "Lloyd", 0),
('EDGES', "Edges", "Edges", 1),
('FACES', "Faces", "Faces", 2)
]
algorithm : EnumProperty(
name = "Algorithm",
items = algorithms,
default = 'LLOYD',
update = update_sockets)
def get_available_methods(self, context):
items = []
if self.algorithm in {'EDGES', 'FACES'}:
items.append((NONE, "Do not use", "Do not use", 0))
if self.algorithm == 'LLOYD':
items.append((LINEAR, "Linear", "Linear", 1))
items.append((NORMAL, "Normal", "Move points along mesh tangent only", 2))
items.append((BVH, "BVH", "Use BVH Tree", 3))
return items
preserve_shape : EnumProperty(
name = "Preserve shape",
items = get_available_methods,
update = updateNode)
skip_bounds : BoolProperty(
name = "Skip bounds",
description = "Leave boundary vertices where they were",
default = True,
update = updateNode)
targets = [
(AVERAGE, "Average", "Average", 0),
(MINIMUM, "Minimum", "Minimum", 1),
(MAXIMUM, "Maximum", "Maximum", 2)
]
target : EnumProperty(
name = "Target",
items = targets,
default = AVERAGE,
update = updateNode)
def sv_init(self, context):
self.inputs.new('SvVerticesSocket', "Vertices")
self.inputs.new('SvStringsSocket', 'Edges')
self.inputs.new('SvStringsSocket', 'Faces')
self.inputs.new('SvStringsSocket', 'VertMask')
self.inputs.new('SvStringsSocket', 'Iterations').prop_name = "iterations"
self.inputs.new('SvStringsSocket', 'Factor').prop_name = "factor"
self.outputs.new('SvVerticesSocket', 'Vertices')
#self.outputs.new('SvStringsSocket', 'Edges')
#self.outputs.new('SvStringsSocket', 'Faces')
self.update_sockets(context)
def draw_buttons(self, context, layout):
layout.prop(self, 'algorithm')
if self.algorithm in {'EDGES', 'FACES'}:
layout.prop(self, 'target')
layout.prop(self, 'preserve_shape')
layout.prop(self, 'skip_bounds')
def process(self):
if not any(output.is_linked for output in self.outputs):
return
vertices_s = self.inputs['Vertices'].sv_get(deepcopy=False)
edges_s = self.inputs['Edges'].sv_get(default=[[]], deepcopy=False)
faces_s = self.inputs['Faces'].sv_get(default=[[]], deepcopy=False)
masks_s = self.inputs['VertMask'].sv_get(default=[[1]], deepcopy=False)
iterations_s = self.inputs['Iterations'].sv_get(deepcopy=False)
factor_s = self.inputs['Factor'].sv_get(deepcopy=False)
input_level = get_data_nesting_level(vertices_s)
vertices_s = ensure_nesting_level(vertices_s, 4)
edges_s = ensure_nesting_level(edges_s, 4)
faces_s = ensure_nesting_level(faces_s, 4)
masks_s = ensure_nesting_level(masks_s, 3)
iterations_s = ensure_nesting_level(iterations_s, 2)
factor_s = ensure_nesting_level(factor_s, 2)
nested_output = input_level > 3
verts_out = []
for params in zip_long_repeat(vertices_s, edges_s, faces_s, masks_s, iterations_s, factor_s):
for vertices, edges, faces, mask, iterations, factor in zip_long_repeat(*params):
if self.algorithm == 'LLOYD':
vertices = lloyd_relax(vertices, faces, iterations,
mask = mask,
method = self.preserve_shape,
skip_boundary = self.skip_bounds)
elif self.algorithm == 'EDGES':
vertices = edges_relax(vertices, edges, faces, iterations,
k = factor,
mask = mask,
method = self.preserve_shape,
target = self.target,
skip_boundary = self.skip_bounds)
elif self.algorithm == 'FACES':
vertices = faces_relax(vertices, edges, faces, iterations,
k = factor,
mask = mask,
method = self.preserve_shape,
target = self.target,
skip_boundary = self.skip_bounds)
else:
raise Exception("Unsupported algorithm")
verts_out.append(vertices)
self.outputs['Vertices'].sv_set(verts_out)
def register():
bpy.utils.register_class(SvRelaxMeshNode)
def unregister():
bpy.utils.unregister_class(SvRelaxMeshNode)
......@@ -22,6 +22,7 @@ import bmesh.ops
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode, match_long_repeat, repeat_last_for_length
from sverchok.utils.sv_mesh_utils import get_unique_faces
from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh
......@@ -125,6 +126,8 @@ class SvTriangulateNode(bpy.types.Node, SverchCustomTreeNode):
new_face_data = []
bm.free()
new_faces = get_unique_faces(new_faces)
result_vertices.append(new_vertices)
result_edges.append(new_edges)
result_faces.append(new_faces)
......
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE
import numpy as np
from collections import defaultdict
from math import sqrt
import bmesh
import mathutils
from mathutils.bvhtree import BVHTree
from sverchok.data_structure import repeat_last_for_length
from sverchok.utils.sv_mesh_utils import polygons_to_edges
from sverchok.utils.sv_bmesh_utils import pydata_from_bmesh, bmesh_from_pydata
from sverchok.utils.geom import center, linear_approximation
NONE = 'NONE'
BVH = 'BVH'
LINEAR = 'LINEAR'
NORMAL = 'NORMAL'
MINIMUM = 'MIN'
MAXIMUM = 'MAX'
AVERAGE = 'MEAN'
def lloyd_relax(vertices, faces, iterations, mask=None, method=NORMAL, skip_boundary=True):
"""
supported shape preservation methods: NORMAL, LINEAR, BVH
"""
def do_iteration(bvh, bm):
verts_out = []
face_centers = np.array([face.calc_center_median() for face in bm.faces])
for bm_vert in bm.verts:
co = bm_vert.co
if (skip_boundary and bm_vert.is_boundary) or (mask is not None and not mask[bm_vert.index]):
new_vert = tuple(co)
else:
normal = bm_vert.normal
cs = np.array([face_centers[face.index] for face in bm_vert.link_faces])
if method == NORMAL:
median = mathutils.Vector(cs.mean(axis=0))
dv = median - co
dv = dv - dv.project(normal)
new_vert = co + dv
elif method == LINEAR:
approx = linear_approximation(cs)
median = mathutils.Vector(approx.center)
plane = approx.most_similar_plane()
dist = plane.distance_to_point(bm_vert.co)
new_vert = median + plane.normal.normalized() * dist
elif method == BVH:
median = mathutils.Vector(cs.mean(axis=0))
new_vert, normal, idx, dist = bvh.find_nearest(median)
else:
raise Exception("Unsupported volume preservation method")
new_vert = tuple(new_vert)
verts_out.append(new_vert)
return verts_out
if mask is not None:
mask = repeat_last_for_length(mask, len(vertices))
bvh = BVHTree.FromPolygons(vertices, faces)
for i in range(iterations):
bm = bmesh_from_pydata(vertices, [], faces, normal_update=True)
vertices = do_iteration(bvh, bm)
bm.free()
return vertices
def edges_relax(vertices, edges, faces, iterations, k, mask=None, method=NONE, target=AVERAGE, skip_boundary=True):
"""
supported shape preservation methods: NONE, NORMAL, BVH
"""
def do_iteration(bvh, bm, verts):
verts = np.asarray(verts)
v1s = verts[edges[:,0]]
v2s = verts[edges[:,1]]
edge_vecs = v2s - v1s
edge_lens = np.linalg.norm(edge_vecs, axis=1)
if target == MINIMUM:
target_len = np.min(edge_lens)
elif target == MAXIMUM:
target_len = np.max(edge_lens)
elif target == AVERAGE:
target_len = np.mean(edge_lens)
else:
raise Exception("Unsupported target edge length type")
forces = defaultdict(lambda: np.zeros((3,)))
counts = defaultdict(int)
for edge_idx, (v1_idx, v2_idx) in enumerate(edges):
edge_vec = edge_vecs[edge_idx]
edge_len = edge_lens[edge_idx]
d_len = (edge_len - target_len)/2.0
dv1 = d_len * edge_vec
dv2 = - d_len * edge_vec
forces[v1_idx] += dv1
forces[v2_idx] += dv2
counts[v1_idx] += 1
counts[v2_idx] += 1
target_verts = verts.copy()
for v_idx in range(len(verts)):
if skip_boundary and bm.verts[v_idx].is_boundary:
continue
if mask is not None and not mask[v_idx]:
continue
count = counts[v_idx]
if count:
forces[v_idx] /= count
target_verts[v_idx] += k*forces[v_idx]
if method == NONE:
verts_out = target_verts.tolist()
elif method == NORMAL:
verts_out = []
for bm_vert in bm.verts:
normal = bm_vert.normal
dv = mathutils.Vector(target_verts[bm_vert.index]) - bm_vert.co
dv = dv - dv.project(normal)
new_vert = tuple(bm_vert.co + dv)
verts_out.append(new_vert)
elif method == BVH:
verts_out = []
for vert in target_verts:
new_vert, normal, idx, dist = bvh.find_nearest(vert)
verts_out.append(tuple(new_vert))
else:
raise Exception("Unsupported shape preservation method")
return verts_out
if not edges:
edges = polygons_to_edges([faces], unique_edges=True)[0]
edges = np.array(edges)
if mask is not None:
mask = repeat_last_for_length(mask, len(vertices))
bvh = BVHTree.FromPolygons(vertices, faces)
for i in range(iterations):
bm = bmesh_from_pydata(vertices, edges, faces, normal_update=True)
vertices = do_iteration(bvh, bm, vertices)
bm.free()
return vertices
def faces_relax(vertices, edges, faces, iterations, k, mask=None, method=NONE, target=AVERAGE, skip_boundary=True):
"""
supported shape preservation methods: NONE, NORMAL, BVH
"""
def do_iteration(bvh, bm):
areas = np.array([face.calc_area() for face in bm.faces])
vert_cos = np.array([tuple(vert.co) for vert in bm.verts])
if target == MINIMUM:
target_area = areas.min()
elif target == MAXIMUM:
target_area = areas.max()
elif target == AVERAGE:
target_area = areas.mean()
else:
raise Exception("Unsupported target face area type")
forces = defaultdict(lambda: np.zeros((3,)))
counts = defaultdict(int)
for bm_face in bm.faces:
face_vert_idxs = [vert.index for vert in bm_face.verts]
face_verts = vert_cos[face_vert_idxs]
mean = face_verts.mean(axis=0)
face_verts_0 = face_verts - mean
src_area = areas[bm_face.index]
scale = sqrt(target_area / src_area)
dvs = (scale - 1) * face_verts_0
for vert_idx, dv in zip(face_vert_idxs, dvs):
forces[vert_idx] += dv
counts[vert_idx] += 1
target_verts = vert_cos.copy()
for bm_vert in bm.verts:
idx = bm_vert.index
if skip_boundary and bm_vert.is_boundary:
continue
if mask is not None and not mask[idx]:
continue
count = counts[idx]
if count:
forces[idx] /= count
force = forces[idx]
target_verts[idx] += k*force
if method == NONE:
verts_out = target_verts.tolist()
elif method == NORMAL:
verts_out = []
for bm_vert in bm.verts:
idx = bm_vert.index
dv = mathutils.Vector(target_verts[idx]) - bm_vert.co
normal = bm_vert.normal
dv = dv - dv.project(normal)
new_vert = tuple(bm_vert.co + dv)
verts_out.append(new_vert)
elif method == BVH:
verts_out = []
for bm_vert in bm.verts:
new_vert, normal, idx, dist = bvh.find_nearest(bm_vert.co)
verts_out.append(tuple(new_vert))
else:
raise Exception("Unsupported shape preservation method")
return verts_out
if mask is not None:
mask = repeat_last_for_length(mask, len(vertices))
bvh = BVHTree.FromPolygons(vertices, faces)
for i in range(iterations):
bm = bmesh_from_pydata(vertices, edges, faces, normal_update=True)
vertices = do_iteration(bvh, bm)
bm.free()
return vertices
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать