mirror of http://CODE.RHODECODE.COM/u/O/O/O
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4280 lines
166 KiB
4280 lines
166 KiB
# ##### 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 ##### |
|
|
|
# ---------------------------- ADAPTIVE DUPLIFACES --------------------------- # |
|
# ------------------------------- version 0.84 ------------------------------- # |
|
# # |
|
# Creates duplicates of selected mesh to active morphing the shape according # |
|
# to target faces. # |
|
# # |
|
# (c) Alessandro Zomparelli # |
|
# (2017) # |
|
# # |
|
# http://www.co-de-it.com/ # |
|
# # |
|
# ############################################################################ # |
|
|
|
|
|
import bpy |
|
from bpy.types import ( |
|
Operator, |
|
Panel, |
|
PropertyGroup, |
|
) |
|
from bpy.props import ( |
|
BoolProperty, |
|
EnumProperty, |
|
FloatProperty, |
|
IntProperty, |
|
StringProperty, |
|
PointerProperty |
|
) |
|
from mathutils import Vector |
|
import numpy as np |
|
from math import * |
|
import random, time, copy |
|
import bmesh |
|
from .utils import * |
|
|
|
def anim_tessellate_active(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
if not (props.bool_lock or props.bool_hold): |
|
try: |
|
props.generator.name |
|
props.component.name |
|
bpy.ops.object.tissue_update_tessellate() |
|
except: pass |
|
|
|
def anim_tessellate_object(ob): |
|
try: |
|
#bpy.context.view_layer.objects.active = ob |
|
bpy.ops.object.tissue_update_tessellate() |
|
except: |
|
return None |
|
|
|
#from bpy.app.handlers import persistent |
|
|
|
def anim_tessellate(scene): |
|
try: |
|
active_object = bpy.context.object |
|
old_mode = bpy.context.object.mode |
|
selected_objects = bpy.context.selected_objects |
|
except: active_object = old_mode = selected_objects = None |
|
if old_mode in ('OBJECT', 'PAINT_WEIGHT'): |
|
update_objects = [] |
|
for ob in scene.objects: |
|
if ob.tissue_tessellate.bool_run and not ob.tissue_tessellate.bool_lock: |
|
if ob not in update_objects: update_objects.append(ob) |
|
update_objects = list(reversed(update_dependencies(ob, update_objects))) |
|
for ob in update_objects: |
|
override = {'object': ob} |
|
''' |
|
win = bpy.data.window_managers[0].windows[0]#bpy.context.window |
|
scr = win.screen |
|
areas3d = [area for area in scr.areas if area.type == 'VIEW_3D'] |
|
region = [region for region in areas3d[0].regions if region.type == 'WINDOW'] |
|
override = { |
|
'window':win, |
|
'screen':scr, |
|
'area' :areas3d[0], |
|
'region':region[0], |
|
'scene' :scene, |
|
'object': ob |
|
} |
|
''' |
|
print(override) |
|
bpy.ops.object.tissue_update_tessellate(override) |
|
# restore selected objects |
|
if old_mode != None: |
|
for o in scene.objects: |
|
if not o.hide_viewport: o.select_set(o in selected_objects) |
|
bpy.context.view_layer.objects.active = active_object |
|
bpy.ops.object.mode_set(mode=old_mode) |
|
return |
|
|
|
|
|
def set_tessellate_handler(self, context): |
|
old_handlers = [] |
|
blender_handlers = bpy.app.handlers.frame_change_post |
|
for h in blender_handlers: |
|
if "anim_tessellate" in str(h): |
|
old_handlers.append(h) |
|
for h in old_handlers: blender_handlers.remove(h) |
|
for o in context.scene.objects: |
|
if o.tissue_tessellate.bool_run: |
|
blender_handlers.append(anim_tessellate) |
|
break |
|
return |
|
|
|
class tissue_tessellate_prop(PropertyGroup): |
|
bool_lock : BoolProperty( |
|
name="Lock", |
|
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.", |
|
default=False |
|
) |
|
bool_hold : BoolProperty( |
|
name="Hold", |
|
description="Wait...", |
|
default=False |
|
) |
|
bool_dependencies : BoolProperty( |
|
name="Update Dependencies", |
|
description="Automatically updates base and components as well, if results of other tessellations", |
|
default=False |
|
) |
|
bool_run : BoolProperty( |
|
name="Animatable Tessellation", |
|
description="Automatically recompute the tessellation when the frame is changed. Currently is not working during Render Animation", |
|
default = False, |
|
update = set_tessellate_handler |
|
) |
|
zscale : FloatProperty( |
|
name="Scale", default=1, soft_min=0, soft_max=10, |
|
description="Scale factor for the component thickness", |
|
update = anim_tessellate_active |
|
) |
|
scale_mode : EnumProperty( |
|
items=( |
|
('CONSTANT', "Constant", "Uniform thinkness"), |
|
('ADAPTIVE', "Relative", "Preserve component's proportions") |
|
), |
|
default='ADAPTIVE', |
|
name="Z-Scale according to faces size", |
|
update = anim_tessellate_active |
|
) |
|
offset : FloatProperty( |
|
name="Surface Offset", |
|
default=1, |
|
min=-1, |
|
max=1, |
|
soft_min=-1, |
|
soft_max=1, |
|
description="Surface offset", |
|
update = anim_tessellate_active |
|
) |
|
mode : EnumProperty( |
|
items=( |
|
('BOUNDS', "Bounds", "The component fits automatically the size of the target face"), |
|
('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"), |
|
('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")), |
|
default='BOUNDS', |
|
name="Component Mode", |
|
update = anim_tessellate_active |
|
) |
|
rotation_mode : EnumProperty( |
|
items=(('RANDOM', "Random", "Random faces rotation"), |
|
('UV', "Active UV", "Rotate according to UV coordinates"), |
|
('WEIGHT', "Active Weight", "Rotate according to Vertex Group gradient"), |
|
('DEFAULT', "Default", "Default rotation")), |
|
default='DEFAULT', |
|
name="Component Rotation", |
|
update = anim_tessellate_active |
|
) |
|
rotation_direction : EnumProperty( |
|
items=(('ORTHO', "Orthogonal", "Component main directions in XY"), |
|
('DIAG', "Diagonal", "Component main direction aligned with diagonal")), |
|
default='ORTHO', |
|
name="Direction", |
|
update = anim_tessellate_active |
|
) |
|
rotation_shift : IntProperty( |
|
name="Shift", |
|
default=0, |
|
soft_min=0, |
|
soft_max=3, |
|
description="Shift components rotation", |
|
update = anim_tessellate_active |
|
) |
|
fill_mode : EnumProperty( |
|
items=( |
|
('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'), |
|
('FAN', 'Fan', 'Radial tessellation for polygonal faces'), |
|
('PATCH', 'Patch', 'Curved tessellation according to the last ' + |
|
'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' + |
|
'patches.\nAfter the last Subsurf (or Multires) only ' + |
|
'deformation\nmodifiers can be used'), |
|
('FRAME', 'Frame', 'Essellation along the edges of each face')), |
|
default='QUAD', |
|
name="Fill Mode", |
|
update = anim_tessellate_active |
|
) |
|
combine_mode : EnumProperty( |
|
items=( |
|
('LAST', 'Last', 'Show only the last iteration'), |
|
('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'), |
|
('ALL', 'All', 'Combine the result of all iterations')), |
|
default='LAST', |
|
name="Combine Mode", |
|
update = anim_tessellate_active |
|
) |
|
gen_modifiers : BoolProperty( |
|
name="Generator Modifiers", |
|
default=False, |
|
description="Apply Modifiers and Shape Keys to the base object", |
|
update = anim_tessellate_active |
|
) |
|
com_modifiers : BoolProperty( |
|
name="Component Modifiers", |
|
default=False, |
|
description="Apply Modifiers and Shape Keys to the component object", |
|
update = anim_tessellate_active |
|
) |
|
merge : BoolProperty( |
|
name="Merge", |
|
default=False, |
|
description="Merge vertices in adjacent duplicates", |
|
update = anim_tessellate_active |
|
) |
|
merge_thres : FloatProperty( |
|
name="Distance", |
|
default=0.001, |
|
soft_min=0, |
|
soft_max=10, |
|
description="Limit below which to merge vertices", |
|
update = anim_tessellate_active |
|
) |
|
generator : PointerProperty( |
|
type=bpy.types.Object, |
|
name="", |
|
description="Base object for the tessellation", |
|
update = anim_tessellate_active |
|
) |
|
component : PointerProperty( |
|
type=bpy.types.Object, |
|
name="", |
|
description="Component object for the tessellation", |
|
#default="", |
|
update = anim_tessellate_active |
|
) |
|
bool_random : BoolProperty( |
|
name="Randomize", |
|
default=False, |
|
description="Randomize component rotation", |
|
update = anim_tessellate_active |
|
) |
|
random_seed : IntProperty( |
|
name="Seed", |
|
default=0, |
|
soft_min=0, |
|
soft_max=10, |
|
description="Random seed", |
|
update = anim_tessellate_active |
|
) |
|
bool_vertex_group : BoolProperty( |
|
name="Map Vertex Group", |
|
default=False, |
|
description="Transfer all Vertex Groups from Base object", |
|
update = anim_tessellate_active |
|
) |
|
bool_selection : BoolProperty( |
|
name="On selected Faces", |
|
default=False, |
|
description="Create Tessellation only on selected faces", |
|
update = anim_tessellate_active |
|
) |
|
bool_shapekeys : BoolProperty( |
|
name="Use Shape Keys", |
|
default=False, |
|
description="Transfer Component's Shape Keys. If the name of Vertex " |
|
"Groups and Shape Keys are the same, they will be " |
|
"automatically combined", |
|
update = anim_tessellate_active |
|
) |
|
bool_smooth : BoolProperty( |
|
name="Smooth Shading", |
|
default=False, |
|
description="Output faces with smooth shading rather than flat shaded", |
|
update = anim_tessellate_active |
|
) |
|
bool_materials : BoolProperty( |
|
name="Transfer Materials", |
|
default=False, |
|
description="Preserve component's materials", |
|
update = anim_tessellate_active |
|
) |
|
bool_material_id : BoolProperty( |
|
name="Tessellation on Material ID", |
|
default=False, |
|
description="Apply the component only on the selected Material", |
|
update = anim_tessellate_active |
|
) |
|
material_id : IntProperty( |
|
name="Material ID", |
|
default=0, |
|
min=0, |
|
description="Material ID", |
|
update = anim_tessellate_active |
|
) |
|
bool_dissolve_seams : BoolProperty( |
|
name="Dissolve Seams", |
|
default=False, |
|
description="Dissolve all seam edges", |
|
update = anim_tessellate_active |
|
) |
|
iterations : IntProperty( |
|
name="Iterations", |
|
default=1, |
|
min=1, |
|
soft_max=5, |
|
description="Automatically repeat the Tessellation using the " |
|
+ "generated geometry as new base object.\nUsefull for " |
|
+ "for branching systems. Dangerous!", |
|
update = anim_tessellate_active |
|
) |
|
bool_combine : BoolProperty( |
|
name="Combine unused", |
|
default=False, |
|
description="Combine the generated geometry with unused faces", |
|
update = anim_tessellate_active |
|
) |
|
bool_advanced : BoolProperty( |
|
name="Advanced Settings", |
|
default=False, |
|
description="Show more settings" |
|
) |
|
normals_mode : EnumProperty( |
|
items=( |
|
('VERTS', 'Normals', 'Consistent direction based on vertices normal'), |
|
('FACES', 'Individual Faces', 'Based on individual faces normal'), |
|
('CUSTOM', 'Custom', "According to Base object's shape keys")), |
|
default='VERTS', |
|
name="Direction", |
|
update = anim_tessellate_active |
|
) |
|
bool_multi_components : BoolProperty( |
|
name="Multi Components", |
|
default=False, |
|
description="Combine different components according to materials name", |
|
update = anim_tessellate_active |
|
) |
|
error_message : StringProperty( |
|
name="Error Message", |
|
default="" |
|
) |
|
warning_message : StringProperty( |
|
name="Warning Message", |
|
default="" |
|
) |
|
bounds_x : EnumProperty( |
|
items=( |
|
('EXTEND', 'Extend', 'Default X coordinates'), |
|
('CLIP', 'Clip', 'Trim out of bounds in X direction'), |
|
('CYCLIC', 'Cyclic', 'Cyclic components in X direction')), |
|
default='EXTEND', |
|
name="Bounds X", |
|
update = anim_tessellate_active |
|
) |
|
bounds_y : EnumProperty( |
|
items=( |
|
('EXTEND', 'Extend', 'Default Y coordinates'), |
|
('CLIP', 'Clip', 'Trim out of bounds in Y direction'), |
|
('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')), |
|
default='EXTEND', |
|
name="Bounds Y", |
|
update = anim_tessellate_active |
|
) |
|
close_mesh : EnumProperty( |
|
items=( |
|
('NONE', 'None', 'Keep the mesh open'), |
|
('CAP', 'Cap Holes', 'Automatically cap open loops'), |
|
('BRIDGE', 'Bridge Loops', 'Automatically bridge loop pairs')), |
|
default='NONE', |
|
name="Close Mesh", |
|
update = anim_tessellate_active |
|
) |
|
cap_faces : BoolProperty( |
|
name="Cap Holes", |
|
default=False, |
|
description="Cap open edges loops", |
|
update = anim_tessellate_active |
|
) |
|
frame_boundary : BoolProperty( |
|
name="Frame Boundary", |
|
default=False, |
|
description="Support face boundaries", |
|
update = anim_tessellate_active |
|
) |
|
fill_frame : BoolProperty( |
|
name="Fill Frame", |
|
default=False, |
|
description="Fill inner faces with Fan tessellation", |
|
update = anim_tessellate_active |
|
) |
|
frame_boundary_mat : IntProperty( |
|
name="Material Offset", |
|
default=0, |
|
description="Material Offset for boundaries", |
|
update = anim_tessellate_active |
|
) |
|
fill_frame_mat : IntProperty( |
|
name="Material Offset", |
|
default=0, |
|
description="Material Offset for inner faces", |
|
update = anim_tessellate_active |
|
) |
|
open_edges_crease : FloatProperty( |
|
name="Open Edges Crease", |
|
default=0, |
|
min=0, |
|
max=1, |
|
description="Automatically set crease for open edges", |
|
update = anim_tessellate_active |
|
) |
|
bridge_smoothness : FloatProperty( |
|
name="Smoothness", |
|
default=1, |
|
min=0, |
|
max=1, |
|
description="Bridge Smoothness", |
|
update = anim_tessellate_active |
|
) |
|
frame_thickness : FloatProperty( |
|
name="Frame Thickness", |
|
default=0.2, |
|
min=0, |
|
soft_max=2, |
|
description="Frame Thickness", |
|
update = anim_tessellate_active |
|
) |
|
frame_mode : EnumProperty( |
|
items=( |
|
('CONSTANT', 'Constant', 'Even thickness'), |
|
('RELATIVE', 'Relative', 'Frame offset depends on face areas')), |
|
default='CONSTANT', |
|
name="Offset", |
|
update = anim_tessellate_active |
|
) |
|
bridge_cuts : IntProperty( |
|
name="Cuts", |
|
default=0, |
|
min=0, |
|
max=20, |
|
description="Bridge Cuts", |
|
update = anim_tessellate_active |
|
) |
|
cap_material_index : IntProperty( |
|
name="Material", |
|
default=0, |
|
min=0, |
|
description="Material index for the cap/bridge faces", |
|
update = anim_tessellate_active |
|
) |
|
patch_subs : IntProperty( |
|
name="Patch Subdivisions", |
|
default=1, |
|
min=0, |
|
description="Subdivisions levels for Patch tessellation after the first iteration", |
|
update = anim_tessellate_active |
|
) |
|
|
|
def store_parameters(operator, ob): |
|
ob.tissue_tessellate.bool_hold = True |
|
ob.tissue_tessellate.bool_lock = operator.bool_lock |
|
ob.tissue_tessellate.bool_dependencies = operator.bool_dependencies |
|
ob.tissue_tessellate.generator = bpy.data.objects[operator.generator] |
|
ob.tissue_tessellate.component = bpy.data.objects[operator.component] |
|
ob.tissue_tessellate.zscale = operator.zscale |
|
ob.tissue_tessellate.offset = operator.offset |
|
ob.tissue_tessellate.gen_modifiers = operator.gen_modifiers |
|
ob.tissue_tessellate.com_modifiers = operator.com_modifiers |
|
ob.tissue_tessellate.mode = operator.mode |
|
ob.tissue_tessellate.rotation_mode = operator.rotation_mode |
|
ob.tissue_tessellate.rotation_shift = operator.rotation_shift |
|
ob.tissue_tessellate.rotation_direction = operator.rotation_direction |
|
ob.tissue_tessellate.merge = operator.merge |
|
ob.tissue_tessellate.merge_thres = operator.merge_thres |
|
ob.tissue_tessellate.scale_mode = operator.scale_mode |
|
ob.tissue_tessellate.bool_random = operator.bool_random |
|
ob.tissue_tessellate.random_seed = operator.random_seed |
|
ob.tissue_tessellate.fill_mode = operator.fill_mode |
|
ob.tissue_tessellate.bool_vertex_group = operator.bool_vertex_group |
|
ob.tissue_tessellate.bool_selection = operator.bool_selection |
|
ob.tissue_tessellate.bool_shapekeys = operator.bool_shapekeys |
|
ob.tissue_tessellate.bool_smooth = operator.bool_smooth |
|
ob.tissue_tessellate.bool_materials = operator.bool_materials |
|
ob.tissue_tessellate.bool_material_id = operator.bool_material_id |
|
ob.tissue_tessellate.material_id = operator.material_id |
|
ob.tissue_tessellate.bool_dissolve_seams = operator.bool_dissolve_seams |
|
ob.tissue_tessellate.iterations = operator.iterations |
|
ob.tissue_tessellate.bool_advanced = operator.bool_advanced |
|
ob.tissue_tessellate.normals_mode = operator.normals_mode |
|
ob.tissue_tessellate.bool_combine = operator.bool_combine |
|
ob.tissue_tessellate.bool_multi_components = operator.bool_multi_components |
|
ob.tissue_tessellate.combine_mode = operator.combine_mode |
|
ob.tissue_tessellate.bounds_x = operator.bounds_x |
|
ob.tissue_tessellate.bounds_y = operator.bounds_y |
|
ob.tissue_tessellate.cap_faces = operator.cap_faces |
|
ob.tissue_tessellate.close_mesh = operator.close_mesh |
|
ob.tissue_tessellate.bridge_cuts = operator.bridge_cuts |
|
ob.tissue_tessellate.bridge_smoothness = operator.bridge_smoothness |
|
ob.tissue_tessellate.frame_thickness = operator.frame_thickness |
|
ob.tissue_tessellate.frame_mode = operator.frame_mode |
|
ob.tissue_tessellate.frame_boundary = operator.frame_boundary |
|
ob.tissue_tessellate.fill_frame = operator.fill_frame |
|
ob.tissue_tessellate.frame_boundary_mat = operator.frame_boundary_mat |
|
ob.tissue_tessellate.fill_frame_mat = operator.fill_frame_mat |
|
ob.tissue_tessellate.cap_material_index = operator.cap_material_index |
|
ob.tissue_tessellate.patch_subs = operator.patch_subs |
|
ob.tissue_tessellate.bool_hold = False |
|
return ob |
|
|
|
def load_parameters(operator, ob): |
|
operator.bool_lock = ob.tissue_tessellate.bool_lock |
|
operator.bool_dependencies = ob.tissue_tessellate.bool_dependencies |
|
operator.generator = ob.tissue_tessellate.generator.name |
|
operator.component = ob.tissue_tessellate.component.name |
|
operator.zscale = ob.tissue_tessellate.zscale |
|
operator.offset = ob.tissue_tessellate.offset |
|
operator.gen_modifiers = ob.tissue_tessellate.gen_modifiers |
|
operator.com_modifiers = ob.tissue_tessellate.com_modifiers |
|
operator.mode = ob.tissue_tessellate.mode |
|
operator.rotation_mode = ob.tissue_tessellate.rotation_mode |
|
operator.rotation_shift = ob.tissue_tessellate.rotation_shift |
|
operator.rotation_direction = ob.tissue_tessellate.rotation_direction |
|
operator.merge = ob.tissue_tessellate.merge |
|
operator.merge_thres = ob.tissue_tessellate.merge_thres |
|
operator.scale_mode = ob.tissue_tessellate.scale_mode |
|
operator.bool_random = ob.tissue_tessellate.bool_random |
|
operator.random_seed = ob.tissue_tessellate.random_seed |
|
operator.fill_mode = ob.tissue_tessellate.fill_mode |
|
operator.bool_vertex_group = ob.tissue_tessellate.bool_vertex_group |
|
operator.bool_selection = ob.tissue_tessellate.bool_selection |
|
operator.bool_shapekeys = ob.tissue_tessellate.bool_shapekeys |
|
operator.bool_smooth = ob.tissue_tessellate.bool_smooth |
|
operator.bool_materials = ob.tissue_tessellate.bool_materials |
|
operator.bool_material_id = ob.tissue_tessellate.bool_material_id |
|
operator.material_id = ob.tissue_tessellate.material_id |
|
operator.bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams |
|
operator.iterations = ob.tissue_tessellate.iterations |
|
operator.bool_advanced = ob.tissue_tessellate.bool_advanced |
|
operator.normals_mode = ob.tissue_tessellate.normals_mode |
|
operator.bool_combine = ob.tissue_tessellate.bool_combine |
|
operator.bool_multi_components = ob.tissue_tessellate.bool_multi_components |
|
operator.combine_mode = ob.tissue_tessellate.combine_mode |
|
operator.bounds_x = ob.tissue_tessellate.bounds_x |
|
operator.bounds_y = ob.tissue_tessellate.bounds_y |
|
operator.cap_faces = ob.tissue_tessellate.cap_faces |
|
operator.close_mesh = ob.tissue_tessellate.close_mesh |
|
operator.bridge_cuts = ob.tissue_tessellate.bridge_cuts |
|
operator.bridge_smoothness = ob.tissue_tessellate.bridge_smoothness |
|
operator.cap_material_index = ob.tissue_tessellate.cap_material_index |
|
operator.patch_subs = ob.tissue_tessellate.patch_subs |
|
operator.frame_boundary = ob.tissue_tessellate.frame_boundary |
|
operator.fill_frame = ob.tissue_tessellate.fill_frame |
|
operator.frame_boundary_mat = ob.tissue_tessellate.frame_boundary_mat |
|
operator.fill_frame_mat = ob.tissue_tessellate.fill_frame_mat |
|
operator.frame_thickness = ob.tissue_tessellate.frame_thickness |
|
operator.frame_mode = ob.tissue_tessellate.frame_mode |
|
return ob |
|
|
|
def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode, |
|
scale_mode, rotation_mode, rotation_shift, rand_seed, bool_vertex_group, |
|
bool_selection, bool_shapekeys, bool_material_id, material_id, |
|
normals_mode, bounds_x, bounds_y): |
|
random.seed(rand_seed) |
|
|
|
if normals_mode == 'CUSTOM': |
|
if _ob0.data.shape_keys != None: |
|
ob0_sk = convert_object_to_mesh(_ob0) |
|
me0_sk = ob0_sk.data |
|
key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks] |
|
for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0 |
|
else: normals_mode = 'VERTS' |
|
|
|
ob0 = convert_object_to_mesh(_ob0) |
|
me0 = ob0.data |
|
|
|
# base normals |
|
normals0 = [] |
|
if normals_mode == 'CUSTOM': |
|
for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val |
|
for v0, v1 in zip(ob0.data.vertices, me0_sk.vertices): |
|
normals0.append(v1.co - v0.co) |
|
bpy.data.objects.remove(ob0_sk) |
|
else: |
|
ob0.data.update() |
|
normals0 = [v.normal for v in ob0.data.vertices] |
|
|
|
# ob0 = convert_object_to_mesh(_ob0) |
|
ob0.name = _ob0.name + "_apply_mod" |
|
me0 = _ob0.data |
|
|
|
# Check if zero faces are selected |
|
if _ob0.type == 'MESH': |
|
bool_cancel = True |
|
for p in me0.polygons: |
|
check_sel = check_mat = False |
|
if not bool_selection or p.select: check_sel = True |
|
if not bool_material_id or p.material_index == material_id: check_mat = True |
|
if check_sel and check_mat: |
|
bool_cancel = False |
|
break |
|
if bool_cancel: |
|
bpy.data.meshes.remove(ob0.data) |
|
#bpy.data.objects.remove(ob0) |
|
return 0 |
|
|
|
levels = 0 |
|
sculpt_levels = 0 |
|
render_levels = 0 |
|
bool_multires = False |
|
multires_name = "" |
|
not_allowed = ['FLUID_SIMULATION', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD', |
|
'DECIMATE', 'EDGE_SPLIT', 'MASK', 'MIRROR', 'REMESH', |
|
'SCREW', 'SOLIDIFY', 'TRIANGULATE', 'WIREFRAME', 'SKIN', |
|
'EXPLODE', 'PARTICLE_INSTANCE', 'PARTICLE_SYSTEM', 'SMOKE'] |
|
modifiers0 = list(_ob0.modifiers)#[m for m in ob0.modifiers] |
|
show_modifiers = [m.show_viewport for m in _ob0.modifiers] |
|
show_modifiers.reverse() |
|
modifiers0.reverse() |
|
for m in modifiers0: |
|
visible = m.show_viewport |
|
if not visible: continue |
|
#m.show_viewport = False |
|
if m.type in ('SUBSURF', 'MULTIRES') and visible: |
|
levels = m.levels |
|
multires_name = m.name |
|
if m.type == 'MULTIRES': |
|
bool_multires = True |
|
multires_name = m.name |
|
sculpt_levels = m.sculpt_levels |
|
render_levels = m.render_levels |
|
else: bool_multires = False |
|
break |
|
elif m.type in not_allowed: |
|
bpy.data.meshes.remove(ob0.data) |
|
#bpy.data.meshes.remove(me0) |
|
return "modifiers_error" |
|
|
|
before = _ob0.copy() |
|
before.name = _ob0.name + "_before_subs" |
|
bpy.context.collection.objects.link(before) |
|
#if ob0.type == 'MESH': before.data = me0 |
|
before_mod = list(before.modifiers) |
|
before_mod.reverse() |
|
for m in before_mod: |
|
if m.type in ('SUBSURF', 'MULTIRES') and m.show_viewport: |
|
before.modifiers.remove(m) |
|
break |
|
else: before.modifiers.remove(m) |
|
|
|
before_subsurf = simple_to_mesh(before) |
|
|
|
before_bm = bmesh.new() |
|
before_bm.from_mesh(before_subsurf) |
|
before_bm.faces.ensure_lookup_table() |
|
before_bm.edges.ensure_lookup_table() |
|
before_bm.verts.ensure_lookup_table() |
|
|
|
error = "" |
|
for f in before_bm.faces: |
|
if len(f.loops) != 4: |
|
error = "topology_error" |
|
break |
|
for e in before_bm.edges: |
|
if len(e.link_faces) == 0: |
|
error = "wires_error" |
|
break |
|
for v in before_bm.verts: |
|
if len(v.link_faces) == 0: |
|
error = "verts_error" |
|
break |
|
if error != "": |
|
bpy.data.meshes.remove(ob0.data) |
|
#bpy.data.meshes.remove(me0) |
|
bpy.data.meshes.remove(before_subsurf) |
|
bpy.data.objects.remove(before) |
|
return error |
|
|
|
me0 = ob0.data |
|
verts0 = me0.vertices # Collect generator vertices |
|
|
|
if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False |
|
|
|
# set Shape Keys to zero |
|
if bool_shapekeys or not com_modifiers: |
|
try: |
|
original_key_values = [] |
|
for sk in _ob1.data.shape_keys.key_blocks: |
|
original_key_values.append(sk.value) |
|
sk.value = 0 |
|
except: |
|
bool_shapekeys = False |
|
|
|
if not com_modifiers and not bool_shapekeys: |
|
mod_visibility = [] |
|
for m in _ob1.modifiers: |
|
mod_visibility.append(m.show_viewport) |
|
m.show_viewport = False |
|
com_modifiers = True |
|
|
|
ob1 = convert_object_to_mesh(_ob1, com_modifiers, False) |
|
me1 = ob1.data |
|
|
|
if mode != 'BOUNDS': |
|
ob1.active_shape_key_index = 0 |
|
# Bound X |
|
if bounds_x != 'EXTEND': |
|
if mode == 'GLOBAL': |
|
planes_co = ((0,0,0),(1,1,1)) |
|
plane_no = (1,0,0) |
|
if mode == 'LOCAL': |
|
planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0))) |
|
plane_no = planes_co[0]-planes_co[1] |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
for co in planes_co: |
|
bpy.ops.mesh.select_all(action='SELECT') |
|
bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no) |
|
bpy.ops.mesh.mark_seam() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
_faces = ob1.data.polygons |
|
if mode == 'GLOBAL': |
|
for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]: |
|
f.select = True |
|
for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]: |
|
f.select = True |
|
else: |
|
for f in [f for f in _faces if f.center.x > 1]: |
|
f.select = True |
|
for f in [f for f in _faces if f.center.x < 0]: |
|
f.select = True |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode(type='FACE') |
|
if bounds_x == 'CLIP': |
|
bpy.ops.mesh.delete(type='FACE') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
if bounds_x == 'CYCLIC': |
|
bpy.ops.mesh.split() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
# Bound Y |
|
if bounds_y != 'EXTEND': |
|
if mode == 'GLOBAL': |
|
planes_co = ((0,0,0),(1,1,1)) |
|
plane_no = (0,1,0) |
|
if mode == 'LOCAL': |
|
planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0))) |
|
plane_no = planes_co[0]-planes_co[1] |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
for co in planes_co: |
|
bpy.ops.mesh.select_all(action='SELECT') |
|
bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no) |
|
bpy.ops.mesh.mark_seam() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
_faces = ob1.data.polygons |
|
if mode == 'GLOBAL': |
|
for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]: |
|
f.select = True |
|
for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]: |
|
f.select = True |
|
else: |
|
for f in [f for f in _faces if f.center.y > 1]: |
|
f.select = True |
|
for f in [f for f in _faces if f.center.y < 0]: |
|
f.select = True |
|
|
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode(type='FACE') |
|
if bounds_y == 'CLIP': |
|
bpy.ops.mesh.delete(type='FACE') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
if bounds_y == 'CYCLIC': |
|
bpy.ops.mesh.split() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
|
|
# Component statistics |
|
n_verts = len(me1.vertices) |
|
|
|
# Create empty lists |
|
new_verts = [] |
|
new_edges = [] |
|
new_faces = [] |
|
new_verts_np = np.array(()) |
|
|
|
# Component bounding box |
|
min_c = Vector((0, 0, 0)) |
|
max_c = Vector((0, 0, 0)) |
|
first = True |
|
for v in me1.vertices: |
|
vert = v.co |
|
if vert[0] < min_c[0] or first: |
|
min_c[0] = vert[0] |
|
if vert[1] < min_c[1] or first: |
|
min_c[1] = vert[1] |
|
if vert[2] < min_c[2] or first: |
|
min_c[2] = vert[2] |
|
if vert[0] > max_c[0] or first: |
|
max_c[0] = vert[0] |
|
if vert[1] > max_c[1] or first: |
|
max_c[1] = vert[1] |
|
if vert[2] > max_c[2] or first: |
|
max_c[2] = vert[2] |
|
first = False |
|
bb = max_c - min_c |
|
|
|
# adaptive XY |
|
verts1 = [] |
|
for v in me1.vertices: |
|
if mode == 'BOUNDS': |
|
vert = v.co - min_c # (ob1.matrix_world * v.co) - min_c |
|
vert[0] = vert[0] / bb[0] if bb[0] != 0 else 0.5 |
|
vert[1] = vert[1] / bb[1] if bb[1] != 0 else 0.5 |
|
vert[2] = vert[2] / bb[2] if bb[2] != 0 else 0 |
|
vert[2] = (vert[2] - 0.5 + offset * 0.5) * zscale |
|
elif mode == 'LOCAL': |
|
vert = v.co.xyz |
|
vert[2] *= zscale |
|
#vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale |
|
elif mode == 'GLOBAL': |
|
vert = ob1.matrix_world @ v.co |
|
vert[2] *= zscale |
|
try: |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co |
|
except: pass |
|
#verts1.append(vert) |
|
v.co = vert |
|
|
|
# Bounds X, Y |
|
if mode != 'BOUNDS': |
|
if bounds_x == 'CYCLIC': |
|
move_verts = [] |
|
for f in [f for f in me1.polygons if (f.center).x > 1]: |
|
for v in f.vertices: |
|
if v not in move_verts: move_verts.append(v) |
|
for v in move_verts: |
|
me1.vertices[v].co.x -= 1 |
|
try: |
|
_ob1.active_shape_key_index = 0 |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v].co.x -= 1 |
|
except: pass |
|
move_verts = [] |
|
for f in [f for f in me1.polygons if (f.center).x < 0]: |
|
for v in f.vertices: |
|
if v not in move_verts: move_verts.append(v) |
|
for v in move_verts: |
|
me1.vertices[v].co.x += 1 |
|
try: |
|
_ob1.active_shape_key_index = 0 |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v].co.x += 1 |
|
except: pass |
|
if bounds_y == 'CYCLIC': |
|
move_verts = [] |
|
for f in [f for f in me1.polygons if (f.center).y > 1]: |
|
for v in f.vertices: |
|
if v not in move_verts: move_verts.append(v) |
|
for v in move_verts: |
|
me1.vertices[v].co.y -= 1 |
|
try: |
|
_ob1.active_shape_key_index = 0 |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v].co.y -= 1 |
|
except: pass |
|
move_verts = [] |
|
for f in [f for f in me1.polygons if (f.center).y < 0]: |
|
for v in f.vertices: |
|
if v not in move_verts: move_verts.append(v) |
|
for v in move_verts: |
|
me1.vertices[v].co.y += 1 |
|
try: |
|
_ob1.active_shape_key_index = 0 |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v].co.y += 1 |
|
except: pass |
|
verts1 = [v.co for v in me1.vertices] |
|
n_verts1 = len(verts1) |
|
|
|
patch_faces = 4**levels |
|
sides = int(sqrt(patch_faces)) |
|
step = 1/sides |
|
sides0 = sides-2 |
|
patch_faces0 = int((sides-2)**2) |
|
n_patches = int(len(me0.polygons)/patch_faces) |
|
if len(me0.polygons)%patch_faces != 0: |
|
#ob0.data = old_me0 |
|
return "topology_error" |
|
|
|
new_verts = [] |
|
new_edges = [] |
|
new_faces = [] |
|
|
|
for o in bpy.context.view_layer.objects: o.select_set(False) |
|
new_patch = None |
|
|
|
# All vertex group |
|
if bool_vertex_group: |
|
try: |
|
weight = [] |
|
for vg in ob0.vertex_groups: |
|
_weight = [] |
|
for v in me0.vertices: |
|
try: |
|
_weight.append(vg.weight(v.index)) |
|
except: |
|
_weight.append(0) |
|
weight.append(_weight) |
|
except: |
|
bool_vertex_group = False |
|
|
|
# Adaptive Z |
|
if scale_mode == 'ADAPTIVE': |
|
com_area = bb[0]*bb[1] |
|
if mode != 'BOUNDS' or com_area == 0: com_area = 1 |
|
#mult = 1/com_area |
|
verts_area = [] |
|
bm = bmesh.new() |
|
bm.from_mesh(me0) |
|
bm.verts.ensure_lookup_table() |
|
for v in bm.verts: |
|
area = 0 |
|
faces = v.link_faces |
|
for f in faces: |
|
area += f.calc_area() |
|
area = area/len(faces)*patch_faces/com_area |
|
#area*=mult* |
|
verts_area.append(sqrt(area)*bb[2]) |
|
|
|
random.seed(rand_seed) |
|
bool_correct = False |
|
|
|
_faces = [[[0] for ii in range(sides)] for jj in range(sides)] |
|
_verts = [[[0] for ii in range(sides+1)] for jj in range(sides+1)] |
|
|
|
# find relative UV component's vertices |
|
verts1_uv_quads = [0]*len(verts1) |
|
verts1_uv = [0]*len(verts1) |
|
for i, vert in enumerate(verts1): |
|
# grid coordinates |
|
u = int(vert[0]//step) |
|
v = int(vert[1]//step) |
|
u1 = min(u+1, sides) |
|
v1 = min(v+1, sides) |
|
if mode != 'BOUNDS': |
|
if u > sides-1: |
|
u = sides-1 |
|
u1 = sides |
|
if u < 0: |
|
u = 0 |
|
u1 = 1 |
|
if v > sides-1: |
|
v = sides-1 |
|
v1 = sides |
|
if v < 0: |
|
v = 0 |
|
v1 = 1 |
|
verts1_uv_quads[i] = (u,v,u1,v1) |
|
# factor coordinates |
|
fu = (vert[0]-u*step)/step |
|
fv = (vert[1]-v*step)/step |
|
fw = vert.z |
|
# interpolate Z scaling factor |
|
verts1_uv[i] = Vector((fu,fv,fw)) |
|
|
|
sk_uv_quads = [] |
|
sk_uv = [] |
|
if bool_shapekeys: |
|
for sk in ob1.data.shape_keys.key_blocks: |
|
source = sk.data |
|
_sk_uv_quads = [0]*len(verts1) |
|
_sk_uv = [0]*len(verts1) |
|
for i, sk_v in enumerate(source): |
|
if mode == 'BOUNDS': |
|
sk_vert = sk_v.co - min_c |
|
sk_vert[0] = (sk_vert[0] / bb[0] if bb[0] != 0 else 0.5) |
|
sk_vert[1] = (sk_vert[1] / bb[1] if bb[1] != 0 else 0.5) |
|
sk_vert[2] = (sk_vert[2] / bb[2] if bb[2] != 0 else sk_vert[2]) |
|
sk_vert[2] = (sk_vert[2] - 0.5 + offset * 0.5) * zscale |
|
elif mode == 'LOCAL': |
|
sk_vert = sk_v.co |
|
sk_vert[2] *= zscale |
|
elif mode == 'GLOBAL': |
|
sk_vert = sk_v.co |
|
sk_vert[2] *= zscale |
|
|
|
# grid coordinates |
|
u = int(sk_vert[0]//step) |
|
v = int(sk_vert[1]//step) |
|
u1 = min(u+1, sides) |
|
v1 = min(v+1, sides) |
|
if mode != 'BOUNDS': |
|
if u > sides-1: |
|
u = sides-1 |
|
u1 = sides |
|
if u < 0: |
|
u = 0 |
|
u1 = 1 |
|
if v > sides-1: |
|
v = sides-1 |
|
v1 = sides |
|
if v < 0: |
|
v = 0 |
|
v1 = 1 |
|
_sk_uv_quads[i] = (u,v,u1,v1) |
|
# factor coordinates |
|
fu = (sk_vert[0]-u*step)/step |
|
fv = (sk_vert[1]-v*step)/step |
|
fw = sk_vert.z |
|
_sk_uv[i] = Vector((fu,fv,fw)) |
|
sk_uv_quads.append(_sk_uv_quads) |
|
sk_uv.append(_sk_uv) |
|
|
|
for i in range(n_patches): |
|
poly = me0.polygons[i*patch_faces] |
|
if bool_selection and not poly.select: continue |
|
if bool_material_id and not poly.material_index == material_id: continue |
|
|
|
bool_correct = True |
|
new_patch = bpy.data.objects.new("patch", me1.copy()) |
|
bpy.context.collection.objects.link(new_patch) |
|
|
|
new_patch.select_set(True) |
|
bpy.context.view_layer.objects.active = new_patch |
|
|
|
for area in bpy.context.screen.areas: |
|
for space in area.spaces: |
|
try: new_patch.local_view_set(space, True) |
|
except: pass |
|
|
|
# Vertex Group |
|
if bool_vertex_group: |
|
for vg in ob0.vertex_groups: |
|
new_patch.vertex_groups.new(name=vg.name) |
|
|
|
# find patch faces |
|
faces = _faces.copy() |
|
verts = _verts.copy() |
|
shift1 = sides |
|
shift2 = sides*2-1 |
|
shift3 = sides*3-2 |
|
for j in range(patch_faces): |
|
if j < patch_faces0: |
|
if levels == 0: |
|
u = j%sides0 |
|
v = j//sides0 |
|
else: |
|
u = j%sides0+1 |
|
v = j//sides0+1 |
|
elif j < patch_faces0 + shift1: |
|
u = j-patch_faces0 |
|
v = 0 |
|
elif j < patch_faces0 + shift2: |
|
u = sides-1 |
|
v = j-(patch_faces0 + sides)+1 |
|
elif j < patch_faces0 + shift3: |
|
jj = j-(patch_faces0 + shift2) |
|
u = sides-jj-2 |
|
v = sides-1 |
|
else: |
|
jj = j-(patch_faces0 + shift3) |
|
u = 0 |
|
v = sides-jj-2 |
|
face = me0.polygons[j+i*patch_faces] |
|
faces[u][v] = face |
|
verts[u][v] = verts0[face.vertices[0]] |
|
if u == sides-1: |
|
verts[sides][v] = verts0[face.vertices[1]] |
|
if v == sides-1: |
|
verts[u][sides] = verts0[face.vertices[3]] |
|
if u == v == sides-1: |
|
verts[sides][sides] = verts0[face.vertices[2]] |
|
|
|
# Random rotation |
|
if rotation_mode == 'RANDOM' or rotation_shift != 0: |
|
if rotation_mode == 'RANDOM': rot = random.randint(0, 3) |
|
else: rot = rotation_shift%4 |
|
if rot == 1: |
|
verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)] |
|
elif rot == 2: |
|
verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)] |
|
elif rot == 3: |
|
verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)] |
|
|
|
# UV rotation |
|
if rotation_mode == 'UV' and ob0.type == 'MESH': |
|
if len(ob0.data.uv_layers) > 0: |
|
uv0 = me0.uv_layers.active.data[faces[0][0].index*4].uv |
|
uv1 = me0.uv_layers.active.data[faces[0][-1].index*4 + 3].uv |
|
uv2 = me0.uv_layers.active.data[faces[-1][-1].index*4 + 2].uv |
|
uv3 = me0.uv_layers.active.data[faces[-1][0].index*4 + 1].uv |
|
v01 = (uv0 + uv1) |
|
v32 = (uv3 + uv2) |
|
v0132 = v32 - v01 |
|
v0132.normalize() |
|
v12 = (uv1 + uv2) |
|
v03 = (uv0 + uv3) |
|
v1203 = v03 - v12 |
|
v1203.normalize() |
|
|
|
vertUV = [] |
|
dot1203 = v1203.x |
|
dot0132 = v0132.x |
|
if(abs(dot1203) < abs(dot0132)): |
|
if (dot0132 > 0): |
|
pass |
|
else: |
|
verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)] |
|
else: |
|
if(dot1203 < 0): |
|
verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)] |
|
else: |
|
verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)] |
|
|
|
if True: |
|
verts_xyz = np.array([[v.co for v in _verts] for _verts in verts]) |
|
#verts_norm = np.array([[v.normal for v in _verts] for _verts in verts]) |
|
verts_norm = np.array([[normals0[v.index] for v in _verts] for _verts in verts]) |
|
if normals_mode == 'FACES': |
|
verts_norm = np.mean(verts_norm, axis=(0,1)) |
|
verts_norm = np.expand_dims(verts_norm, axis=0) |
|
verts_norm = np.repeat(verts_norm,len(verts),axis=0) |
|
verts_norm = np.expand_dims(verts_norm, axis=0) |
|
verts_norm = np.repeat(verts_norm,len(verts),axis=0) |
|
np_verts1_uv = np.array(verts1_uv) |
|
verts1_uv_quads = np.array(verts1_uv_quads) |
|
u = verts1_uv_quads[:,0] |
|
v = verts1_uv_quads[:,1] |
|
u1 = verts1_uv_quads[:,2] |
|
v1 = verts1_uv_quads[:,3] |
|
v00 = verts_xyz[u,v] |
|
v10 = verts_xyz[u1,v] |
|
v01 = verts_xyz[u,v1] |
|
v11 = verts_xyz[u1,v1] |
|
n00 = verts_norm[u,v] |
|
n10 = verts_norm[u1,v] |
|
n01 = verts_norm[u,v1] |
|
n11 = verts_norm[u1,v1] |
|
vx = np_verts1_uv[:,0].reshape((n_verts1,1)) |
|
vy = np_verts1_uv[:,1].reshape((n_verts1,1)) |
|
vz = np_verts1_uv[:,2].reshape((n_verts1,1)) |
|
co2 = np_lerp2(v00,v10,v01,v11,vx,vy) |
|
n2 = np_lerp2(n00,n10,n01,n11,vx,vy) |
|
if scale_mode == 'ADAPTIVE': |
|
areas = np.array([[verts_area[v.index] for v in verts_v] for verts_v in verts]) |
|
a00 = areas[u,v].reshape((n_verts1,1)) |
|
a10 = areas[u1,v].reshape((n_verts1,1)) |
|
a01 = areas[u,v1].reshape((n_verts1,1)) |
|
a11 = areas[u1,v1].reshape((n_verts1,1)) |
|
# remapped z scale |
|
a2 = np_lerp2(a00,a10,a01,a11,vx,vy) |
|
co3 = co2 + n2 * vz * a2 |
|
else: |
|
co3 = co2 + n2 * vz |
|
coordinates = co3.flatten().tolist() |
|
new_patch.data.vertices.foreach_set('co',coordinates) |
|
|
|
# vertex groups |
|
if bool_vertex_group: |
|
for _weight, vg in zip(weight, new_patch.vertex_groups): |
|
np_weight = np.array([[_weight[v.index] for v in verts_v] for verts_v in verts]) |
|
w00 = np_weight[u,v].reshape((n_verts1,1)) |
|
w10 = np_weight[u1,v].reshape((n_verts1,1)) |
|
w01 = np_weight[u,v1].reshape((n_verts1,1)) |
|
w11 = np_weight[u1,v1].reshape((n_verts1,1)) |
|
# remapped z scale |
|
w2 = np_lerp2(w00,w10,w01,w11,vx,vy) |
|
for vert_id in range(n_verts1): |
|
vg.add([vert_id], w2[vert_id], "ADD") |
|
|
|
if bool_shapekeys: |
|
for i_sk, sk in enumerate(ob1.data.shape_keys.key_blocks): |
|
np_verts1_uv = np.array(sk_uv[i_sk]) |
|
np_sk_uv_quads = np.array(sk_uv_quads[i_sk]) |
|
u = np_sk_uv_quads[:,0] |
|
v = np_sk_uv_quads[:,1] |
|
u1 = np_sk_uv_quads[:,2] |
|
v1 = np_sk_uv_quads[:,3] |
|
v00 = verts_xyz[u,v] |
|
v10 = verts_xyz[u1,v] |
|
v01 = verts_xyz[u,v1] |
|
v11 = verts_xyz[u1,v1] |
|
vx = np_verts1_uv[:,0].reshape((n_verts1,1)) |
|
vy = np_verts1_uv[:,1].reshape((n_verts1,1)) |
|
vz = np_verts1_uv[:,2].reshape((n_verts1,1)) |
|
co2 = np_lerp2(v00,v10,v01,v11,vx,vy) |
|
n2 = np_lerp2(n00,n10,n01,n11,vx,vy) |
|
if scale_mode == 'ADAPTIVE': |
|
areas = np.array([[verts_area[v.index] for v in verts_v] for verts_v in verts]) |
|
a00 = areas[u,v].reshape((n_verts1,1)) |
|
a10 = areas[u1,v].reshape((n_verts1,1)) |
|
a01 = areas[u,v1].reshape((n_verts1,1)) |
|
a11 = areas[u1,v1].reshape((n_verts1,1)) |
|
# remapped z scale |
|
a2 = np_lerp2(a00,a10,a01,a11,vx,vy) |
|
co3 = co2 + n2 * vz * a2 |
|
else: |
|
co3 = co2 + n2 * vz |
|
coordinates = co3.flatten().tolist() |
|
new_patch.data.shape_keys.key_blocks[sk.name].data.foreach_set('co', coordinates) |
|
#new_patch.data.shape_keys.key_blocks[sk.name].data[i_vert].co = sk_co |
|
else: |
|
for _fvec, uv_quad, patch_vert in zip(verts1_uv, verts1_uv_quads, new_patch.data.vertices): |
|
u = uv_quad[0] |
|
v = uv_quad[1] |
|
u1 = uv_quad[2] |
|
v1 = uv_quad[3] |
|
v00 = verts[u][v] |
|
v10 = verts[u1][v] |
|
v01 = verts[u][v1] |
|
v11 = verts[u1][v1] |
|
# interpolate Z scaling factor |
|
fvec = _fvec.copy() |
|
if scale_mode == 'ADAPTIVE': |
|
a00 = verts_area[v00.index] |
|
a10 = verts_area[v10.index] |
|
a01 = verts_area[v01.index] |
|
a11 = verts_area[v11.index] |
|
fvec[2]*=lerp2(a00,a10,a01,a11,fvec) |
|
# interpolate vertex on patch |
|
patch_vert.co = lerp3(v00, v10, v01, v11, fvec) |
|
|
|
# Vertex Group |
|
if bool_vertex_group: |
|
for _weight, vg in zip(weight, new_patch.vertex_groups): |
|
w00 = _weight[v00.index] |
|
w10 = _weight[v10.index] |
|
w01 = _weight[v01.index] |
|
w11 = _weight[v11.index] |
|
wuv = lerp2(w00,w10,w01,w11, fvec) |
|
vg.add([patch_vert.index], wuv, "ADD") |
|
|
|
if bool_shapekeys: |
|
for i_sk, sk in enumerate(ob1.data.shape_keys.key_blocks): |
|
for i_vert, _fvec, _sk_uv_quad in zip(range(len(new_patch.data.vertices)), sk_uv[i_sk], sk_uv_quads[i_sk]): |
|
u = _sk_uv_quad[0] |
|
v = _sk_uv_quad[1] |
|
u1 = _sk_uv_quad[2] |
|
v1 = _sk_uv_quad[3] |
|
v00 = verts[u][v] |
|
v10 = verts[u1][v] |
|
v01 = verts[u][v1] |
|
v11 = verts[u1][v1] |
|
|
|
fvec = _fvec.copy() |
|
if scale_mode == 'ADAPTIVE': |
|
a00 = verts_area[v00.index] |
|
a10 = verts_area[v10.index] |
|
a01 = verts_area[v01.index] |
|
a11 = verts_area[v11.index] |
|
fvec[2]*=lerp2(a00, a10, a01, a11, fvec) |
|
sk_co = lerp3(v00, v10, v01, v11, fvec) |
|
|
|
new_patch.data.shape_keys.key_blocks[sk.name].data[i_vert].co = sk_co |
|
|
|
#if ob0.type == 'MESH': ob0.data = old_me0 |
|
if not bool_correct: return 0 |
|
|
|
bpy.ops.object.join() |
|
|
|
|
|
if bool_shapekeys: |
|
# set original values and combine Shape Keys and Vertex Groups |
|
for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): |
|
sk.value = val |
|
new_patch.data.shape_keys.key_blocks[sk.name].value = val |
|
if bool_vertex_group: |
|
for sk in new_patch.data.shape_keys.key_blocks: |
|
for vg in new_patch.vertex_groups: |
|
if sk.name == vg.name: |
|
sk.vertex_group = vg.name |
|
else: |
|
try: |
|
for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): |
|
sk.value = val |
|
except: pass |
|
|
|
new_name = ob0.name + "_" + ob1.name |
|
new_patch.name = "tessellate_temp" |
|
|
|
if bool_multires: |
|
for m in ob0.modifiers: |
|
if m.type == 'MULTIRES' and m.name == multires_name: |
|
m.levels = levels |
|
m.sculpt_levels = sculpt_levels |
|
m.render_levels = render_levels |
|
# restore original modifiers visibility for component object |
|
try: |
|
for m, vis in zip(_ob1.modifiers, mod_visibility): |
|
m.show_viewport = vis |
|
except: pass |
|
|
|
bpy.data.objects.remove(before) |
|
bpy.data.objects.remove(ob0) |
|
bpy.data.objects.remove(ob1) |
|
return new_patch |
|
|
|
def tessellate_original(_ob0, _ob1, offset, zscale, gen_modifiers, com_modifiers, mode, |
|
scale_mode, rotation_mode, rotation_shift, rotation_direction, rand_seed, fill_mode, |
|
bool_vertex_group, bool_selection, bool_shapekeys, |
|
bool_material_id, material_id, normals_mode, bounds_x, bounds_y): |
|
|
|
if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False |
|
random.seed(rand_seed) |
|
|
|
if bool_shapekeys: |
|
try: |
|
original_key_values = [] |
|
for sk in _ob1.data.shape_keys.key_blocks: |
|
original_key_values.append(sk.value) |
|
sk.value = 0 |
|
except: |
|
bool_shapekeys = False |
|
|
|
if normals_mode == 'CUSTOM': |
|
if _ob0.data.shape_keys != None: |
|
ob0_sk = convert_object_to_mesh(_ob0, True, True) |
|
me0_sk = ob0_sk.data |
|
key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks] |
|
for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0 |
|
else: normals_mode == 'VERTS' |
|
|
|
ob0 = convert_object_to_mesh(_ob0, gen_modifiers, True) |
|
me0 = ob0.data |
|
ob1 = convert_object_to_mesh(_ob1, com_modifiers, True) |
|
me1 = ob1.data |
|
|
|
# base normals |
|
normals0 = [] |
|
if normals_mode == 'CUSTOM' and _ob0.data.shape_keys != None: |
|
for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val |
|
for v0, v1 in zip(me0.vertices, me0_sk.vertices): |
|
normals0.append(v1.co - v0.co) |
|
bpy.data.objects.remove(ob0_sk) |
|
else: |
|
me0.update() |
|
normals0 = [v.normal for v in me0.vertices] |
|
|
|
base_polygons = [] |
|
base_face_normals = [] |
|
|
|
n_faces0 = len(me0.polygons) |
|
|
|
# Check if zero faces are selected |
|
if (bool_selection and ob0.type == 'MESH') or bool_material_id: |
|
for p in me0.polygons: |
|
if (bool_selection and ob0.type == 'MESH'): |
|
is_sel = p.select |
|
else: is_sel = True |
|
if bool_material_id: |
|
is_mat = p.material_index == material_id |
|
else: is_mat = True |
|
if is_sel and is_mat: |
|
base_polygons.append(p) |
|
base_face_normals.append(p.normal) |
|
else: |
|
base_polygons = me0.polygons |
|
base_face_normals = [p.normal for p in me0.polygons] |
|
|
|
# numpy test: slower |
|
#base_face_normals = np.zeros(n_faces0*3) |
|
#me0.polygons.foreach_get("normal", base_face_normals) |
|
#base_face_normals = base_face_normals.reshape((n_faces0,3)) |
|
|
|
if len(base_polygons) == 0: |
|
bpy.data.objects.remove(ob0) |
|
bpy.data.objects.remove(ob1) |
|
bpy.data.meshes.remove(me1) |
|
bpy.data.meshes.remove(me0) |
|
return 0 |
|
|
|
if mode != 'BOUNDS': |
|
|
|
bpy.ops.object.select_all(action='DESELECT') |
|
for o in bpy.context.view_layer.objects: o.select_set(False) |
|
bpy.context.view_layer.objects.active = ob1 |
|
ob1.select_set(True) |
|
ob1.active_shape_key_index = 0 |
|
# Bound X |
|
if bounds_x != 'EXTEND': |
|
if mode == 'GLOBAL': |
|
planes_co = ((0,0,0),(1,1,1)) |
|
plane_no = (1,0,0) |
|
if mode == 'LOCAL': |
|
planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0))) |
|
plane_no = planes_co[0]-planes_co[1] |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
for co in planes_co: |
|
bpy.ops.mesh.select_all(action='SELECT') |
|
bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no) |
|
bpy.ops.mesh.mark_seam() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
_faces = ob1.data.polygons |
|
if mode == 'GLOBAL': |
|
for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]: |
|
f.select = True |
|
for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]: |
|
f.select = True |
|
else: |
|
for f in [f for f in _faces if f.center.x > 1]: |
|
f.select = True |
|
for f in [f for f in _faces if f.center.x < 0]: |
|
f.select = True |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode(type='FACE') |
|
if bounds_x == 'CLIP': |
|
bpy.ops.mesh.delete(type='FACE') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
if bounds_x == 'CYCLIC': |
|
bpy.ops.mesh.split() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
# Bound Y |
|
if bounds_y != 'EXTEND': |
|
if mode == 'GLOBAL': |
|
planes_co = ((0,0,0),(1,1,1)) |
|
plane_no = (0,1,0) |
|
if mode == 'LOCAL': |
|
planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0))) |
|
plane_no = planes_co[0]-planes_co[1] |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
for co in planes_co: |
|
bpy.ops.mesh.select_all(action='SELECT') |
|
bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no) |
|
bpy.ops.mesh.mark_seam() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
_faces = ob1.data.polygons |
|
if mode == 'GLOBAL': |
|
for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]: |
|
f.select = True |
|
for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]: |
|
f.select = True |
|
else: |
|
for f in [f for f in _faces if f.center.y > 1]: |
|
f.select = True |
|
for f in [f for f in _faces if f.center.y < 0]: |
|
f.select = True |
|
|
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode(type='FACE') |
|
if bounds_y == 'CLIP': |
|
bpy.ops.mesh.delete(type='FACE') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
if bounds_y == 'CYCLIC': |
|
bpy.ops.mesh.split() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
#ob1 = new_ob1 |
|
|
|
me1 = ob1.data |
|
|
|
verts0 = me0.vertices # Collect generator vertices |
|
|
|
# Component statistics |
|
n_verts1 = len(me1.vertices) |
|
n_edges1 = len(me1.edges) |
|
n_faces1 = len(me1.polygons) |
|
|
|
# Create empty lists |
|
new_verts = [] |
|
new_edges = [] |
|
new_faces = [] |
|
new_verts_np = np.array(()) |
|
|
|
# Component Coordinates |
|
co1 = [0]*n_verts1*3 |
|
|
|
if mode == 'GLOBAL': |
|
for v in me1.vertices: |
|
v.co = ob1.matrix_world @ v.co |
|
try: |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co |
|
except: pass |
|
if mode != 'BOUNDS': |
|
if bounds_x == 'CYCLIC': |
|
move_verts = [] |
|
for f in [f for f in me1.polygons if (f.center).x > 1]: |
|
for v in f.vertices: |
|
if v not in move_verts: move_verts.append(v) |
|
for v in move_verts: |
|
me1.vertices[v].co.x -= 1 |
|
try: |
|
_ob1.active_shape_key_index = 0 |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v].co.x -= 1 |
|
except: pass |
|
move_verts = [] |
|
for f in [f for f in me1.polygons if (f.center).x < 0]: |
|
for v in f.vertices: |
|
if v not in move_verts: move_verts.append(v) |
|
for v in move_verts: |
|
me1.vertices[v].co.x += 1 |
|
try: |
|
_ob1.active_shape_key_index = 0 |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v].co.x += 1 |
|
except: pass |
|
if bounds_y == 'CYCLIC': |
|
move_verts = [] |
|
for f in [f for f in me1.polygons if (f.center).y > 1]: |
|
for v in f.vertices: |
|
if v not in move_verts: move_verts.append(v) |
|
for v in move_verts: |
|
me1.vertices[v].co.y -= 1 |
|
try: |
|
#new_ob1.active_shape_key_index = 0 |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v].co.y -= 1 |
|
except: pass |
|
move_verts = [] |
|
for f in [f for f in me1.polygons if (f.center).y < 0]: |
|
for v in f.vertices: |
|
if v not in move_verts: move_verts.append(v) |
|
for v in move_verts: |
|
me1.vertices[v].co.y += 1 |
|
try: |
|
#new_ob1.active_shape_key_index = 0 |
|
for sk in me1.shape_keys.key_blocks: |
|
sk.data[v].co.y += 1 |
|
except: pass |
|
if len(me1.vertices) == 0: |
|
bpy.data.objects.remove(ob0) |
|
bpy.data.objects.remove(ob1) |
|
return 0 |
|
|
|
me1.vertices.foreach_get("co", co1) |
|
co1 = np.array(co1) |
|
vx = co1[0::3].reshape((n_verts1,1)) |
|
vy = co1[1::3].reshape((n_verts1,1)) |
|
vz = co1[2::3].reshape((n_verts1,1)) |
|
min_c = Vector((vx.min(), vy.min(), vz.min())) # Min BB Corner |
|
max_c = Vector((vx.max(), vy.max(), vz.max())) # Max BB Corner |
|
bb = max_c - min_c # Bounding Box |
|
|
|
# Component Coordinates |
|
if mode == 'BOUNDS': |
|
vx = (vx - min_c[0]) / bb[0] if bb[0] != 0 else 0.5 |
|
vy = (vy - min_c[1]) / bb[1] if bb[1] != 0 else 0.5 |
|
vz = (vz - min_c[2]) / bb[2] if bb[2] != 0 else 0 |
|
vz = (vz - 0.5 + offset * 0.5) * zscale |
|
#vz = ((vz - min_c[2]) + (-0.5 + offset * 0.5) * bb[2]) * zscale |
|
else: |
|
vz *= zscale |
|
|
|
# Component polygons |
|
fs1 = [[i for i in p.vertices] for p in me1.polygons] |
|
new_faces = fs1[:] |
|
|
|
# Component edges |
|
es1 = np.array([[i for i in e.vertices] for e in me1.edges]) |
|
#es1 = [[i for i in e.vertices] for e in me1.edges if e.is_loose] |
|
new_edges = es1[:] |
|
|
|
# SHAPE KEYS |
|
if bool_shapekeys: |
|
basis = True #com_modifiers |
|
vx_key = [] |
|
vy_key = [] |
|
vz_key = [] |
|
sk_np = [] |
|
for sk in ob1.data.shape_keys.key_blocks: |
|
do_shapekeys = True |
|
# set all keys to 0 |
|
for _sk in ob1.data.shape_keys.key_blocks: _sk.value = 0 |
|
sk.value = 1 |
|
|
|
if basis: |
|
basis = False |
|
continue |
|
|
|
# Apply component modifiers |
|
if com_modifiers: |
|
sk_ob = convert_object_to_mesh(_ob1) |
|
sk_data = sk_ob.data |
|
source = sk_data.vertices |
|
else: |
|
source = sk.data |
|
|
|
shapekeys = [] |
|
for v in source: |
|
if mode == 'BOUNDS': |
|
vert = v.co - min_c |
|
vert[0] = (vert[0] / bb[0] if bb[0] != 0 else 0.5) |
|
vert[1] = (vert[1] / bb[1] if bb[1] != 0 else 0.5) |
|
vert[2] = (vert[2] / bb[2] if bb[2] != 0 else vert[2]) |
|
vert[2] = (vert[2] - 0.5 + offset * 0.5) * zscale |
|
elif mode == 'LOCAL': |
|
vert = v.co.xyz |
|
vert[2] *= zscale |
|
#vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \ |
|
# zscale |
|
elif mode == 'GLOBAL': |
|
vert = v.co.xyz |
|
#vert = ob1.matrix_world @ v.co |
|
vert[2] *= zscale |
|
shapekeys.append(vert) |
|
|
|
# Component vertices |
|
key1 = np.array([v for v in shapekeys]).reshape(len(shapekeys), 3, 1) |
|
vx_key.append(key1[:, 0]) |
|
vy_key.append(key1[:, 1]) |
|
vz_key.append(key1[:, 2]) |
|
#sk_np.append([]) |
|
|
|
# All vertex group |
|
if bool_vertex_group or rotation_mode == 'WEIGHT': |
|
try: |
|
weight = [] |
|
for vg in ob0.vertex_groups: |
|
_weight = [] |
|
for i,v in enumerate(me0.vertices): |
|
try: |
|
_weight.append(vg.weight(i)) |
|
except: |
|
_weight.append(0) |
|
weight.append(_weight) |
|
except: |
|
bool_vertex_group = False |
|
|
|
# Adaptive Z |
|
if scale_mode == 'ADAPTIVE': |
|
com_area = bb[0]*bb[1] |
|
if mode != 'BOUNDS' or com_area == 0: com_area = 1 |
|
verts_area = [] |
|
bm = bmesh.new() |
|
bm.from_mesh(me0) |
|
bm.verts.ensure_lookup_table() |
|
for v in bm.verts: |
|
area = 0 |
|
faces = v.link_faces |
|
for f in faces: |
|
area += f.calc_area() |
|
try: |
|
area/=len(faces) # average area |
|
area/=com_area |
|
verts_area.append(sqrt(area)*bb[2]) |
|
#verts_area.append(area) |
|
except: |
|
verts_area.append(1) |
|
|
|
count = 0 # necessary for UV calculation |
|
|
|
# TESSELLATION |
|
j = 0 |
|
jj = -1 |
|
bool_correct = False |
|
|
|
# optimization test |
|
n_faces = len(base_polygons) |
|
_vs0 = [0]*n_faces |
|
_nvs0 = [0]*n_faces |
|
_sz = [0]*n_faces |
|
n_vg = len(ob0.vertex_groups) |
|
_w0 = [[0]*n_faces for i in range(n_vg)] |
|
np_faces = [np.array(p) for p in fs1] |
|
new_faces = [0]*n_faces*n_faces1 |
|
face1_count = 0 |
|
|
|
for j, p in enumerate(base_polygons): |
|
|
|
bool_correct = True |
|
if rotation_mode in ['UV', 'WEIGHT'] and ob0.type != 'MESH': |
|
rotation_mode = 'DEFAULT' |
|
|
|
ordered = p.vertices |
|
|
|
# Random rotation |
|
if rotation_mode == 'RANDOM': |
|
shifted_vertices = [] |
|
n_poly_verts = len(p.vertices) |
|
rand = random.randint(0, n_poly_verts) |
|
for i in range(n_poly_verts): |
|
shifted_vertices.append(p.vertices[(i + rand) % n_poly_verts]) |
|
if scale_mode == 'ADAPTIVE': |
|
verts_area0 = np.array([verts_area[i] for i in shifted_vertices]) |
|
ordered = shifted_vertices |
|
|
|
# UV rotation |
|
elif rotation_mode == 'UV': |
|
if len(ob0.data.uv_layers) > 0: |
|
i = p.index |
|
if bool_material_id: |
|
count = sum([len(p.vertices) for p in me0.polygons[:i]]) |
|
#if i == 0: count = 0 |
|
v01 = (me0.uv_layers.active.data[count].uv + |
|
me0.uv_layers.active.data[count + 1].uv) |
|
if len(p.vertices) > 3: |
|
v32 = (me0.uv_layers.active.data[count + 3].uv + |
|
me0.uv_layers.active.data[count + 2].uv) |
|
else: |
|
v32 = (me0.uv_layers.active.data[count].uv + |
|
me0.uv_layers.active.data[count + 2].uv) |
|
v0132 = v32 - v01 |
|
v0132.normalize() |
|
|
|
v12 = (me0.uv_layers.active.data[count + 1].uv + |
|
me0.uv_layers.active.data[count + 2].uv) |
|
if len(p.vertices) > 3: |
|
v03 = (me0.uv_layers.active.data[count].uv + |
|
me0.uv_layers.active.data[count + 3].uv) |
|
else: |
|
v03 = (me0.uv_layers.active.data[count].uv + |
|
me0.uv_layers.active.data[count].uv) |
|
v1203 = v03 - v12 |
|
v1203.normalize() |
|
|
|
vertUV = [] |
|
dot1203 = v1203.x |
|
dot0132 = v0132.x |
|
if(abs(dot1203) < abs(dot0132)): |
|
if (dot0132 > 0): |
|
vertUV = p.vertices[1:] + p.vertices[:1] |
|
else: |
|
vertUV = p.vertices[3:] + p.vertices[:3] |
|
else: |
|
if(dot1203 < 0): |
|
vertUV = p.vertices[:] |
|
else: |
|
vertUV = p.vertices[2:] + p.vertices[:2] |
|
ordered = vertUV |
|
count += len(p.vertices) |
|
|
|
# Weight Rotation |
|
elif rotation_mode == 'WEIGHT': |
|
if len(weight) > 0: |
|
active_weight = weight[ob0.vertex_groups.active_index] |
|
i = p.index |
|
face_weights = [active_weight[v] for v in p.vertices] |
|
face_weights*=2 |
|
if rotation_direction == 'DIAG': |
|
differential = [face_weights[ii]-face_weights[ii+2] for ii in range(4)] |
|
else: |
|
differential = [face_weights[ii]+face_weights[ii+1]-face_weights[ii+2]- face_weights[ii+3] for ii in range(4)] |
|
starting = differential.index(max(differential)) |
|
|
|
ordered = p.vertices[starting:] + p.vertices[:starting] |
|
|
|
if rotation_mode != 'RANDOM': |
|
ordered = np.roll(np.array(ordered),rotation_shift) |
|
ordered = np.array((ordered[0], ordered[1], ordered[2], ordered[-1])) |
|
|
|
# assign vertices and values |
|
vs0 = np.array([verts0[i].co for i in ordered]) |
|
#nvs0 = np.array([verts0[i].normal for i in ordered]) |
|
nvs0 = np.array([normals0[i] for i in ordered]) |
|
if scale_mode == 'ADAPTIVE': |
|
np_verts_area = np.array([verts_area[i] for i in ordered]) |
|
_sz[j] = np_verts_area |
|
# Vertex weight |
|
if bool_vertex_group: |
|
ws0 = [] |
|
for w in weight: |
|
_ws0 = [] |
|
for i in ordered: |
|
try: |
|
_ws0.append(w[i]) |
|
except: |
|
_ws0.append(0) |
|
ws0.append(np.array(_ws0)) |
|
|
|
# optimization test |
|
_vs0[j] = (vs0[0], vs0[1], vs0[2], vs0[-1]) |
|
if normals_mode != 'FACES': |
|
_nvs0[j] = (nvs0[0], nvs0[1], nvs0[2], nvs0[-1]) |
|
|
|
if bool_vertex_group: |
|
for i_vg, ws0_face in enumerate(ws0): |
|
_w0[i_vg][j] = (ws0_face[0], ws0_face[1], ws0_face[2], ws0_face[-1]) |
|
|
|
for p in fs1: |
|
new_faces[face1_count] = [i + n_verts1 * j for i in p] |
|
face1_count += 1 |
|
|
|
# build edges list |
|
n_edges1 = new_edges.shape[0] |
|
new_edges = new_edges.reshape((1, n_edges1, 2)) |
|
new_edges = new_edges.repeat(n_faces,axis=0) |
|
new_edges = new_edges.reshape((n_edges1*n_faces, 2)) |
|
increment = np.arange(n_faces)*n_verts1 |
|
increment = increment.repeat(n_edges1, axis=0) |
|
increment = increment.reshape((n_faces*n_edges1,1)) |
|
new_edges = new_edges + increment |
|
|
|
# optimization test |
|
_vs0 = np.array(_vs0) |
|
_sz = np.array(_sz) |
|
|
|
_vs0_0 = _vs0[:,0].reshape((n_faces,1,3)) |
|
_vs0_1 = _vs0[:,1].reshape((n_faces,1,3)) |
|
_vs0_2 = _vs0[:,2].reshape((n_faces,1,3)) |
|
_vs0_3 = _vs0[:,3].reshape((n_faces,1,3)) |
|
|
|
# remapped vertex coordinates |
|
v2 = np_lerp2(_vs0_0, _vs0_1, _vs0_3, _vs0_2, vx, vy) |
|
|
|
# remapped vertex normal |
|
if normals_mode != 'FACES': |
|
_nvs0 = np.array(_nvs0) |
|
_nvs0_0 = _nvs0[:,0].reshape((n_faces,1,3)) |
|
_nvs0_1 = _nvs0[:,1].reshape((n_faces,1,3)) |
|
_nvs0_2 = _nvs0[:,2].reshape((n_faces,1,3)) |
|
_nvs0_3 = _nvs0[:,3].reshape((n_faces,1,3)) |
|
nv2 = np_lerp2(_nvs0_0, _nvs0_1, _nvs0_3, _nvs0_2, vx, vy) |
|
else: |
|
nv2 = np.array(base_face_normals).reshape((n_faces,1,3)) |
|
|
|
# interpolate vertex groups |
|
if bool_vertex_group: |
|
w = np.array(_w0) |
|
w_0 = w[:,:,0].reshape((n_vg, n_faces,1,1)) |
|
w_1 = w[:,:,1].reshape((n_vg, n_faces,1,1)) |
|
w_2 = w[:,:,2].reshape((n_vg, n_faces,1,1)) |
|
w_3 = w[:,:,3].reshape((n_vg, n_faces,1,1)) |
|
# remapped weight |
|
w = np_lerp2(w_0, w_1, w_3, w_2, vx, vy) |
|
w = w.reshape((n_vg, n_faces*n_verts1)) |
|
|
|
if scale_mode == 'ADAPTIVE': |
|
_sz_0 = _sz[:,0].reshape((n_faces,1,1)) |
|
_sz_1 = _sz[:,1].reshape((n_faces,1,1)) |
|
_sz_2 = _sz[:,2].reshape((n_faces,1,1)) |
|
_sz_3 = _sz[:,3].reshape((n_faces,1,1)) |
|
# remapped z scale |
|
sz2 = np_lerp2(_sz_0, _sz_1, _sz_3, _sz_2, vx, vy) |
|
v3 = v2 + nv2 * vz * sz2 |
|
else: |
|
v3 = v2 + nv2 * vz |
|
|
|
new_verts_np = v3.reshape((n_faces*n_verts1,3)) |
|
|
|
if bool_shapekeys: |
|
n_sk = len(vx_key) |
|
sk_np = [0]*n_sk |
|
for i in range(n_sk): |
|
vx = np.array(vx_key[i]) |
|
vy = np.array(vy_key[i]) |
|
vz = np.array(vz_key[i]) |
|
|
|
# remapped vertex coordinates |
|
v2 = np_lerp2(_vs0_0, _vs0_1, _vs0_3, _vs0_2, vx, vy) |
|
|
|
# remapped vertex normal |
|
if normals_mode != 'FACES': |
|
nv2 = np_lerp2(_nvs0_0, _nvs0_1, _nvs0_3, _nvs0_2, vx, vy) |
|
else: |
|
nv2 = np.array(base_face_normals).reshape((n_faces,1,3)) |
|
|
|
if scale_mode == 'ADAPTIVE': |
|
# remapped z scale |
|
sz2 = np_lerp2(_sz_0, _sz_1, _sz_3, _sz_2, vx, vy) |
|
v3 = v2 + nv2 * vz * sz2 |
|
else: |
|
v3 = v2 + nv2 * vz |
|
|
|
sk_np[i] = v3.reshape((n_faces*n_verts1,3)) |
|
|
|
#if ob0.type == 'MESH': ob0.data = old_me0 |
|
|
|
if not bool_correct: |
|
#bpy.data.objects.remove(ob1) |
|
return 0 |
|
|
|
new_verts = new_verts_np.tolist() |
|
new_name = ob0.name + "_" + ob1.name |
|
new_me = bpy.data.meshes.new(new_name) |
|
new_me.from_pydata(new_verts, new_edges.tolist(), new_faces) |
|
new_me.update(calc_edges=True) |
|
new_ob = bpy.data.objects.new("tessellate_temp", new_me) |
|
|
|
# vertex group |
|
if bool_vertex_group and False: |
|
for vg in ob0.vertex_groups: |
|
new_ob.vertex_groups.new(name=vg.name) |
|
for i in range(len(vg_np[vg.index])): |
|
new_ob.vertex_groups[vg.name].add([i], vg_np[vg.index][i],"ADD") |
|
# vertex group |
|
if bool_vertex_group: |
|
for vg in ob0.vertex_groups: |
|
new_ob.vertex_groups.new(name=vg.name) |
|
for i, vertex_weight in enumerate(w[vg.index]): |
|
new_ob.vertex_groups[vg.name].add([i], vertex_weight,"ADD") |
|
|
|
if bool_shapekeys: |
|
basis = com_modifiers |
|
sk_count = 0 |
|
for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): |
|
sk.value = val |
|
new_ob.shape_key_add(name=sk.name, from_mix=False) |
|
new_ob.data.shape_keys.key_blocks[sk.name].value = val |
|
# set shape keys vertices |
|
sk_data = new_ob.data.shape_keys.key_blocks[sk.name].data |
|
if sk_count == 0: |
|
sk_count += 1 |
|
continue |
|
for id in range(len(sk_data)): |
|
sk_data[id].co = sk_np[sk_count-1][id] |
|
sk_count += 1 |
|
if bool_vertex_group: |
|
for sk in new_ob.data.shape_keys.key_blocks: |
|
for vg in new_ob.vertex_groups: |
|
if sk.name == vg.name: |
|
sk.vertex_group = vg.name |
|
|
|
# EDGES SEAMS |
|
edge_data = [0]*n_edges1 |
|
me1.edges.foreach_get("use_seam",edge_data) |
|
if any(edge_data): |
|
edge_data = edge_data*n_faces |
|
new_ob.data.edges.foreach_set("use_seam",edge_data) |
|
|
|
# EDGES SHARP |
|
edge_data = [0]*n_edges1 |
|
me1.edges.foreach_get("use_edge_sharp",edge_data) |
|
if any(edge_data): |
|
edge_data = edge_data*n_faces |
|
new_ob.data.edges.foreach_set("use_edge_sharp",edge_data) |
|
|
|
bpy.ops.object.select_all(action='DESELECT') |
|
bpy.context.collection.objects.link(new_ob) |
|
new_ob.select_set(True) |
|
bpy.context.view_layer.objects.active = new_ob |
|
|
|
# EDGES BEVEL |
|
edge_data = [0]*n_edges1 |
|
me1.edges.foreach_get("bevel_weight",edge_data) |
|
if any(edge_data): |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_all(action='SELECT') |
|
bpy.ops.transform.edge_bevelweight(value=1) |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
edge_data = edge_data*n_faces |
|
new_ob.data.edges.foreach_set("bevel_weight",edge_data) |
|
|
|
# EDGE CREASES |
|
edge_data = [0]*n_edges1 |
|
me1.edges.foreach_get("crease",edge_data) |
|
if any(edge_data): |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_all(action='SELECT') |
|
bpy.ops.transform.edge_crease(value=1) |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
edge_data = edge_data*n_faces |
|
new_ob.data.edges.foreach_set('crease', edge_data) |
|
|
|
# MATERIALS |
|
for slot in ob1.material_slots: new_ob.data.materials.append(slot.material) |
|
|
|
polygon_materials = [0]*n_faces1 |
|
me1.polygons.foreach_get("material_index", polygon_materials) |
|
polygon_materials *= n_faces |
|
new_ob.data.polygons.foreach_set("material_index", polygon_materials) |
|
new_ob.data.update() ### |
|
|
|
try: |
|
bpy.data.objects.remove(new_ob1) |
|
except: pass |
|
|
|
bpy.data.objects.remove(ob0) |
|
bpy.data.meshes.remove(me0) |
|
bpy.data.objects.remove(ob1) |
|
bpy.data.meshes.remove(me1) |
|
|
|
return new_ob |
|
|
|
|
|
class tissue_tessellate(Operator): |
|
bl_idname = "object.tissue_tessellate" |
|
bl_label = "Tessellate" |
|
bl_description = ("Create a copy of selected object on the active object's " |
|
"faces, adapting the shape to the different faces") |
|
bl_options = {'REGISTER', 'UNDO'} |
|
|
|
|
|
bool_hold : BoolProperty( |
|
name="Hold", |
|
description="Wait...", |
|
default=False |
|
) |
|
bool_lock : BoolProperty( |
|
name="Lock", |
|
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.", |
|
default=False |
|
) |
|
bool_dependencies : BoolProperty( |
|
name="Update Dependencies", |
|
description="Automatically updates base and components as well, if results of other tessellations", |
|
default=False |
|
) |
|
object_name : StringProperty( |
|
name="", |
|
description="Name of the generated object" |
|
) |
|
zscale : FloatProperty( |
|
name="Scale", |
|
default=1, |
|
soft_min=0, |
|
soft_max=10, |
|
description="Scale factor for the component thickness" |
|
) |
|
scale_mode : EnumProperty( |
|
items=( |
|
('CONSTANT', "Constant", "Uniform thickness"), |
|
('ADAPTIVE', "Relative", "Preserve component's proportions") |
|
), |
|
default='ADAPTIVE', |
|
name="Z-Scale according to faces size" |
|
) |
|
offset : FloatProperty( |
|
name="Surface Offset", |
|
default=1, |
|
min=-1, max=1, |
|
soft_min=-1, |
|
soft_max=1, |
|
description="Surface offset" |
|
) |
|
mode : EnumProperty( |
|
items=( |
|
('BOUNDS', "Bounds", "The component fits automatically the size of the target face"), |
|
('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"), |
|
('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")), |
|
default='BOUNDS', |
|
name="Component Mode" |
|
) |
|
rotation_mode : EnumProperty( |
|
items=(('RANDOM', "Random", "Random faces rotation"), |
|
('UV', "Active UV", "Face rotation is based on UV coordinates"), |
|
('WEIGHT', "Active Weight", "Rotate according to Vertex Group gradient"), |
|
('DEFAULT', "Default", "Default rotation")), |
|
default='DEFAULT', |
|
name="Component Rotation" |
|
) |
|
rotation_direction : EnumProperty( |
|
items=(('ORTHO', "Orthogonal", "Component main directions in XY"), |
|
('DIAG', "Diagonal", "Component main direction aligned with diagonal")), |
|
default='ORTHO', |
|
name="Direction" |
|
) |
|
rotation_shift : IntProperty( |
|
name="Shift", |
|
default=0, |
|
soft_min=0, |
|
soft_max=3, |
|
description="Shift components rotation" |
|
) |
|
fill_mode : EnumProperty( |
|
items=( |
|
('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'), |
|
('FAN', 'Fan', 'Radial tessellation for polygonal faces'), |
|
('PATCH', 'Patch', 'Curved tessellation according to the last ' + |
|
'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' + |
|
'patches.\nAfter the last Subsurf (or Multires) only ' + |
|
'deformation\nmodifiers can be used'), |
|
('FRAME', 'Frame', 'Essellation along the edges of each face')), |
|
default='QUAD', |
|
name="Fill Mode" |
|
) |
|
combine_mode : EnumProperty( |
|
items=( |
|
('LAST', 'Last', 'Show only the last iteration'), |
|
('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'), |
|
('ALL', 'All', 'Combine the result of all iterations')), |
|
default='LAST', |
|
name="Combine Mode", |
|
) |
|
gen_modifiers : BoolProperty( |
|
name="Generator Modifiers", |
|
default=False, |
|
description="Apply Modifiers and Shape Keys to the base object" |
|
) |
|
com_modifiers : BoolProperty( |
|
name="Component Modifiers", |
|
default=False, |
|
description="Apply Modifiers and Shape Keys to the component object" |
|
) |
|
merge : BoolProperty( |
|
name="Merge", |
|
default=False, |
|
description="Merge vertices in adjacent duplicates" |
|
) |
|
merge_thres : FloatProperty( |
|
name="Distance", |
|
default=0.001, |
|
soft_min=0, |
|
soft_max=10, |
|
description="Limit below which to merge vertices" |
|
) |
|
bool_random : BoolProperty( |
|
name="Randomize", |
|
default=False, |
|
description="Randomize component rotation" |
|
) |
|
random_seed : IntProperty( |
|
name="Seed", |
|
default=0, |
|
soft_min=0, |
|
soft_max=10, |
|
description="Random seed" |
|
) |
|
bool_vertex_group : BoolProperty( |
|
name="Map Vertex Groups", |
|
default=False, |
|
description="Transfer all Vertex Groups from Base object" |
|
) |
|
bool_selection : BoolProperty( |
|
name="On selected Faces", |
|
default=False, |
|
description="Create Tessellation only on selected faces" |
|
) |
|
bool_shapekeys : BoolProperty( |
|
name="Use Shape Keys", |
|
default=False, |
|
description="Transfer Component's Shape Keys. If the name of Vertex " |
|
"Groups and Shape Keys are the same, they will be " |
|
"automatically combined" |
|
) |
|
bool_smooth : BoolProperty( |
|
name="Smooth Shading", |
|
default=False, |
|
description="Output faces with smooth shading rather than flat shaded" |
|
) |
|
bool_materials : BoolProperty( |
|
name="Transfer Materials", |
|
default=True, |
|
description="Preserve component's materials" |
|
) |
|
generator : StringProperty( |
|
name="", |
|
description="Base object for the tessellation", |
|
default = "" |
|
) |
|
component : StringProperty( |
|
name="", |
|
description="Component object for the tessellation", |
|
default = "" |
|
) |
|
bool_material_id : BoolProperty( |
|
name="Tessellation on Material ID", |
|
default=False, |
|
description="Apply the component only on the selected Material" |
|
) |
|
bool_dissolve_seams : BoolProperty( |
|
name="Dissolve Seams", |
|
default=False, |
|
description="Dissolve all seam edges" |
|
) |
|
material_id : IntProperty( |
|
name="Material ID", |
|
default=0, |
|
min=0, |
|
description="Material ID" |
|
) |
|
iterations : IntProperty( |
|
name="Iterations", |
|
default=1, |
|
min=1, |
|
soft_max=5, |
|
description="Automatically repeat the Tessellation using the " |
|
+ "generated geometry as new base object.\nUsefull for " |
|
+ "for branching systems. Dangerous!" |
|
) |
|
bool_combine : BoolProperty( |
|
name="Combine unused", |
|
default=False, |
|
description="Combine the generated geometry with unused faces" |
|
) |
|
bool_advanced : BoolProperty( |
|
name="Advanced Settings", |
|
default=False, |
|
description="Show more settings" |
|
) |
|
normals_mode : EnumProperty( |
|
items=( |
|
('VERTS', 'Normals', 'Consistent direction based on vertices normal'), |
|
('FACES', 'Individual Faces', 'Based on individual faces normal'), |
|
('CUSTOM', 'Custom', "According to Base object's shape keys")), |
|
default='VERTS', |
|
name="Direction" |
|
) |
|
bool_multi_components : BoolProperty( |
|
name="Multi Components", |
|
default=False, |
|
description="Combine different components according to materials name" |
|
) |
|
bounds_x : EnumProperty( |
|
items=( |
|
('EXTEND', 'Extend', 'Default X coordinates'), |
|
('CLIP', 'Clip', 'Trim out of bounds in X direction'), |
|
('CYCLIC', 'Cyclic', 'Cyclic components in X direction')), |
|
default='EXTEND', |
|
name="Bounds X", |
|
) |
|
bounds_y : EnumProperty( |
|
items=( |
|
('EXTEND', 'Extend', 'Default Y coordinates'), |
|
('CLIP', 'Clip', 'Trim out of bounds in Y direction'), |
|
('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')), |
|
default='EXTEND', |
|
name="Bounds Y", |
|
) |
|
close_mesh : EnumProperty( |
|
items=( |
|
('NONE', 'None', 'Keep the mesh open'), |
|
('CAP', 'Cap Holes', 'Automatically cap open loops'), |
|
('BRIDGE', 'Bridge Loops', 'Automatically bridge loop pairs')), |
|
default='NONE', |
|
name="Close Mesh" |
|
) |
|
cap_faces : BoolProperty( |
|
name="Cap Holes", |
|
default=False, |
|
description="Cap open edges loops" |
|
) |
|
frame_boundary : BoolProperty( |
|
name="Frame Boundary", |
|
default=False, |
|
description="Support face boundaries" |
|
) |
|
fill_frame : BoolProperty( |
|
name="Fill Frame", |
|
default=False, |
|
description="Fill inner faces with Fan tessellation" |
|
) |
|
frame_boundary_mat : IntProperty( |
|
name="Material Offset", |
|
default=0, |
|
description="Material Offset for boundaries" |
|
) |
|
fill_frame_mat : IntProperty( |
|
name="Material Offset", |
|
default=0, |
|
description="Material Offset for inner faces" |
|
) |
|
open_edges_crease : FloatProperty( |
|
name="Open Edges Crease", |
|
default=0, |
|
min=0, |
|
max=1, |
|
description="Automatically set crease for open edges" |
|
) |
|
bridge_smoothness : FloatProperty( |
|
name="Smoothness", |
|
default=1, |
|
min=0, |
|
max=1, |
|
description="Bridge Smoothness" |
|
) |
|
frame_thickness : FloatProperty( |
|
name="Frame Thickness", |
|
default=0.2, |
|
min=0, |
|
soft_max=2, |
|
description="Frame Thickness" |
|
) |
|
frame_mode : EnumProperty( |
|
items=( |
|
('CONSTANT', 'Constant', 'Even thickness'), |
|
('RELATIVE', 'Relative', 'Frame offset depends on face areas')), |
|
default='CONSTANT', |
|
name="Offset" |
|
) |
|
bridge_cuts : IntProperty( |
|
name="Cuts", |
|
default=0, |
|
min=0, |
|
max=20, |
|
description="Bridge Cuts" |
|
) |
|
cap_material_index : IntProperty( |
|
name="Material", |
|
default=0, |
|
min=0, |
|
description="Material index for the cap/bridge faces" |
|
) |
|
patch_subs : IntProperty( |
|
name="Patch Subdivisions", |
|
default=1, |
|
min=0, |
|
description="Subdivisions levels for Patch tessellation after the first iteration" |
|
) |
|
working_on = "" |
|
|
|
def draw(self, context): |
|
allowed_obj = ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META') |
|
''' |
|
try: |
|
bool_working = self.working_on == self.object_name and \ |
|
self.working_on != "" |
|
except: |
|
bool_working = False |
|
''' |
|
|
|
bool_working = False |
|
bool_allowed = False |
|
ob0 = None |
|
ob1 = None |
|
|
|
sel = bpy.context.selected_objects |
|
if len(sel) == 1: |
|
try: |
|
ob0 = sel[0].tissue_tessellate.generator |
|
ob1 = sel[0].tissue_tessellate.component |
|
self.generator = ob0.name |
|
self.component = ob1.name |
|
if self.working_on == '': |
|
load_parameters(self,sel[0]) |
|
self.working_on = sel[0].name |
|
bool_working = True |
|
bool_allowed = True |
|
except: |
|
pass |
|
|
|
if len(sel) == 2: |
|
bool_allowed = True |
|
for o in sel: |
|
if o.type not in allowed_obj: |
|
bool_allowed = False |
|
|
|
if len(sel) != 2 and not bool_working: |
|
layout = self.layout |
|
layout.label(icon='INFO') |
|
layout.label(text="Please, select two different objects") |
|
layout.label(text="Select first the Component object, then select") |
|
layout.label(text="the Base object.") |
|
elif not bool_allowed and not bool_working: |
|
layout = self.layout |
|
layout.label(icon='INFO') |
|
layout.label(text="Only Mesh, Curve, Surface or Text objects are allowed") |
|
else: |
|
if ob0 == ob1 == None: |
|
ob0 = bpy.context.active_object |
|
self.generator = ob0.name |
|
for o in sel: |
|
if o != ob0: |
|
ob1 = o |
|
self.component = o.name |
|
self.no_component = False |
|
break |
|
|
|
# new object name |
|
if self.object_name == "": |
|
if self.generator == "": |
|
self.object_name = "Tessellation" |
|
else: |
|
#self.object_name = self.generator + "_Tessellation" |
|
self.object_name = "Tessellation" |
|
|
|
layout = self.layout |
|
# Base and Component |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
row.label(text="BASE : " + self.generator) |
|
row.label(text="COMPONENT : " + self.component) |
|
|
|
# Base Modifiers |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.prop(self, "gen_modifiers", text="Use Modifiers", icon='MODIFIER') |
|
base = bpy.data.objects[self.generator] |
|
try: |
|
if not (base.modifiers or base.data.shape_keys): |
|
col2.enabled = False |
|
self.gen_modifiers = False |
|
except: |
|
col2.enabled = False |
|
self.gen_modifiers = False |
|
|
|
# Component Modifiers |
|
row.separator() |
|
col3 = row.column(align=True) |
|
col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER') |
|
component = bpy.data.objects[self.component] |
|
try: |
|
if not (component.modifiers or component.data.shape_keys): |
|
col3.enabled = False |
|
self.com_modifiers = False |
|
except: |
|
col3.enabled = False |
|
self.com_modifiers = False |
|
col.separator() |
|
# Fill and Rotation |
|
row = col.row(align=True) |
|
row.label(text="Fill Mode:") |
|
row = col.row(align=True) |
|
row.prop( |
|
self, "fill_mode", icon='NONE', expand=True, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
row = col.row(align=True) |
|
row.prop(self, "bool_smooth") |
|
|
|
# frame settings |
|
if self.fill_mode == 'FRAME': |
|
col.separator() |
|
col.label(text="Frame Settings:") |
|
row = col.row(align=True) |
|
row.prop(self, "frame_mode", expand=True) |
|
col.prop(self, "frame_thickness", text='Thickness', icon='NONE') |
|
col.separator() |
|
row = col.row(align=True) |
|
row.prop(self, "fill_frame", icon='NONE') |
|
show_frame_mat = self.bool_multi_components or self.bool_material_id |
|
if self.fill_frame and show_frame_mat: |
|
row.prop(self, "fill_frame_mat", icon='NONE') |
|
row = col.row(align=True) |
|
row.prop(self, "frame_boundary", text='Boundary', icon='NONE') |
|
if self.frame_boundary and show_frame_mat: |
|
row.prop(self, "frame_boundary_mat", icon='NONE') |
|
|
|
if self.rotation_mode == 'UV': |
|
uv_error = False |
|
|
|
if ob0.type != 'MESH': |
|
row = col.row(align=True) |
|
row.label( |
|
text="UV rotation supported only for Mesh objects", |
|
icon='ERROR') |
|
uv_error = True |
|
else: |
|
if len(ob0.data.uv_layers) == 0: |
|
row = col.row(align=True) |
|
check_name = self.generator |
|
row.label(text="'" + check_name + |
|
"' doesn't have UV Maps", icon='ERROR') |
|
uv_error = True |
|
if uv_error: |
|
row = col.row(align=True) |
|
row.label(text="Default rotation will be used instead", |
|
icon='INFO') |
|
|
|
# Component Z |
|
col.separator() |
|
col.label(text="Thickness:") |
|
row = col.row(align=True) |
|
row.prop( |
|
self, "scale_mode", text="Scale Mode", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
col.prop( |
|
self, "zscale", text="Scale", icon='NONE', expand=False, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
if self.mode == 'BOUNDS': |
|
col.prop( |
|
self, "offset", text="Offset", icon='NONE', expand=False, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
# Component XY |
|
col.separator() |
|
row = col.row(align=True) |
|
row.label(text="Component Coordinates:") |
|
row = col.row(align=True) |
|
row.prop( |
|
self, "mode", text="Component XY", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
if self.mode != 'BOUNDS': |
|
col.separator() |
|
row = col.row(align=True) |
|
row.label(text="X:") |
|
row.prop( |
|
self, "bounds_x", text="Bounds X", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
row = col.row(align=True) |
|
row.label(text="Y:") |
|
row.prop( |
|
self, "bounds_y", text="Bounds X", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
|
|
# merge settings |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
row.prop(self, "merge") |
|
if self.merge: |
|
row.prop(self, "merge_thres") |
|
col.separator() |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.label(text='Close Mesh:') |
|
col2 = row.column(align=True) |
|
col2.prop(self, "close_mesh",text='') |
|
if self.close_mesh != 'NONE': |
|
row = col.row(align=True) |
|
row.prop(self, "open_edges_crease", text="Crease") |
|
row.prop(self, "cap_material_index") |
|
if self.close_mesh == 'BRIDGE': |
|
row = col.row(align=True) |
|
row.prop(self, "bridge_cuts") |
|
row.prop(self, "bridge_smoothness") |
|
row = col.row(align=True) |
|
row.prop(self, "bool_dissolve_seams") |
|
|
|
# Advanced Settings |
|
col = layout.column(align=True) |
|
col.separator() |
|
col.separator() |
|
row = col.row(align=True) |
|
row.prop(self, "bool_advanced", icon='SETTINGS') |
|
if self.bool_advanced: |
|
# rotation |
|
layout.use_property_split = True |
|
layout.use_property_decorate = False # No animation. |
|
col = layout.column(align=True) |
|
col.prop(self, "rotation_mode", text='Rotation', icon='NONE', expand=False, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
if self.rotation_mode == 'WEIGHT': |
|
col.prop(self, "rotation_direction", expand=False, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
if self.rotation_mode == 'RANDOM': |
|
col.prop(self, "random_seed") |
|
else: |
|
col.prop(self, "rotation_shift") |
|
|
|
if self.rotation_mode == 'UV': |
|
uv_error = False |
|
if self.generator.type != 'MESH': |
|
row = col.row(align=True) |
|
row.label( |
|
text="UV rotation supported only for Mesh objects", |
|
icon='ERROR') |
|
uv_error = True |
|
else: |
|
if len(self.generator.data.uv_layers) == 0: |
|
row = col.row(align=True) |
|
row.label(text="'" + props.generator.name + |
|
" doesn't have UV Maps", icon='ERROR') |
|
uv_error = True |
|
if uv_error: |
|
row = col.row(align=True) |
|
row.label(text="Default rotation will be used instead", |
|
icon='INFO') |
|
layout.use_property_split = False |
|
|
|
# Direction |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
row.label(text="Direction:") |
|
row = col.row(align=True) |
|
row.prop( |
|
self, "normals_mode", text="Direction", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
#row.enabled = self.fill_mode != 'PATCH' |
|
|
|
allow_multi = False |
|
allow_shapekeys = not self.com_modifiers |
|
if self.com_modifiers: self.bool_shapekeys = False |
|
for m in ob0.data.materials: |
|
try: |
|
o = bpy.data.objects[m.name] |
|
allow_multi = True |
|
try: |
|
if o.data.shape_keys is None: continue |
|
elif len(o.data.shape_keys.key_blocks) < 2: continue |
|
else: allow_shapekeys = not self.com_modifiers |
|
except: pass |
|
except: pass |
|
# DATA # |
|
col = layout.column(align=True) |
|
col.label(text="Weight and Morphing:") |
|
# vertex group + shape keys |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.prop(self, "bool_vertex_group", icon='GROUP_VERTEX') |
|
try: |
|
if len(ob0.vertex_groups) == 0: |
|
col2.enabled = False |
|
except: |
|
col2.enabled = False |
|
row.separator() |
|
col2 = row.column(align=True) |
|
row2 = col2.row(align=True) |
|
row2.prop(self, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA') |
|
row2.enabled = allow_shapekeys |
|
|
|
# LIMITED TESSELLATION |
|
col = layout.column(align=True) |
|
col.label(text="Limited Tessellation:") |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.prop(self, "bool_multi_components", icon='MOD_TINT') |
|
if not allow_multi: |
|
col2.enabled = False |
|
self.bool_multi_components = False |
|
col.separator() |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.prop(self, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF') |
|
row.separator() |
|
if ob0.type != 'MESH': |
|
col2.enabled = False |
|
col2 = row.column(align=True) |
|
col2.prop(self, "bool_material_id", icon='MATERIAL_DATA', text="Material ID") |
|
if self.bool_material_id and not self.bool_multi_components: |
|
#col2 = row.column(align=True) |
|
col2.prop(self, "material_id") |
|
col2.enabled = not self.bool_multi_components |
|
|
|
col.separator() |
|
row = col.row(align=True) |
|
row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH') |
|
row.prop(self, 'iterations', text='Repeat', icon='SETTINGS') |
|
if self.iterations > 1 and self.fill_mode == 'PATCH': |
|
col.separator() |
|
row = col.row(align=True) |
|
row.prop(self, 'patch_subs') |
|
col.separator() |
|
row = col.row(align=True) |
|
row.label(text='Combine Iterations:') |
|
row = col.row(align=True) |
|
row.prop( |
|
self, "combine_mode", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
def execute(self, context): |
|
allowed_obj = ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT') |
|
try: |
|
ob0 = bpy.data.objects[self.generator] |
|
ob1 = bpy.data.objects[self.component] |
|
except: |
|
return {'CANCELLED'} |
|
|
|
self.object_name = "Tessellation" |
|
# Check if existing object with same name |
|
names = [o.name for o in bpy.data.objects] |
|
if self.object_name in names: |
|
count_name = 1 |
|
while True: |
|
test_name = self.object_name + '.{:03d}'.format(count_name) |
|
if not (test_name in names): |
|
self.object_name = test_name |
|
break |
|
count_name += 1 |
|
|
|
if ob1.type not in allowed_obj: |
|
message = "Component must be Mesh, Curve, Surface, Text or Meta object!" |
|
self.report({'ERROR'}, message) |
|
self.component = None |
|
|
|
if ob0.type not in allowed_obj: |
|
message = "Generator must be Mesh, Curve, Surface, Text or Meta object!" |
|
self.report({'ERROR'}, message) |
|
self.generator = "" |
|
|
|
if True:#self.component not in ("",None) and self.generator not in ("",None): |
|
if bpy.ops.object.select_all.poll(): |
|
bpy.ops.object.select_all(action='TOGGLE') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
|
|
#data0 = ob0.to_mesh(False) |
|
#data0 = ob0.data.copy() |
|
bool_update = False |
|
if bpy.context.object == ob0: |
|
auto_layer_collection() |
|
#new_ob = bpy.data.objects.new(self.object_name, data0) |
|
new_ob = convert_object_to_mesh(ob0,False,False) |
|
new_ob.data.name = self.object_name |
|
#bpy.context.collection.objects.link(new_ob) |
|
#bpy.context.view_layer.objects.active = new_ob |
|
new_ob.name = self.object_name |
|
#new_ob.select_set(True) |
|
else: |
|
new_ob = bpy.context.object |
|
bool_update = True |
|
new_ob = store_parameters(self, new_ob) |
|
try: bpy.ops.object.tissue_update_tessellate() |
|
except RuntimeError as e: |
|
bpy.data.objects.remove(new_ob) |
|
self.report({'ERROR'}, str(e)) |
|
return {'CANCELLED'} |
|
if not bool_update: |
|
self.object_name = new_ob.name |
|
#self.working_on = self.object_name |
|
new_ob.location = ob0.location |
|
new_ob.matrix_world = ob0.matrix_world |
|
|
|
return {'FINISHED'} |
|
|
|
def invoke(self, context, event): |
|
return context.window_manager.invoke_props_dialog(self) |
|
|
|
|
|
def update_dependencies(ob, objects): |
|
ob0 = ob.tissue_tessellate.generator |
|
ob1 = ob.tissue_tessellate.component |
|
deps = [ob0, ob1] |
|
for o in deps: |
|
if o.tissue_tessellate.bool_lock: continue |
|
o0 = o.tissue_tessellate.generator |
|
o1 = o.tissue_tessellate.component |
|
deps_deps = [o0, o1] |
|
try: |
|
o0.name |
|
o1.name |
|
if o0 not in objects and o1 not in objects: |
|
objects.append(o) |
|
objects = update_dependencies(o, objects) |
|
except: |
|
continue |
|
return objects |
|
|
|
|
|
class tissue_refresh_tessellate(Operator): |
|
bl_idname = "object.tissue_refresh_tessellate" |
|
bl_label = "Refresh" |
|
bl_description = ("Fast update the tessellated mesh according to base and " |
|
"component changes") |
|
bl_options = {'REGISTER', 'UNDO'} |
|
|
|
go = False |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
return context.object.tissue_tessellate.generator != None and \ |
|
context.object.tissue_tessellate.component != None |
|
except: |
|
return False |
|
|
|
@staticmethod |
|
def check_gen_comp(checking): |
|
# note pass the stored name key in here to check it out |
|
return checking in bpy.data.objects.keys() |
|
|
|
def execute(self, context): |
|
ob = bpy.context.object |
|
ob0 = ob.tissue_tessellate.generator |
|
ob1 = ob.tissue_tessellate.component |
|
try: |
|
ob0.name |
|
ob1.name |
|
except: |
|
self.report({'ERROR'}, |
|
"Active object must be Tessellate before Update") |
|
return {'CANCELLED'} |
|
|
|
if ob.tissue_tessellate.bool_dependencies: |
|
update_objects = list(reversed(update_dependencies(ob, [ob]))) |
|
else: |
|
update_objects = [ob] |
|
for o in update_objects: |
|
override = {'object': o} |
|
bpy.ops.object.tissue_update_tessellate(override) |
|
|
|
return {'FINISHED'} |
|
|
|
|
|
class tissue_update_tessellate(Operator): |
|
bl_idname = "object.tissue_update_tessellate" |
|
bl_label = "Refresh" |
|
bl_description = ("Fast update the tessellated mesh according to base and " |
|
"component changes") |
|
bl_options = {'REGISTER', 'UNDO'} |
|
|
|
go = False |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
#try: |
|
try: #context.object == None: return False |
|
return context.object.tissue_tessellate.generator != None and \ |
|
context.object.tissue_tessellate.component != None |
|
except: |
|
return False |
|
|
|
@staticmethod |
|
def check_gen_comp(checking): |
|
# note pass the stored name key in here to check it out |
|
return checking in bpy.data.objects.keys() |
|
|
|
def execute(self, context): |
|
start_time = time.time() |
|
|
|
ob = context.object |
|
if not self.go: |
|
generator = ob.tissue_tessellate.generator |
|
component = ob.tissue_tessellate.component |
|
zscale = ob.tissue_tessellate.zscale |
|
scale_mode = ob.tissue_tessellate.scale_mode |
|
rotation_mode = ob.tissue_tessellate.rotation_mode |
|
rotation_shift = ob.tissue_tessellate.rotation_shift |
|
rotation_direction = ob.tissue_tessellate.rotation_direction |
|
offset = ob.tissue_tessellate.offset |
|
merge = ob.tissue_tessellate.merge |
|
merge_thres = ob.tissue_tessellate.merge_thres |
|
gen_modifiers = ob.tissue_tessellate.gen_modifiers |
|
com_modifiers = ob.tissue_tessellate.com_modifiers |
|
bool_random = ob.tissue_tessellate.bool_random |
|
random_seed = ob.tissue_tessellate.random_seed |
|
fill_mode = ob.tissue_tessellate.fill_mode |
|
bool_vertex_group = ob.tissue_tessellate.bool_vertex_group |
|
bool_selection = ob.tissue_tessellate.bool_selection |
|
bool_shapekeys = ob.tissue_tessellate.bool_shapekeys |
|
mode = ob.tissue_tessellate.mode |
|
bool_smooth = ob.tissue_tessellate.bool_smooth |
|
bool_materials = ob.tissue_tessellate.bool_materials |
|
bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams |
|
bool_material_id = ob.tissue_tessellate.bool_material_id |
|
material_id = ob.tissue_tessellate.material_id |
|
iterations = ob.tissue_tessellate.iterations |
|
bool_combine = ob.tissue_tessellate.bool_combine |
|
normals_mode = ob.tissue_tessellate.normals_mode |
|
bool_advanced = ob.tissue_tessellate.bool_advanced |
|
bool_multi_components = ob.tissue_tessellate.bool_multi_components |
|
combine_mode = ob.tissue_tessellate.combine_mode |
|
bounds_x = ob.tissue_tessellate.bounds_x |
|
bounds_y = ob.tissue_tessellate.bounds_y |
|
cap_faces = ob.tissue_tessellate.cap_faces |
|
close_mesh = ob.tissue_tessellate.close_mesh |
|
open_edges_crease = ob.tissue_tessellate.open_edges_crease |
|
bridge_smoothness = ob.tissue_tessellate.bridge_smoothness |
|
frame_thickness = ob.tissue_tessellate.frame_thickness |
|
frame_mode = ob.tissue_tessellate.frame_mode |
|
frame_boundary = ob.tissue_tessellate.frame_boundary |
|
fill_frame = ob.tissue_tessellate.fill_frame |
|
frame_boundary_mat = ob.tissue_tessellate.frame_boundary_mat |
|
fill_frame_mat = ob.tissue_tessellate.fill_frame_mat |
|
bridge_cuts = ob.tissue_tessellate.bridge_cuts |
|
cap_material_index = ob.tissue_tessellate.cap_material_index |
|
patch_subs = ob.tissue_tessellate.patch_subs |
|
try: |
|
generator.name |
|
component.name |
|
except: |
|
self.report({'ERROR'}, |
|
"Active object must be Tessellate before Update") |
|
return {'CANCELLED'} |
|
|
|
# Solve Local View issues |
|
local_spaces = [] |
|
local_ob0 = [] |
|
local_ob1 = [] |
|
for area in context.screen.areas: |
|
for space in area.spaces: |
|
try: |
|
if ob.local_view_get(space): |
|
local_spaces.append(space) |
|
local_ob0 = ob0.local_view_get(space) |
|
ob0.local_view_set(space, True) |
|
local_ob1 = ob1.local_view_get(space) |
|
ob1.local_view_set(space, True) |
|
except: |
|
pass |
|
|
|
starting_mode = context.object.mode |
|
|
|
#if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT' |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
|
|
ob0 = generator |
|
ob1 = component |
|
##### auto_layer_collection() |
|
|
|
ob0_hide = ob0.hide_get() |
|
ob0_hidev = ob0.hide_viewport |
|
ob0_hider = ob0.hide_render |
|
ob1_hide = ob1.hide_get() |
|
ob1_hidev = ob1.hide_viewport |
|
ob1_hider = ob1.hide_render |
|
ob0.hide_set(False) |
|
ob0.hide_viewport = False |
|
ob0.hide_render = False |
|
ob1.hide_set(False) |
|
ob1.hide_viewport = False |
|
ob1.hide_render = False |
|
|
|
if ob0.type == 'META': |
|
base_ob = convert_object_to_mesh(ob0, False, True) |
|
else: |
|
base_ob = ob0.copy() |
|
base_ob.data = ob0.data# |
|
context.collection.objects.link(base_ob) |
|
base_ob.name = '_tissue_tmp_base' |
|
|
|
# In Blender 2.80 cache of copied objects is lost, must be re-baked |
|
bool_update_cloth = False |
|
for m in base_ob.modifiers: |
|
if m.type == 'CLOTH': |
|
m.point_cache.frame_end = context.scene.frame_current |
|
bool_update_cloth = True |
|
if bool_update_cloth: |
|
bpy.ops.ptcache.free_bake_all() |
|
bpy.ops.ptcache.bake_all() |
|
base_ob.modifiers.update() |
|
|
|
|
|
#new_ob.location = ob.location |
|
#new_ob.matrix_world = ob.matrix_world |
|
#bpy.ops.object.select_all(action='DESELECT') |
|
if bool_selection: |
|
faces = base_ob.data.polygons |
|
selections = [False]*len(faces) |
|
faces.foreach_get('select',selections) |
|
selections = np.array(selections) |
|
if not selections.any(): |
|
message = "There are no faces selected." |
|
context.view_layer.objects.active = ob |
|
ob.select_set(True) |
|
bpy.ops.object.mode_set(mode=starting_mode) |
|
bpy.data.objects.remove(base_ob) |
|
self.report({'ERROR'}, message) |
|
return {'CANCELLED'} |
|
|
|
|
|
|
|
iter_objects = [base_ob] |
|
ob_location = ob.location |
|
ob_matrix_world = ob.matrix_world |
|
#base_ob = new_ob#.copy() |
|
|
|
for iter in range(iterations): |
|
|
|
if iter > 0 and len(iter_objects) == 0: break |
|
if iter > 0 and normals_mode == 'CUSTOM': normals_mode = 'VERTS' |
|
same_iteration = [] |
|
matched_materials = [] |
|
# iterate base object materials (needed for multi-components) |
|
if bool_multi_components: mat_iter = len(base_ob.material_slots) |
|
else: mat_iter = 1 |
|
for m_id in range(mat_iter): |
|
if bool_multi_components: |
|
# check if material and components match |
|
try: |
|
mat = base_ob.material_slots[m_id].material |
|
ob1 = bpy.data.objects[mat.name] |
|
if ob1.type not in ('MESH', 'CURVE','SURFACE','FONT', 'META'): |
|
continue |
|
material_id = m_id |
|
matched_materials.append(m_id) |
|
bool_material_id = True |
|
except: |
|
continue |
|
if com_modifiers or ob1.type != 'MESH': |
|
data1 = simple_to_mesh(ob1) |
|
else: |
|
data1 = ob1.data.copy() |
|
n_edges1 = len(data1.edges) |
|
bpy.data.meshes.remove(data1) |
|
|
|
if iter != 0: gen_modifiers = True |
|
|
|
if fill_mode == 'PATCH': |
|
# patch subdivisions for additional iterations |
|
if iter > 0: |
|
base_ob.modifiers.new('Tissue_Subsurf', type='SUBSURF') |
|
base_ob.modifiers['Tissue_Subsurf'].levels = patch_subs |
|
temp_mod = base_ob.modifiers['Tissue_Subsurf'] |
|
# patch tessellation |
|
new_ob = tessellate_patch( |
|
base_ob, ob1, offset, zscale, com_modifiers, mode, scale_mode, |
|
rotation_mode, rotation_shift, random_seed, bool_vertex_group, |
|
bool_selection, bool_shapekeys, bool_material_id, material_id, |
|
normals_mode, bounds_x, bounds_y |
|
) |
|
if iter > 0: |
|
base_ob.modifiers.remove(temp_mod) |
|
else: |
|
### FRAME and FAN ### |
|
if fill_mode in ('FRAME','FAN'): |
|
|
|
if fill_mode == 'FRAME': convert_function = convert_to_frame |
|
else: convert_function = convert_to_fan |
|
|
|
if normals_mode == 'CUSTOM' and base_ob.data.shape_keys != None: |
|
## base key |
|
sk_values = [sk.value for sk in base_ob.data.shape_keys.key_blocks] |
|
for sk in ob0.data.shape_keys.key_blocks: sk.value = 0 |
|
_base_ob = convert_function(base_ob, ob.tissue_tessellate, gen_modifiers) |
|
for i, sk in enumerate(ob0.data.shape_keys.key_blocks): |
|
sk.value = sk_values[i] |
|
## key 1 |
|
# hide modifiers |
|
if not gen_modifiers and len(base_ob.modifiers) > 0: |
|
mod_visibility = [m.show_viewport for m in base_ob.modifiers] |
|
for m in base_ob.modifiers: m.show_viewport = False |
|
base_ob.modifiers.update() |
|
base_ob_sk = convert_function(ob0, ob.tissue_tessellate, True) |
|
## combine shapekeys |
|
_base_ob.shape_key_add(name='Basis', from_mix=False) |
|
_base_ob.shape_key_add(name='Key1', from_mix=False) |
|
sk_block = _base_ob.data.shape_keys.key_blocks[1] |
|
sk_block.value = 1 |
|
for vert, sk in zip(base_ob_sk.data.vertices, sk_block.data): |
|
sk.co = vert.co |
|
bpy.data.objects.remove(base_ob_sk) |
|
# set original modifiers |
|
if not gen_modifiers and len(base_ob.modifiers) > 0: |
|
for i,m in enumerate(base_ob.modifiers): |
|
m.show_viewport = mod_visibility[i] |
|
base_ob.modifiers.update() |
|
else: |
|
_base_ob = convert_function(base_ob, ob.tissue_tessellate, gen_modifiers) |
|
bpy.data.objects.remove(base_ob) |
|
base_ob = _base_ob |
|
# quad tessellation |
|
new_ob = tessellate_original( |
|
base_ob, ob1, offset, zscale, gen_modifiers, |
|
com_modifiers, mode, scale_mode, rotation_mode, |
|
rotation_shift, rotation_direction, |
|
random_seed, fill_mode, bool_vertex_group, |
|
bool_selection, bool_shapekeys, bool_material_id, |
|
material_id, normals_mode, bounds_x, bounds_y |
|
) |
|
|
|
# if empty or error, continue |
|
if type(new_ob) is not bpy.types.Object: |
|
continue |
|
|
|
# prepare base object |
|
if iter == 0 and gen_modifiers: |
|
temp_base_ob = convert_object_to_mesh(base_ob, True, True) |
|
bpy.data.objects.remove(base_ob) |
|
base_ob = temp_base_ob |
|
iter_objects = [base_ob] |
|
|
|
# rename, make active and change transformations |
|
new_ob.name = '_tissue_tmp_{}_{}'.format(iter,m_id) |
|
new_ob.select_set(True) |
|
context.view_layer.objects.active = new_ob |
|
new_ob.location = ob_location |
|
new_ob.matrix_world = ob_matrix_world |
|
|
|
n_components = int(len(new_ob.data.edges) / n_edges1) |
|
# SELECTION |
|
if bool_selection: |
|
try: |
|
# create selection list |
|
polygon_selection = [p.select for p in ob1.data.polygons] * int( |
|
len(new_ob.data.polygons) / len(ob1.data.polygons)) |
|
new_ob.data.polygons.foreach_set("select", polygon_selection) |
|
except: |
|
pass |
|
if bool_multi_components: same_iteration.append(new_ob) |
|
|
|
base_ob.location = ob_location |
|
base_ob.matrix_world = ob_matrix_world |
|
|
|
# join together multiple components iterations |
|
if bool_multi_components: |
|
if len(same_iteration) > 0: |
|
context.view_layer.update() |
|
for o in context.view_layer.objects: |
|
o.select_set(o in same_iteration) |
|
bpy.ops.object.join() |
|
new_ob = context.view_layer.objects.active |
|
new_ob.select_set(True) |
|
#new_ob.data.update() |
|
|
|
if type(new_ob) in (int,str): |
|
if iter == 0: |
|
try: |
|
bpy.data.objects.remove(iter_objects[0]) |
|
iter_objects = [] |
|
except: continue |
|
continue |
|
|
|
# Clean last iteration, needed for combine object |
|
if (bool_selection or bool_material_id) and combine_mode == 'UNUSED': |
|
# remove faces from last mesh |
|
bm = bmesh.new() |
|
last_mesh = iter_objects[-1].data.copy() |
|
bm.from_mesh(last_mesh) |
|
bm.faces.ensure_lookup_table() |
|
if bool_multi_components: |
|
remove_materials = matched_materials |
|
elif bool_material_id: |
|
remove_materials = [material_id] |
|
else: remove_materials = [] |
|
if bool_selection: |
|
if bool_multi_components or bool_material_id: |
|
remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select] |
|
else: |
|
remove_faces = [f for f in bm.faces if f.select] |
|
else: |
|
remove_faces = [f for f in bm.faces if f.material_index in remove_materials] |
|
bmesh.ops.delete(bm, geom=remove_faces, context='FACES') |
|
bm.to_mesh(last_mesh) |
|
last_mesh.update() |
|
last_mesh.name = '_tissue_tmp_previous_unused' |
|
|
|
# delete previous iteration if empty or update it |
|
if len(last_mesh.vertices) > 0: |
|
iter_objects[-1].data = last_mesh.copy() |
|
iter_objects[-1].data.update() |
|
else: |
|
bpy.data.objects.remove(iter_objects[-1]) |
|
iter_objects = iter_objects[:-1] |
|
# set new base object for next iteration |
|
base_ob = convert_object_to_mesh(new_ob,True,True) |
|
if iter < iterations-1: new_ob.data = base_ob.data |
|
# store new iteration and set transformations |
|
iter_objects.append(new_ob) |
|
#try: |
|
# bpy.data.objects.remove(bpy.data.objects['_tissue_tmp_base']) |
|
#except: |
|
# pass |
|
base_ob.name = '_tissue_tmp_base' |
|
elif combine_mode == 'ALL': |
|
base_ob = new_ob.copy() |
|
iter_objects.append(new_ob) |
|
else: |
|
if base_ob != new_ob: |
|
bpy.data.objects.remove(base_ob) |
|
base_ob = new_ob |
|
iter_objects = [new_ob] |
|
|
|
# Combine |
|
if combine_mode != 'LAST' and len(iter_objects)>0: |
|
if base_ob not in iter_objects and type(base_ob) == bpy.types.Object: |
|
bpy.data.objects.remove(base_ob) |
|
for o in context.view_layer.objects: |
|
o.select_set(o in iter_objects) |
|
bpy.ops.object.join() |
|
new_ob.data.update() |
|
iter_objects = [new_ob] |
|
|
|
if merge: |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode( |
|
use_extend=False, use_expand=False, type='VERT') |
|
bpy.ops.mesh.select_non_manifold( |
|
extend=False, use_wire=True, use_boundary=True, |
|
use_multi_face=False, use_non_contiguous=False, use_verts=False) |
|
|
|
bpy.ops.mesh.remove_doubles( |
|
threshold=merge_thres, use_unselected=False) |
|
|
|
if bool_dissolve_seams: |
|
bpy.ops.mesh.select_mode(type='EDGE') |
|
bpy.ops.mesh.select_all(action='DESELECT') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
for e in new_ob.data.edges: |
|
e.select = e.use_seam |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.dissolve_edges() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
|
|
if close_mesh != 'NONE': |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode( |
|
use_extend=False, use_expand=False, type='EDGE') |
|
bpy.ops.mesh.select_non_manifold( |
|
extend=False, use_wire=False, use_boundary=True, |
|
use_multi_face=False, use_non_contiguous=False, use_verts=False) |
|
if open_edges_crease != 0: |
|
bpy.ops.transform.edge_crease(value=open_edges_crease) |
|
if close_mesh == 'CAP': |
|
bpy.ops.mesh.edge_face_add() |
|
if close_mesh == 'BRIDGE': |
|
try: |
|
bpy.ops.mesh.bridge_edge_loops( |
|
type='PAIRS', |
|
number_cuts=bridge_cuts, |
|
interpolation='SURFACE', |
|
smoothness=bridge_smoothness) |
|
except: pass |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
for f in new_ob.data.polygons: |
|
if f.select: f.material_index = cap_material_index |
|
base_ob = context.view_layer.objects.active |
|
|
|
# Combine iterations |
|
if combine_mode != 'LAST' and len(iter_objects)>0: |
|
#if base_ob not in iter_objects and type(base_ob) == bpy.types.Object: |
|
# bpy.data.objects.remove(base_ob) |
|
for o in context.view_layer.objects: |
|
o.select_set(o in iter_objects) |
|
bpy.ops.object.join() |
|
new_ob = context.view_layer.objects.active |
|
elif combine_mode == 'LAST' and type(new_ob) != bpy.types.Object: |
|
# if last iteration gives error, then use the last correct iteration |
|
try: |
|
if type(iter_objects[-1]) == bpy.types.Object: |
|
new_ob = iter_objects[-1] |
|
except: pass |
|
|
|
if new_ob == 0: |
|
#bpy.data.objects.remove(base_ob.data) |
|
try: bpy.data.objects.remove(base_ob) |
|
except: pass |
|
message = "The generated object is an empty geometry!" |
|
context.view_layer.objects.active = ob |
|
ob.select_set(True) |
|
bpy.ops.object.mode_set(mode=starting_mode) |
|
self.report({'ERROR'}, message) |
|
return {'CANCELLED'} |
|
errors = {} |
|
errors["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \ |
|
"after the last Subsurf (or Multires) are not allowed." |
|
errors["topology_error"] = "Make sure that the topology of the mesh before \n" \ |
|
"the last Subsurf (or Multires) is quads only." |
|
errors["wires_error"] = "Please remove all wire edges in the base object." |
|
errors["verts_error"] = "Please remove all floating vertices in the base object" |
|
if new_ob in errors: |
|
for o in iter_objects: |
|
try: bpy.data.objects.remove(o) |
|
except: pass |
|
try: bpy.data.meshes.remove(data1) |
|
except: pass |
|
context.view_layer.objects.active = ob |
|
ob.select_set(True) |
|
message = errors[new_ob] |
|
ob.tissue_tessellate.error_message = message |
|
bpy.ops.object.mode_set(mode=starting_mode) |
|
self.report({'ERROR'}, message) |
|
return {'CANCELLED'} |
|
|
|
#new_ob.location = ob_location |
|
#new_ob.matrix_world = ob_matrix_world |
|
|
|
# update data and preserve name |
|
if ob.type != 'MESH': |
|
loc, matr = ob.location, ob.matrix_world |
|
ob = convert_object_to_mesh(ob,False,True) |
|
ob.location, ob.matrix_world = loc, matr |
|
data_name = ob.data.name |
|
old_data = ob.data |
|
#ob.data = bpy.data.meshes.new_from_object(new_ob)# |
|
ob.data = new_ob.data.copy() |
|
ob.data.name = data_name |
|
bpy.data.meshes.remove(old_data) |
|
|
|
# copy vertex group |
|
if bool_vertex_group: |
|
for vg in new_ob.vertex_groups: |
|
if not vg.name in ob.vertex_groups.keys(): |
|
ob.vertex_groups.new(name=vg.name) |
|
new_vg = ob.vertex_groups[vg.name] |
|
for i in range(len(ob.data.vertices)): |
|
try: |
|
weight = vg.weight(i) |
|
except: |
|
weight = 0 |
|
new_vg.add([i], weight, 'REPLACE') |
|
|
|
selected_objects = [o for o in context.selected_objects] |
|
for o in selected_objects: o.select_set(False) |
|
|
|
ob.select_set(True) |
|
context.view_layer.objects.active = ob |
|
|
|
if merge: |
|
try: |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
#bpy.ops.mesh.select_mode( |
|
# use_extend=False, use_expand=False, type='VERT') |
|
bpy.ops.mesh.select_mode(type='VERT') |
|
bpy.ops.mesh.select_non_manifold( |
|
extend=False, use_wire=True, use_boundary=True, |
|
use_multi_face=False, use_non_contiguous=False, use_verts=False) |
|
|
|
bpy.ops.mesh.remove_doubles( |
|
threshold=merge_thres, use_unselected=False) |
|
|
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
if bool_dissolve_seams: |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode(type='EDGE') |
|
bpy.ops.mesh.select_all(action='DESELECT') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
for e in ob.data.edges: |
|
e.select = e.use_seam |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.dissolve_edges() |
|
except: pass |
|
if close_mesh != 'NONE': |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode( |
|
use_extend=False, use_expand=False, type='EDGE') |
|
bpy.ops.mesh.select_non_manifold( |
|
extend=False, use_wire=False, use_boundary=True, |
|
use_multi_face=False, use_non_contiguous=False, use_verts=False) |
|
if open_edges_crease != 0: |
|
bpy.ops.transform.edge_crease(value=open_edges_crease) |
|
if close_mesh == 'CAP': |
|
bpy.ops.mesh.edge_face_add() |
|
if close_mesh == 'BRIDGE': |
|
try: |
|
bpy.ops.mesh.bridge_edge_loops( |
|
type='PAIRS', |
|
number_cuts=bridge_cuts, |
|
interpolation='SURFACE', |
|
smoothness=bridge_smoothness) |
|
except: |
|
pass |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
for f in ob.data.polygons: |
|
if f.select: f.material_index = cap_material_index |
|
#else: |
|
|
|
try: |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
except: pass |
|
|
|
if bool_smooth: bpy.ops.object.shade_smooth() |
|
|
|
for mesh in bpy.data.meshes: |
|
if not mesh.users: bpy.data.meshes.remove(mesh) |
|
|
|
for o in selected_objects: |
|
try: o.select_set(True) |
|
except: pass |
|
|
|
bpy.ops.object.mode_set(mode=starting_mode) |
|
|
|
ob.tissue_tessellate.error_message = "" |
|
|
|
# Restore Base visibility |
|
ob0.hide_set(ob0_hide) |
|
ob0.hide_viewport = ob0_hidev |
|
ob0.hide_render = ob0_hider |
|
# Restore Component visibility |
|
ob1.hide_set(ob1_hide) |
|
ob1.hide_viewport = ob1_hidev |
|
ob1.hide_render = ob1_hider |
|
# Restore Local visibility |
|
for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1): |
|
ob0.local_view_set(space, local0) |
|
ob1.local_view_set(space, local1) |
|
|
|
bpy.data.objects.remove(new_ob) |
|
|
|
# clean objects |
|
for o in bpy.data.objects: |
|
#if o.name not in context.view_layer.objects and "_tissue_tmp" in o.name: |
|
if "_tissue_tmp" in o.name: |
|
bpy.data.objects.remove(o) |
|
|
|
end_time = time.time() |
|
print('Tissue: object "{}" tessellated in {:.4f} sec'.format(ob.name, end_time-start_time)) |
|
return {'FINISHED'} |
|
|
|
def check(self, context): |
|
return True |
|
|
|
class TISSUE_PT_tessellate(Panel): |
|
bl_label = "Tissue Tools" |
|
bl_category = "Tissue" |
|
bl_space_type = "VIEW_3D" |
|
bl_region_type = "UI" |
|
#bl_options = {'DEFAULT_OPEN'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
return context.mode in {'OBJECT', 'EDIT_MESH'} |
|
|
|
def draw(self, context): |
|
layout = self.layout |
|
|
|
col = layout.column(align=True) |
|
col.label(text="Tessellate:") |
|
col.operator("object.tissue_tessellate") |
|
col.operator("object.dual_mesh_tessellated") |
|
col.separator() |
|
col.operator("object.tissue_refresh_tessellate", icon='FILE_REFRESH') |
|
|
|
col.separator() |
|
col.label(text="Rotate Faces:") |
|
row = col.row(align=True) |
|
row.operator("mesh.tissue_rotate_face_left", text='Left', icon='LOOP_BACK') |
|
row.operator("mesh.tissue_rotate_face_right", text='Right', icon='LOOP_FORWARDS') |
|
|
|
col.separator() |
|
col.label(text="Other:") |
|
col.operator("object.dual_mesh") |
|
col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE") |
|
|
|
act = context.object |
|
if act and act.type == 'MESH': |
|
col.operator("object.uv_to_mesh", icon="UV") |
|
|
|
if act.mode == 'EDIT': |
|
col.separator() |
|
col.label(text="Weight:") |
|
col.operator("object.tissue_weight_distance", icon="TRACKING") |
|
|
|
class TISSUE_PT_tessellate_object(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_label = "Tessellate Settings" |
|
bl_options = {'DEFAULT_CLOSED'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: return context.object.type == 'MESH' |
|
except: return False |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
if not bool_tessellated: |
|
layout.label(text="The selected object is not a Tessellated object", |
|
icon='INFO') |
|
else: |
|
if props.error_message != "": |
|
layout.label(text=props.error_message, |
|
icon='ERROR') |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
|
|
set_tessellate_handler(self,context) |
|
set_animatable_fix_handler(self,context) |
|
row.operator("object.tissue_refresh_tessellate", icon='FILE_REFRESH') |
|
lock_icon = 'LOCKED' if props.bool_lock else 'UNLOCKED' |
|
#lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED' |
|
deps_icon = 'LINKED' if props.bool_dependencies else 'UNLINKED' |
|
row.prop(props, "bool_dependencies", text="", icon=deps_icon) |
|
row.prop(props, "bool_lock", text="", icon=lock_icon) |
|
col2 = row.column(align=True) |
|
col2.prop(props, "bool_run", text="",icon='TIME') |
|
col2.enabled = not props.bool_lock |
|
''' |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
row.label(text="Base :") |
|
row.label(text="Component :") |
|
row = col.row(align=True) |
|
|
|
col2 = row.column(align=True) |
|
col2.prop_search(props, "generator", context.scene, "objects") |
|
row.separator() |
|
col2 = row.column(align=True) |
|
col2.prop_search(props, "component", context.scene, "objects") |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.prop(props, "gen_modifiers", text="Use Modifiers", icon='MODIFIER') |
|
row.separator() |
|
try: |
|
if not (ob0.modifiers or ob0.data.shape_keys) or props.fill_mode == 'PATCH': |
|
col2.enabled = False |
|
except: |
|
col2.enabled = False |
|
col2 = row.column(align=True) |
|
col2.prop(props, "com_modifiers", text="Use Modifiers", icon='MODIFIER') |
|
try: |
|
if not (props.component.modifiers or props.component.data.shape_keys): |
|
col2.enabled = False |
|
except: |
|
col2.enabled = False |
|
''' |
|
layout.use_property_split = True |
|
layout.use_property_decorate = False # No animation. |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
row.label(text='Base:') |
|
row.prop_search(props, "generator", context.scene, "objects") |
|
col2 = row.column(align=True) |
|
col2.prop(props, "gen_modifiers", text='',icon='MODIFIER') |
|
try: |
|
if not (props.generator.modifiers or props.generator.data.shape_keys): |
|
col2.enabled = False |
|
except: |
|
col2.enabled = False |
|
col.separator() |
|
row = col.row(align=True) |
|
row.label(text='Component:') |
|
row.prop_search(props, "component", context.scene, "objects") |
|
col2 = row.column(align=True) |
|
col2.prop(props, "com_modifiers", text='',icon='MODIFIER') |
|
try: |
|
if not (props.component.modifiers or props.component.data.shape_keys): |
|
col2.enabled = False |
|
except: |
|
col2.enabled = False |
|
layout.use_property_split = False |
|
|
|
# Fill |
|
col = layout.column(align=True) |
|
col.label(text="Fill Mode:") |
|
|
|
# fill |
|
row = col.row(align=True) |
|
row.prop(props, "fill_mode", icon='NONE', expand=True, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
#layout.use_property_split = True |
|
col = layout.column(align=True) |
|
col.prop(props, "bool_smooth") |
|
|
|
|
|
class TISSUE_PT_tessellate_frame(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_parent_id = "TISSUE_PT_tessellate_object" |
|
bl_label = "Frame Settings" |
|
#bl_options = {'DEFAULT_CLOSED'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
bool_frame = context.object.tissue_tessellate.fill_mode == 'FRAME' |
|
bool_tessellated = context.object.tissue_tessellate.generator != None |
|
return context.object.type == 'MESH' and bool_frame and bool_tessellated |
|
except: |
|
return False |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
if bool_tessellated: |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
row.prop(props, "frame_mode", expand=True) |
|
row = col.row(align=True) |
|
row.prop(props, "frame_thickness", icon='NONE', expand=True) |
|
row = col.row(align=True) |
|
row.prop(props, "fill_frame", icon='NONE') |
|
show_frame_mat = props.bool_multi_components or props.bool_material_id |
|
if props.fill_frame and show_frame_mat: |
|
row.prop(props, "fill_frame_mat", icon='NONE') |
|
row = col.row(align=True) |
|
row.prop(props, "frame_boundary", text='Boundary', icon='NONE') |
|
if props.frame_boundary and show_frame_mat: |
|
row.prop(props, "frame_boundary_mat", icon='NONE') |
|
|
|
|
|
class TISSUE_PT_tessellate_coordinates(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_parent_id = "TISSUE_PT_tessellate_object" |
|
bl_label = "Component Coordinates" |
|
bl_options = {'DEFAULT_CLOSED'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
bool_tessellated = context.object.tissue_tessellate.generator != None |
|
return context.object.type == 'MESH' and bool_tessellated |
|
except: |
|
return False |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
if bool_tessellated: |
|
col = layout.column(align=True) |
|
# component XY |
|
row = col.row(align=True) |
|
row.prop(props, "mode", expand=True) |
|
|
|
if props.mode != 'BOUNDS': |
|
col.separator() |
|
row = col.row(align=True) |
|
row.label(text="X:") |
|
row.prop( |
|
props, "bounds_x", text="Bounds X", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
row = col.row(align=True) |
|
row.label(text="Y:") |
|
row.prop( |
|
props, "bounds_y", text="Bounds X", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
|
|
class TISSUE_PT_tessellate_rotation(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_parent_id = "TISSUE_PT_tessellate_object" |
|
bl_label = "Rotation" |
|
bl_options = {'DEFAULT_CLOSED'} |
|
|
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
bool_tessellated = context.object.tissue_tessellate.generator != None |
|
return context.object.type == 'MESH' and bool_tessellated |
|
except: |
|
return False |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
if bool_tessellated: |
|
# rotation |
|
layout.use_property_split = True |
|
layout.use_property_decorate = False # No animation. |
|
col = layout.column(align=True) |
|
col.prop(props, "rotation_mode", text='Rotation', icon='NONE', expand=False, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
if props.rotation_mode == 'WEIGHT': |
|
col.prop(props, "rotation_direction", expand=False, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
if props.rotation_mode == 'RANDOM': |
|
col.prop(props, "random_seed") |
|
else: |
|
col.prop(props, "rotation_shift") |
|
|
|
if props.rotation_mode == 'UV': |
|
uv_error = False |
|
if props.generator.type != 'MESH': |
|
row = col.row(align=True) |
|
row.label( |
|
text="UV rotation supported only for Mesh objects", |
|
icon='ERROR') |
|
uv_error = True |
|
else: |
|
if len(props.generator.data.uv_layers) == 0: |
|
row = col.row(align=True) |
|
row.label(text="'" + props.generator.name + |
|
" doesn't have UV Maps", icon='ERROR') |
|
uv_error = True |
|
if uv_error: |
|
row = col.row(align=True) |
|
row.label(text="Default rotation will be used instead", |
|
icon='INFO') |
|
|
|
class TISSUE_PT_tessellate_thickness(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_parent_id = "TISSUE_PT_tessellate_object" |
|
bl_label = "Thickness" |
|
#bl_options = {'DEFAULT_CLOSED'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
bool_tessellated = context.object.tissue_tessellate.generator != None |
|
return context.object.type == 'MESH' and bool_tessellated |
|
except: |
|
return False |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
#layout.use_property_split = True |
|
if bool_tessellated: |
|
col = layout.column(align=True) |
|
# component Z |
|
row = col.row(align=True) |
|
row.prop(props, "scale_mode", expand=True) |
|
col.prop(props, "zscale", text="Scale", icon='NONE', expand=False, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
if props.mode == 'BOUNDS': |
|
col.prop(props, "offset", text="Offset", icon='NONE', expand=False, |
|
slider=True, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
# Direction |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
row.label(text="Direction:") |
|
row = col.row(align=True) |
|
row.prop( |
|
props, "normals_mode", text="Direction", icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
|
|
class TISSUE_PT_tessellate_options(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_parent_id = "TISSUE_PT_tessellate_object" |
|
bl_label = " " |
|
bl_options = {'DEFAULT_CLOSED'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
bool_tessellated = context.object.tissue_tessellate.generator != None |
|
return context.object.type == 'MESH' and bool_tessellated |
|
except: |
|
return False |
|
|
|
def draw_header(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
self.layout.prop(props, "merge") |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
layout.use_property_split = True |
|
layout.use_property_decorate = False # No animation. |
|
if bool_tessellated: |
|
col = layout.column(align=True) |
|
if props.merge: |
|
col.prop(props, "merge_thres") |
|
col.prop(props, "bool_dissolve_seams") |
|
col.prop(props, "close_mesh") |
|
if props.close_mesh != 'NONE': |
|
#row = col.row(align=True) |
|
col.separator() |
|
col.prop(props, "open_edges_crease", text="Crease") |
|
col.prop(props, "cap_material_index", text='Material Index') |
|
if props.close_mesh == 'BRIDGE': |
|
col.separator() |
|
col.prop(props, "bridge_cuts") |
|
col.prop(props, "bridge_smoothness") |
|
|
|
|
|
class TISSUE_PT_tessellate_morphing(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_parent_id = "TISSUE_PT_tessellate_object" |
|
bl_label = "Weight and Morphing" |
|
bl_options = {'DEFAULT_CLOSED'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
bool_tessellated = context.object.tissue_tessellate.generator != None |
|
return context.object.type == 'MESH' and bool_tessellated |
|
except: |
|
return False |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
if bool_tessellated: |
|
allow_shapekeys = not props.com_modifiers |
|
for m in ob0.data.materials: |
|
try: |
|
o = bpy.data.objects[m.name] |
|
allow_multi = True |
|
try: |
|
if o.data.shape_keys is None: continue |
|
elif len(o.data.shape_keys.key_blocks) < 2: continue |
|
else: allow_shapekeys = not props.com_modifiers |
|
except: pass |
|
except: pass |
|
col = layout.column(align=True) |
|
#col.label(text="Morphing:") |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.prop(props, "bool_vertex_group", icon='GROUP_VERTEX') |
|
#col2.prop_search(props, "vertex_group", props.generator, "vertex_groups") |
|
try: |
|
if len(props.generator.vertex_groups) == 0: |
|
col2.enabled = False |
|
except: |
|
col2.enabled = False |
|
row.separator() |
|
col2 = row.column(align=True) |
|
row2 = col2.row(align=True) |
|
row2.prop(props, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA') |
|
row2.enabled = allow_shapekeys |
|
if not allow_shapekeys: |
|
col2 = layout.column(align=True) |
|
row2 = col2.row(align=True) |
|
row2.label(text="Use Shape Keys is not compatible with Use Modifiers", icon='INFO') |
|
|
|
|
|
class TISSUE_PT_tessellate_selective(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_parent_id = "TISSUE_PT_tessellate_object" |
|
bl_label = "Selective" |
|
bl_options = {'DEFAULT_CLOSED'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
bool_tessellated = context.object.tissue_tessellate.generator != None |
|
return context.object.type == 'MESH' and bool_tessellated |
|
except: |
|
return False |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
if bool_tessellated: |
|
allow_multi = False |
|
allow_shapekeys = not props.com_modifiers |
|
for m in ob0.data.materials: |
|
try: |
|
o = bpy.data.objects[m.name] |
|
allow_multi = True |
|
try: |
|
if o.data.shape_keys is None: continue |
|
elif len(o.data.shape_keys.key_blocks) < 2: continue |
|
else: allow_shapekeys = not props.com_modifiers |
|
except: pass |
|
except: pass |
|
# LIMITED TESSELLATION |
|
col = layout.column(align=True) |
|
#col.label(text="Limited Tessellation:") |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF') |
|
row.separator() |
|
if props.generator.type != 'MESH': |
|
col2.enabled = False |
|
col2 = row.column(align=True) |
|
col2.prop(props, "bool_material_id", icon='MATERIAL_DATA', text="Material ID") |
|
if props.bool_material_id and not props.bool_multi_components: |
|
#col2 = row.column(align=True) |
|
col2.prop(props, "material_id") |
|
if props.bool_multi_components: |
|
col2.enabled = False |
|
|
|
col.separator() |
|
row = col.row(align=True) |
|
col2 = row.column(align=True) |
|
col2.prop(props, "bool_multi_components", icon='MOD_TINT') |
|
if not allow_multi: |
|
col2.enabled = False |
|
|
|
|
|
class TISSUE_PT_tessellate_iterations(Panel): |
|
bl_space_type = 'PROPERTIES' |
|
bl_region_type = 'WINDOW' |
|
bl_context = "data" |
|
bl_parent_id = "TISSUE_PT_tessellate_object" |
|
bl_label = "Iterations" |
|
bl_options = {'DEFAULT_CLOSED'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
bool_tessellated = context.object.tissue_tessellate.generator != None |
|
return context.object.type == 'MESH' and bool_tessellated |
|
except: |
|
return False |
|
|
|
def draw(self, context): |
|
ob = context.object |
|
props = ob.tissue_tessellate |
|
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') |
|
|
|
try: |
|
bool_tessellated = props.generator or props.component != None |
|
ob0 = props.generator |
|
ob1 = props.component |
|
except: bool_tessellated = False |
|
layout = self.layout |
|
layout.use_property_split = True |
|
layout.use_property_decorate = False # No animation. |
|
if bool_tessellated: |
|
col = layout.column(align=True) |
|
row = col.row(align=True) |
|
#row.label(text='', icon='FILE_REFRESH') |
|
col.prop(props, 'iterations', text='Repeat')#, icon='FILE_REFRESH') |
|
if props.iterations > 1 and props.fill_mode == 'PATCH': |
|
col.separator() |
|
#row = col.row(align=True) |
|
col.prop(props, 'patch_subs') |
|
layout.use_property_split = False |
|
col = layout.column(align=True) |
|
#row = col.row(align=True) |
|
col.label(text='Combine Iterations:') |
|
row = col.row(align=True) |
|
row.prop( |
|
props, "combine_mode", text="Combine:",icon='NONE', expand=True, |
|
slider=False, toggle=False, icon_only=False, event=False, |
|
full_event=False, emboss=True, index=-1) |
|
|
|
class tissue_rotate_face_right(Operator): |
|
bl_idname = "mesh.tissue_rotate_face_right" |
|
bl_label = "Rotate Faces Right" |
|
bl_description = "Rotate clockwise selected faces and update tessellated meshes" |
|
bl_options = {'REGISTER', 'UNDO'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
#bool_tessellated = context.object.tissue_tessellate.generator != None |
|
ob = context.object |
|
return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated |
|
except: |
|
return False |
|
|
|
def execute(self, context): |
|
ob = context.active_object |
|
me = ob.data |
|
|
|
bm = bmesh.from_edit_mesh(me) |
|
mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode] |
|
|
|
for face in bm.faces: |
|
if (face.select): |
|
vs = face.verts[:] |
|
vs2 = vs[-1:]+vs[:-1] |
|
material_index = face.material_index |
|
bm.faces.remove(face) |
|
f2 = bm.faces.new(vs2) |
|
f2.select = True |
|
f2.material_index = material_index |
|
bm.normal_update() |
|
|
|
# trigger UI update |
|
bmesh.update_edit_mesh(me) |
|
ob.select_set(False) |
|
|
|
# update tessellated meshes |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
for o in [obj for obj in bpy.data.objects if |
|
obj.tissue_tessellate.generator == ob and obj.visible_get()]: |
|
context.view_layer.objects.active = o |
|
bpy.ops.object.tissue_update_tessellate() |
|
o.select_set(False) |
|
ob.select_set(True) |
|
context.view_layer.objects.active = ob |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
context.tool_settings.mesh_select_mode = mesh_select_mode |
|
|
|
return {'FINISHED'} |
|
|
|
class tissue_rotate_face_left(Operator): |
|
bl_idname = "mesh.tissue_rotate_face_left" |
|
bl_label = "Rotate Faces Left" |
|
bl_description = "Rotate counterclockwise selected faces and update tessellated meshes" |
|
bl_options = {'REGISTER', 'UNDO'} |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
try: |
|
#bool_tessellated = context.object.tissue_tessellate.generator != None |
|
ob = context.object |
|
return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated |
|
except: |
|
return False |
|
|
|
def execute(self, context): |
|
ob = context.active_object |
|
me = ob.data |
|
|
|
bm = bmesh.from_edit_mesh(me) |
|
mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode] |
|
|
|
for face in bm.faces: |
|
if (face.select): |
|
vs = face.verts[:] |
|
vs2 = vs[1:]+vs[:1] |
|
material_index = face.material_index |
|
bm.faces.remove(face) |
|
f2 = bm.faces.new(vs2) |
|
f2.select = True |
|
f2.material_index = material_index |
|
bm.normal_update() |
|
|
|
# trigger UI update |
|
bmesh.update_edit_mesh(me) |
|
ob.select_set(False) |
|
|
|
# update tessellated meshes |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
for o in [obj for obj in bpy.data.objects if |
|
obj.tissue_tessellate.generator == ob and obj.visible_get()]: |
|
context.view_layer.objects.active = o |
|
bpy.ops.object.tissue_update_tessellate() |
|
o.select_set(False) |
|
ob.select_set(True) |
|
context.view_layer.objects.active = ob |
|
bpy.ops.object.mode_set(mode='EDIT') |
|
context.tool_settings.mesh_select_mode = mesh_select_mode |
|
|
|
return {'FINISHED'} |
|
|
|
|
|
def convert_to_frame(ob, props, use_modifiers): |
|
new_ob = convert_object_to_mesh(ob, use_modifiers, True) |
|
|
|
# create bmesh |
|
bm = bmesh.new() |
|
bm.from_mesh(new_ob.data) |
|
bm.verts.ensure_lookup_table() |
|
bm.edges.ensure_lookup_table() |
|
bm.faces.ensure_lookup_table() |
|
if props.bool_selection: |
|
original_faces = [f for f in bm.faces if f.select] |
|
else: |
|
original_faces = list(bm.faces) |
|
# detect edge loops |
|
|
|
loops = [] |
|
boundaries_mat = [] |
|
neigh_face_center = [] |
|
face_normals = [] |
|
# append boundary loops |
|
if props.frame_boundary: |
|
#selected_edges = [e for e in bm.edges if e.select] |
|
selected_edges = [e for e in bm.edges if e.is_boundary] |
|
if len(selected_edges) > 0: |
|
loop = [] |
|
count = 0 |
|
e0 = selected_edges[0] |
|
face = e0.link_faces[0] |
|
boundary_mat = [face.material_index] |
|
face_center = [face.calc_center_median()] |
|
loop_normals = [face.normal] |
|
selected_edges = selected_edges[1:] |
|
if props.bool_vertex_group: |
|
n_verts = len(new_ob.data.vertices) |
|
base_vg = [get_weight(vg,n_verts) for vg in new_ob.vertex_groups] |
|
''' |
|
base_vg = [] |
|
for vg in new_ob.vertex_groups: |
|
vertex_group = [] |
|
for v in bm.verts: |
|
try: |
|
vertex_group.append(vg.weight(v.index)) |
|
except: |
|
vertex_group.append(0) |
|
base_vg.append(vertex_group) |
|
''' |
|
while True: |
|
new_vert = None |
|
face = None |
|
for e1 in selected_edges: |
|
if e1.verts[0] in e0.verts: new_vert = e1.verts[1] |
|
elif e1.verts[1] in e0.verts: new_vert = e1.verts[0] |
|
if new_vert != None: |
|
if len(loop)==0: |
|
loop = [v for v in e1.verts if v != new_vert] |
|
loop.append(new_vert) |
|
e0 = e1 |
|
face = e0.link_faces[0] |
|
boundary_mat.append(face.material_index) |
|
face_center.append(face.calc_center_median()) |
|
loop_normals.append(face.normal) |
|
selected_edges.remove(e0) |
|
break |
|
if new_vert == None: |
|
try: |
|
loops.append(loop) |
|
loop = [] |
|
e0 = selected_edges[0] |
|
selected_edges = selected_edges[1:] |
|
boundaries_mat.append(boundary_mat) |
|
neigh_face_center.append(face_center) |
|
face_normals.append(loop_normals) |
|
face = e0.link_faces[0] |
|
boundary_mat = [face.material_index] |
|
face_center = [face.calc_center_median()] |
|
loop_normals = [face.normal] |
|
except: break |
|
boundaries_mat.append(boundary_mat) |
|
neigh_face_center.append(face_center) |
|
face_normals.append(loop_normals) |
|
# compute boundary frames |
|
new_faces = [] |
|
vert_ids = [] |
|
|
|
# append regular faces |
|
for f in original_faces:#bm.faces: |
|
loop = list(f.verts) |
|
loops.append(loop) |
|
boundaries_mat.append([f.material_index for v in loop]) |
|
face_normals.append([f.normal for v in loop]) |
|
|
|
# calc areas for relative frame mode |
|
if props.frame_mode == 'RELATIVE': |
|
verts_area = [] |
|
for v in bm.verts: |
|
linked_faces = v.link_faces |
|
if len(linked_faces) > 0: |
|
area = sum([sqrt(f.calc_area())/len(f.verts) for f in v.link_faces])*2 |
|
area /= len(linked_faces) |
|
else: area = 0 |
|
verts_area.append(area) |
|
|
|
for loop_index, loop in enumerate(loops): |
|
is_boundary = loop_index < len(neigh_face_center) |
|
materials = boundaries_mat[loop_index] |
|
new_loop = [] |
|
loop_ext = [loop[-1]] + loop + [loop[0]] |
|
|
|
# calc tangents |
|
tangents = [] |
|
for i in range(len(loop)): |
|
# vertices |
|
vert0 = loop_ext[i] |
|
vert = loop_ext[i+1] |
|
vert1 = loop_ext[i+2] |
|
# edge vectors |
|
vec0 = (vert0.co - vert.co).normalized() |
|
vec1 = (vert.co - vert1.co).normalized() |
|
# tangent |
|
_vec1 = -vec1 |
|
_vec0 = -vec0 |
|
ang = (pi - vec0.angle(vec1))/2 |
|
normal = face_normals[loop_index][i] |
|
tan0 = normal.cross(vec0) |
|
tan1 = normal.cross(vec1) |
|
tangent = (tan0 + tan1).normalized()/sin(ang)*props.frame_thickness |
|
tangents.append(tangent) |
|
|
|
# calc correct direction for boundaries |
|
mult = -1 |
|
if is_boundary: |
|
dir_val = 0 |
|
for i in range(len(loop)): |
|
surf_point = neigh_face_center[loop_index][i] |
|
tangent = tangents[i] |
|
vert = loop_ext[i+1] |
|
dir_val += tangent.dot(vert.co - surf_point) |
|
if dir_val > 0: mult = 1 |
|
|
|
# add vertices |
|
for i in range(len(loop)): |
|
vert = loop_ext[i+1] |
|
if props.frame_mode == 'RELATIVE': area = verts_area[vert.index] |
|
else: area = 1 |
|
new_co = vert.co + tangents[i] * mult * area |
|
# add vertex |
|
new_vert = bm.verts.new(new_co) |
|
new_loop.append(new_vert) |
|
vert_ids.append(vert.index) |
|
new_loop.append(new_loop[0]) |
|
|
|
# add faces |
|
materials += [materials[0]] |
|
for i in range(len(loop)): |
|
v0 = loop_ext[i+1] |
|
v1 = loop_ext[i+2] |
|
v2 = new_loop[i+1] |
|
v3 = new_loop[i] |
|
face_verts = [v1,v0,v3,v2] |
|
if mult == -1: face_verts = [v0,v1,v2,v3] |
|
new_face = bm.faces.new(face_verts) |
|
new_face.material_index = materials[i+1] + props.frame_boundary_mat |
|
new_face.select = True |
|
new_faces.append(new_face) |
|
# fill frame |
|
if props.fill_frame and not is_boundary: |
|
n_verts = len(new_loop)-1 |
|
loop_center = Vector((0,0,0)) |
|
for v in new_loop[1:]: loop_center += v.co |
|
loop_center /= n_verts |
|
center = bm.verts.new(loop_center) |
|
for i in range(n_verts): |
|
v0 = new_loop[i+1] |
|
v1 = new_loop[i] |
|
face_verts = [v1,v0,center] |
|
new_face = bm.faces.new(face_verts) |
|
new_face.material_index = materials[i] + props.frame_boundary_mat |
|
new_face.select = True |
|
new_faces.append(new_face) |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
#for f in bm.faces: f.select_set(f not in new_faces) |
|
for f in original_faces: bm.faces.remove(f) |
|
bm.to_mesh(new_ob.data) |
|
# propagate vertex groups |
|
if props.bool_vertex_group: |
|
base_vg = [] |
|
for vg in new_ob.vertex_groups: |
|
vertex_group = [] |
|
for v in bm.verts: |
|
try: |
|
vertex_group.append(vg.weight(v.index)) |
|
except: |
|
vertex_group.append(0) |
|
base_vg.append(vertex_group) |
|
new_vert_ids = range(len(bm.verts)-len(vert_ids),len(bm.verts)) |
|
for vg_id, vg in enumerate(new_ob.vertex_groups): |
|
for ii, jj in zip(vert_ids, new_vert_ids): |
|
vg.add([jj], base_vg[vg_id][ii], 'REPLACE') |
|
new_ob.data.update() |
|
return new_ob |
|
|
|
def convert_to_fan(ob, props, use_modifiers): |
|
new_ob = convert_object_to_mesh(ob, use_modifiers, True) |
|
# make base object selected and active |
|
for o in bpy.context.view_layer.objects: o.select_set(False) |
|
new_ob.select_set(True) |
|
bpy.context.view_layer.objects.active = new_ob |
|
sk_index0 = new_ob.active_shape_key_index |
|
new_ob.active_shape_key_index = 0 |
|
|
|
bpy.ops.object.mode_set(mode='EDIT') |
|
bpy.ops.mesh.select_mode(type='FACE') |
|
if not props.bool_selection: |
|
bpy.ops.mesh.select_all(action='SELECT') |
|
bpy.ops.mesh.poke() |
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
return new_ob
|
|
|