blender-addons/mesh_tissue/lattice.py
Damien Picard 8db31f5cb9 Assign modifiers to variables instead of getting them by name
Using hardcoded names to retrieve modifiers can result in errors if
the user has enabled UI translation, because the new modifiers may not
have the expected English names if the UI is translated.

So, use the data API to create the modifiers instead of the ops API,
and assign nodes to variables instead of getting them by name.
2023-02-27 21:51:16 +01:00

476 lines
18 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
# --------------------------- LATTICE ALONG SURFACE -------------------------- #
# -------------------------------- version 0.3 ------------------------------- #
# #
# Automatically generate and assign a lattice that follows the active surface. #
# #
# (c) Alessandro Zomparelli #
# (2017) #
# #
# http://www.co-de-it.com/ #
# #
# ############################################################################ #
import bpy
import bmesh
from bpy.types import Operator
from bpy.props import (BoolProperty, StringProperty, FloatProperty)
from mathutils import Vector
from .utils import *
def not_in(element, grid):
output = True
for loop in grid:
if element in loop:
output = False
break
return output
def grid_from_mesh(mesh, swap_uv):
bm = bmesh.new()
bm.from_mesh(mesh)
verts_grid = []
edges_grid = []
faces_grid = []
running_grid = True
while running_grid:
verts_loop = []
edges_loop = []
faces_loop = []
# storing first point
verts_candidates = []
if len(faces_grid) == 0:
# for first loop check all vertices
verts_candidates = bm.verts
else:
# for other loops start form the vertices of the first face
# the last loop, skipping already used vertices
verts_candidates = [v for v in bm.faces[faces_grid[-1][0]].verts if not_in(v.index, verts_grid)]
# check for last loop
is_last = False
for vert in verts_candidates:
if len(vert.link_faces) == 1: # check if corner vertex
vert.select = True
verts_loop.append(vert.index)
is_last = True
break
if not is_last:
for vert in verts_candidates:
new_link_faces = [f for f in vert.link_faces if not_in(f.index, faces_grid)]
if len(new_link_faces) < 2: # check if corner vertex
vert.select = True
verts_loop.append(vert.index)
break
running_loop = len(verts_loop) > 0
while running_loop:
bm.verts.ensure_lookup_table()
id = verts_loop[-1]
link_edges = bm.verts[id].link_edges
# storing second point
if len(verts_loop) == 1: # only one vertex stored in the loop
if len(faces_grid) == 0: # first loop #
edge = link_edges[swap_uv] # chose direction
for vert in edge.verts:
if vert.index != id:
vert.select = True
verts_loop.append(vert.index) # new vertex
edges_loop.append(edge.index) # chosen edge
faces_loop.append(edge.link_faces[0].index) # only one face
# edge.link_faces[0].select = True
else: # other loops #
# start from the edges of the first face of the last loop
for edge in bm.faces[faces_grid[-1][0]].edges:
# chose an edge starting from the first vertex that is not returning back
if bm.verts[verts_loop[0]] in edge.verts and \
bm.verts[verts_grid[-1][0]] not in edge.verts:
for vert in edge.verts:
if vert.index != id:
vert.select = True
verts_loop.append(vert.index)
edges_loop.append(edge.index)
for face in edge.link_faces:
if not_in(face.index, faces_grid):
faces_loop.append(face.index)
# continuing the loop
else:
for edge in link_edges:
for vert in edge.verts:
store_data = False
if not_in(vert.index, verts_grid) and vert.index not in verts_loop:
if len(faces_loop) > 0:
bm.faces.ensure_lookup_table()
if vert not in bm.faces[faces_loop[-1]].verts:
store_data = True
else:
store_data = True
if store_data:
vert.select = True
verts_loop.append(vert.index)
edges_loop.append(edge.index)
for face in edge.link_faces:
if not_in(face.index, faces_grid):
faces_loop.append(face.index)
break
# ending condition
if verts_loop[-1] == id or verts_loop[-1] == verts_loop[0]:
running_loop = False
verts_grid.append(verts_loop)
edges_grid.append(edges_loop)
faces_grid.append(faces_loop)
if len(faces_loop) == 0:
running_grid = False
bm.free()
return verts_grid, edges_grid, faces_grid
class lattice_along_surface(Operator):
bl_idname = "object.lattice_along_surface"
bl_label = "Lattice along Surface"
bl_description = ("Automatically add a Lattice modifier to the selected "
"object, adapting it to the active one.\nThe active "
"object must be a rectangular grid compatible with the "
"Lattice's topology")
bl_options = {'REGISTER', 'UNDO'}
set_parent : BoolProperty(
name="Set Parent",
default=True,
description="Automatically set the Lattice as parent"
)
flipNormals : BoolProperty(
name="Flip Normals",
default=False,
description="Flip normals direction"
)
swapUV : BoolProperty(
name="Swap UV",
default=False,
description="Flip grid's U and V"
)
flipU : BoolProperty(
name="Flip U",
default=False,
description="Flip grid's U")
flipV : BoolProperty(
name="Flip V",
default=False,
description="Flip grid's V"
)
flipW : BoolProperty(
name="Flip W",
default=False,
description="Flip grid's W"
)
use_groups : BoolProperty(
name="Vertex Group",
default=False,
description="Use active Vertex Group for lattice's thickness"
)
high_quality_lattice : BoolProperty(
name="High quality",
default=True,
description="Increase the the subdivisions in normal direction for a "
"more correct result"
)
hide_lattice : BoolProperty(
name="Hide Lattice",
default=True,
description="Automatically hide the Lattice object"
)
scale_x : FloatProperty(
name="Scale X",
default=1,
min=0.001,
max=1,
description="Object scale"
)
scale_y : FloatProperty(
name="Scale Y", default=1,
min=0.001,
max=1,
description="Object scale"
)
scale_z : FloatProperty(
name="Scale Z",
default=1,
min=0.001,
max=1,
description="Object scale"
)
thickness : FloatProperty(
name="Thickness",
default=1,
soft_min=0,
soft_max=5,
description="Lattice thickness"
)
displace : FloatProperty(
name="Displace",
default=0,
soft_min=-1,
soft_max=1,
description="Lattice displace"
)
weight_factor : FloatProperty(
name="Factor",
default=0,
min=0.000,
max=1.000,
precision=3,
description="Thickness factor to use for zero vertex group influence"
)
grid_object = ""
source_object = ""
@classmethod
def poll(cls, context):
try: return context.object.mode == 'OBJECT'
except: return False
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.label(text="Thickness:")
col.prop(
self, "thickness", text="Thickness", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1
)
col.prop(
self, "displace", text="Offset", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1
)
row = col.row()
row.prop(self, "use_groups")
if self.use_groups:
row = col.row()
row.prop(self, "weight_factor")
col.separator()
col.label(text="Scale:")
col.prop(
self, "scale_x", text="U", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1
)
col.prop(
self, "scale_y", text="V", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1
)
col.separator()
col.label(text="Flip:")
row = col.row()
row.prop(self, "flipU", text="U")
row.prop(self, "flipV", text="V")
row.prop(self, "flipW", text="W")
col.prop(self, "swapUV")
col.prop(self, "flipNormals")
col.separator()
col.label(text="Lattice Options:")
col.prop(self, "high_quality_lattice")
col.prop(self, "hide_lattice")
col.prop(self, "set_parent")
def execute(self, context):
if self.source_object == self.grid_object == "" or True:
if len(context.selected_objects) != 2:
self.report({'ERROR'}, "Please, select two objects")
return {'CANCELLED'}
grid_obj = context.object
if grid_obj.type not in ('MESH', 'CURVE', 'SURFACE'):
self.report({'ERROR'}, "The surface object is not valid. Only Mesh,"
"Curve and Surface objects are allowed.")
return {'CANCELLED'}
obj = None
for o in context.selected_objects:
if o.name != grid_obj.name and o.type in \
('MESH', 'CURVE', 'SURFACE', 'FONT'):
obj = o
o.select_set(False)
break
try:
obj_dim = obj.dimensions
obj_me = simple_to_mesh(obj)#obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
except:
self.report({'ERROR'}, "The object to deform is not valid. Only "
"Mesh, Curve, Surface and Font objects are allowed.")
return {'CANCELLED'}
self.grid_object = grid_obj.name
self.source_object = obj.name
else:
grid_obj = bpy.data.objects[self.grid_object]
obj = bpy.data.objects[self.source_object]
obj_me = simple_to_mesh(obj)# obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
for o in context.selected_objects: o.select_set(False)
grid_obj.select_set(True)
context.view_layer.objects.active = grid_obj
temp_grid_obj = grid_obj.copy()
temp_grid_obj.data = simple_to_mesh(grid_obj)
grid_mesh = temp_grid_obj.data
for v in grid_mesh.vertices:
v.co = grid_obj.matrix_world @ v.co
grid_mesh.calc_normals()
if len(grid_mesh.polygons) > 64 * 64:
bpy.data.objects.remove(temp_grid_obj)
context.view_layer.objects.active = obj
obj.select_set(True)
self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
return {'CANCELLED'}
# CREATING LATTICE
min = Vector((0, 0, 0))
max = Vector((0, 0, 0))
first = True
for v in obj_me.vertices:
v0 = v.co.copy()
vert = obj.matrix_world @ v0
if vert[0] < min[0] or first:
min[0] = vert[0]
if vert[1] < min[1] or first:
min[1] = vert[1]
if vert[2] < min[2] or first:
min[2] = vert[2]
if vert[0] > max[0] or first:
max[0] = vert[0]
if vert[1] > max[1] or first:
max[1] = vert[1]
if vert[2] > max[2] or first:
max[2] = vert[2]
first = False
bb = max - min
lattice_loc = (max + min) / 2
bpy.ops.object.add(type='LATTICE')
lattice = context.active_object
lattice.location = lattice_loc
lattice.scale = Vector((bb.x / self.scale_x, bb.y / self.scale_y,
bb.z / self.scale_z))
if bb.x == 0:
lattice.scale.x = 1
if bb.y == 0:
lattice.scale.y = 1
if bb.z == 0:
lattice.scale.z = 1
context.view_layer.objects.active = obj
lattice_modifier = context.object.modifiers.new("", 'LATTICE')
lattice_modifier.object = lattice
# set as parent
if self.set_parent:
override = {'active_object': obj, 'selected_objects' : [lattice,obj]}
bpy.ops.object.parent_set(override, type='OBJECT', keep_transform=False)
# reading grid structure
verts_grid, edges_grid, faces_grid = grid_from_mesh(
grid_mesh,
swap_uv=self.swapUV
)
nu = len(verts_grid)
nv = len(verts_grid[0])
nw = 2
scale_normal = self.thickness
try:
lattice.data.points_u = nu
lattice.data.points_v = nv
lattice.data.points_w = nw
if self.use_groups:
vg = temp_grid_obj.vertex_groups.active
weight_factor = self.weight_factor
for i in range(nu):
for j in range(nv):
for w in range(nw):
if self.use_groups:
try:
weight_influence = vg.weight(verts_grid[i][j])
except:
weight_influence = 0
weight_influence = weight_influence * (1 - weight_factor) + weight_factor
displace = weight_influence * scale_normal * bb.z
else:
displace = scale_normal * bb.z
target_point = (grid_mesh.vertices[verts_grid[i][j]].co +
grid_mesh.vertices[verts_grid[i][j]].normal *
(w + self.displace / 2 - 0.5) * displace) - lattice.location
if self.flipW:
w = 1 - w
if self.flipU:
i = nu - i - 1
if self.flipV:
j = nv - j - 1
lattice.data.points[i + j * nu + w * nu * nv].co_deform.x = \
target_point.x / bpy.data.objects[lattice.name].scale.x
lattice.data.points[i + j * nu + w * nu * nv].co_deform.y = \
target_point.y / bpy.data.objects[lattice.name].scale.y
lattice.data.points[i + j * nu + w * nu * nv].co_deform.z = \
target_point.z / bpy.data.objects[lattice.name].scale.z
except:
bpy.ops.object.mode_set(mode='OBJECT')
temp_grid_obj.select_set(True)
lattice.select_set(True)
obj.select_set(False)
bpy.ops.object.delete(use_global=False)
context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.modifier_remove(modifier=lattice_modifier.name)
if nu > 64 or nv > 64:
self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
return {'CANCELLED'}
else:
self.report({'ERROR'}, "The grid mesh is not correct")
return {'CANCELLED'}
bpy.ops.object.mode_set(mode='OBJECT')
#grid_obj.select_set(True)
#lattice.select_set(False)
obj.select_set(False)
#bpy.ops.object.delete(use_global=False)
context.view_layer.objects.active = lattice
lattice.select_set(True)
if self.high_quality_lattice:
context.object.data.points_w = 8
else:
context.object.data.use_outside = True
if self.hide_lattice:
bpy.ops.object.hide_view_set(unselected=False)
context.view_layer.objects.active = obj
obj.select_set(True)
lattice.select_set(False)
if self.flipNormals:
try:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.flip_normals()
bpy.ops.object.mode_set(mode='OBJECT')
except:
pass
bpy.data.meshes.remove(grid_mesh)
bpy.data.meshes.remove(obj_me)
return {'FINISHED'}