Sun position: Fix T80379 - Custom startup breaks the add-on
[blender-addons.git] / mesh_tissue / tessellate_numpy.py
blob82799a833034df4e056f7a6b9c3aefd5ec6d3fe1
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
20 # ------------------------------- version 0.84 ------------------------------- #
21 # #
22 # Creates duplicates of selected mesh to active morphing the shape according #
23 # to target faces. #
24 # #
25 # (c) Alessandro Zomparelli #
26 # (2017) #
27 # #
28 # http://www.co-de-it.com/ #
29 # #
30 # ############################################################################ #
33 import bpy
34 from bpy.types import (
35 Operator,
36 Panel,
37 PropertyGroup,
39 from bpy.props import (
40 BoolProperty,
41 EnumProperty,
42 FloatProperty,
43 IntProperty,
44 StringProperty,
45 PointerProperty
47 from mathutils import Vector
48 import numpy as np
49 from math import sqrt
50 import random, time
51 import bmesh
52 from .utils import *
54 def anim_tessellate_active(self, context):
55 ob = context.object
56 props = ob.tissue_tessellate
57 if not props.bool_hold:
58 try:
59 props.generator.name
60 props.component.name
61 bpy.ops.object.update_tessellate()
62 except: pass
64 def anim_tessellate_object(ob):
65 try:
66 #bpy.context.view_layer.objects.active = ob
67 bpy.ops.object.update_tessellate()
68 except:
69 return None
71 #from bpy.app.handlers import persistent
73 #@persistent
74 def anim_tessellate(scene):
75 # store selected objects
76 #scene = context.scene
77 try: active_object = bpy.context.object
78 except: active_object = None
79 try: selected_objects = bpy.context.selected_objects
80 except: selected_objects = []
81 if bpy.context.mode in ('OBJECT', 'PAINT_WEIGHT'):
82 old_mode = bpy.context.mode
83 if old_mode == 'PAINT_WEIGHT': old_mode = 'WEIGHT_PAINT'
84 for ob in scene.objects:
85 if ob.tissue_tessellate.bool_run:
86 hidden = ob.hide_viewport
87 ob.hide_viewport = False
88 for o in scene.objects:
89 if not o.hide_viewport: ob.select_set(False)
90 bpy.context.view_layer.objects.active = ob
91 ob.select_set(True)
92 try:
93 bpy.ops.object.update_tessellate()
94 except: pass
95 ob.hide_viewport = hidden
96 # restore selected objects
97 for o in scene.objects:
98 if not o.hide_viewport: o.select_set(False)
99 for o in selected_objects:
100 if not o.hide_viewport: o.select_set(True)
101 bpy.context.view_layer.objects.active = active_object
102 try: bpy.ops.object.mode_set(mode=old_mode)
103 except: pass
104 return
106 def set_tessellate_handler(self, context):
107 old_handlers = []
108 blender_handlers = bpy.app.handlers.frame_change_post
109 for h in blender_handlers:
110 if "anim_tessellate" in str(h):
111 old_handlers.append(h)
112 for h in old_handlers: blender_handlers.remove(h)
113 for o in context.scene.objects:
114 if o.tissue_tessellate.bool_run:
115 blender_handlers.append(anim_tessellate)
116 break
117 return
119 class tissue_tessellate_prop(PropertyGroup):
120 bool_hold : BoolProperty(
121 name="Hold Update",
122 description="Prevent automatic update while other properties are changed",
123 default=False
125 bool_run : BoolProperty(
126 name="Animatable Tessellation",
127 description="Automatically recompute the tessellation when the frame is changed. Currently is not working during Render Animation",
128 default = False,
129 update = set_tessellate_handler
131 zscale : FloatProperty(
132 name="Scale", default=1, soft_min=0, soft_max=10,
133 description="Scale factor for the component thickness",
134 update = anim_tessellate_active
136 scale_mode : EnumProperty(
137 items=(
138 ('CONSTANT', "Constant", "Uniform thinkness"),
139 ('ADAPTIVE', "Proportional", "Preserve component's proportions")
141 default='ADAPTIVE',
142 name="Z-Scale according to faces size",
143 update = anim_tessellate_active
145 offset : FloatProperty(
146 name="Surface Offset",
147 default=1,
148 min=-1,
149 max=1,
150 soft_min=-1,
151 soft_max=1,
152 description="Surface offset",
153 update = anim_tessellate_active
155 mode : EnumProperty(
156 items=(
157 ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
158 ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
159 ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
160 default='BOUNDS',
161 name="Component Mode",
162 update = anim_tessellate_active
164 rotation_mode : EnumProperty(
165 items=(('RANDOM', "Random", "Random faces rotation"),
166 ('UV', "Active UV", "Rotate according to UV coordinates"),
167 ('DEFAULT', "Default", "Default rotation")),
168 default='DEFAULT',
169 name="Component Rotation",
170 update = anim_tessellate_active
172 fill_mode : EnumProperty(
173 items=(
174 ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
175 ('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
176 ('PATCH', 'Patch', 'Curved tessellation according to the last ' +
177 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
178 'patches.\nAfter the last Subsurf (or Multires) only ' +
179 'deformation\nmodifiers can be used')),
180 default='QUAD',
181 name="Fill Mode",
182 update = anim_tessellate_active
184 combine_mode : EnumProperty(
185 items=(
186 ('LAST', 'Last', 'Show only the last iteration'),
187 ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
188 ('ALL', 'All', 'Combine the result of all iterations')),
189 default='LAST',
190 name="Combine Mode",
191 update = anim_tessellate_active
193 gen_modifiers : BoolProperty(
194 name="Generator Modifiers",
195 default=False,
196 description="Apply Modifiers and Shape Keys to the base object",
197 update = anim_tessellate_active
199 com_modifiers : BoolProperty(
200 name="Component Modifiers",
201 default=False,
202 description="Apply Modifiers and Shape Keys to the component object",
203 update = anim_tessellate_active
205 merge : BoolProperty(
206 name="Merge",
207 default=False,
208 description="Merge vertices in adjacent duplicates",
209 update = anim_tessellate_active
211 merge_thres : FloatProperty(
212 name="Distance",
213 default=0.001,
214 soft_min=0,
215 soft_max=10,
216 description="Limit below which to merge vertices",
217 update = anim_tessellate_active
219 generator : PointerProperty(
220 type=bpy.types.Object,
221 name="",
222 description="Base object for the tessellation",
223 update = anim_tessellate_active
225 component : PointerProperty(
226 type=bpy.types.Object,
227 name="",
228 description="Component object for the tessellation",
229 #default="",
230 update = anim_tessellate_active
232 bool_random : BoolProperty(
233 name="Randomize",
234 default=False,
235 description="Randomize component rotation",
236 update = anim_tessellate_active
238 random_seed : IntProperty(
239 name="Seed",
240 default=0,
241 soft_min=0,
242 soft_max=10,
243 description="Random seed",
244 update = anim_tessellate_active
246 bool_vertex_group : BoolProperty(
247 name="Map Vertex Group",
248 default=False,
249 description="Transfer all Vertex Groups from Base object",
250 update = anim_tessellate_active
252 bool_selection : BoolProperty(
253 name="On selected Faces",
254 default=False,
255 description="Create Tessellation only on selected faces",
256 update = anim_tessellate_active
258 bool_shapekeys : BoolProperty(
259 name="Use Shape Keys",
260 default=False,
261 description="Transfer Component's Shape Keys. If the name of Vertex "
262 "Groups and Shape Keys are the same, they will be "
263 "automatically combined",
264 update = anim_tessellate_active
266 bool_smooth : BoolProperty(
267 name="Smooth Shading",
268 default=False,
269 description="Output faces with smooth shading rather than flat shaded",
270 update = anim_tessellate_active
272 bool_materials : BoolProperty(
273 name="Transfer Materials",
274 default=False,
275 description="Preserve component's materials",
276 update = anim_tessellate_active
278 bool_material_id : BoolProperty(
279 name="Tessellation on Material ID",
280 default=False,
281 description="Apply the component only on the selected Material",
282 update = anim_tessellate_active
284 material_id : IntProperty(
285 name="Material ID",
286 default=0,
287 min=0,
288 description="Material ID",
289 update = anim_tessellate_active
291 bool_dissolve_seams : BoolProperty(
292 name="Dissolve Seams",
293 default=False,
294 description="Dissolve all seam edges",
295 update = anim_tessellate_active
297 iterations : IntProperty(
298 name="Iterations",
299 default=1,
300 min=1,
301 soft_max=5,
302 description="Automatically repeat the Tessellation using the "
303 + "generated geometry as new base object.\nUsefull for "
304 + "for branching systems. Dangerous!",
305 update = anim_tessellate_active
307 bool_combine : BoolProperty(
308 name="Combine unused",
309 default=False,
310 description="Combine the generated geometry with unused faces",
311 update = anim_tessellate_active
313 bool_advanced : BoolProperty(
314 name="Advanced Settings",
315 default=False,
316 description="Show more settings"
318 normals_mode : EnumProperty(
319 items=(
320 ('VERTS', 'Along Normals', 'Consistent direction based on vertices normal'),
321 ('FACES', 'Individual Faces', 'Based on individual faces normal')),
322 default='VERTS',
323 name="Direction",
324 update = anim_tessellate_active
326 bool_multi_components : BoolProperty(
327 name="Multi Components",
328 default=False,
329 description="Combine different components according to materials name",
330 update = anim_tessellate_active
332 error_message : StringProperty(
333 name="Error Message",
334 default=""
336 warning_message : StringProperty(
337 name="Warning Message",
338 default=""
340 bounds_x : EnumProperty(
341 items=(
342 ('EXTEND', 'Extend', 'Default X coordinates'),
343 ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
344 ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
345 default='EXTEND',
346 name="Bounds X",
347 update = anim_tessellate_active
349 bounds_y : EnumProperty(
350 items=(
351 ('EXTEND', 'Extend', 'Default Y coordinates'),
352 ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
353 ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
354 default='EXTEND',
355 name="Bounds Y",
356 update = anim_tessellate_active
358 cap_faces : BoolProperty(
359 name="Cap Holes",
360 default=False,
361 description="Cap open edges loops",
362 update = anim_tessellate_active
364 open_edges_crease : FloatProperty(
365 name="Open Edges Crease",
366 default=0,
367 min=0,
368 max=1,
369 description="Automatically set crease for open edges",
370 update = anim_tessellate_active
373 def store_parameters(operator, ob):
374 ob.tissue_tessellate.bool_hold = True
375 ob.tissue_tessellate.generator = bpy.data.objects[operator.generator]
376 ob.tissue_tessellate.component = bpy.data.objects[operator.component]
377 ob.tissue_tessellate.zscale = operator.zscale
378 ob.tissue_tessellate.offset = operator.offset
379 ob.tissue_tessellate.gen_modifiers = operator.gen_modifiers
380 ob.tissue_tessellate.com_modifiers = operator.com_modifiers
381 ob.tissue_tessellate.mode = operator.mode
382 ob.tissue_tessellate.rotation_mode = operator.rotation_mode
383 ob.tissue_tessellate.merge = operator.merge
384 ob.tissue_tessellate.merge_thres = operator.merge_thres
385 ob.tissue_tessellate.scale_mode = operator.scale_mode
386 ob.tissue_tessellate.bool_random = operator.bool_random
387 ob.tissue_tessellate.random_seed = operator.random_seed
388 ob.tissue_tessellate.fill_mode = operator.fill_mode
389 ob.tissue_tessellate.bool_vertex_group = operator.bool_vertex_group
390 ob.tissue_tessellate.bool_selection = operator.bool_selection
391 ob.tissue_tessellate.bool_shapekeys = operator.bool_shapekeys
392 ob.tissue_tessellate.bool_smooth = operator.bool_smooth
393 ob.tissue_tessellate.bool_materials = operator.bool_materials
394 ob.tissue_tessellate.bool_material_id = operator.bool_material_id
395 ob.tissue_tessellate.material_id = operator.material_id
396 ob.tissue_tessellate.bool_dissolve_seams = operator.bool_dissolve_seams
397 ob.tissue_tessellate.iterations = operator.iterations
398 ob.tissue_tessellate.bool_advanced = operator.bool_advanced
399 ob.tissue_tessellate.normals_mode = operator.normals_mode
400 ob.tissue_tessellate.bool_combine = operator.bool_combine
401 ob.tissue_tessellate.bool_multi_components = operator.bool_multi_components
402 ob.tissue_tessellate.combine_mode = operator.combine_mode
403 ob.tissue_tessellate.bounds_x = operator.bounds_x
404 ob.tissue_tessellate.bounds_y = operator.bounds_y
405 ob.tissue_tessellate.cap_faces = operator.cap_faces
406 ob.tissue_tessellate.bool_hold = False
407 return ob
409 def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode,
410 scale_mode, rotation_mode, rand_seed, bool_vertex_group,
411 bool_selection, bool_shapekeys, bool_material_id, material_id,
412 bounds_x, bounds_y):
413 random.seed(rand_seed)
415 ob0 = convert_object_to_mesh(_ob0)
416 me0 = _ob0.data
418 # Check if zero faces are selected
419 if _ob0.type == 'MESH':
420 bool_cancel = True
421 for p in me0.polygons:
422 check_sel = check_mat = False
423 if not bool_selection or p.select: check_sel = True
424 if not bool_material_id or p.material_index == material_id: check_mat = True
425 if check_sel and check_mat:
426 bool_cancel = False
427 break
428 if bool_cancel:
429 return 0
431 levels = 0
432 sculpt_levels = 0
433 render_levels = 0
434 bool_multires = False
435 multires_name = ""
436 not_allowed = ['FLUID_SIMULATION', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD',
437 'DECIMATE', 'EDGE_SPLIT', 'MASK', 'MIRROR', 'REMESH',
438 'SCREW', 'SOLIDIFY', 'TRIANGULATE', 'WIREFRAME', 'SKIN',
439 'EXPLODE', 'PARTICLE_INSTANCE', 'PARTICLE_SYSTEM', 'SMOKE']
440 modifiers0 = list(_ob0.modifiers)#[m for m in ob0.modifiers]
441 show_modifiers = [m.show_viewport for m in _ob0.modifiers]
442 show_modifiers.reverse()
443 modifiers0.reverse()
444 for m in modifiers0:
445 visible = m.show_viewport
446 #m.show_viewport = False
447 if m.type in ('SUBSURF', 'MULTIRES') and visible:
448 levels = m.levels
449 multires_name = m.name
450 if m.type == 'MULTIRES':
451 bool_multires = True
452 multires_name = m.name
453 sculpt_levels = m.sculpt_levels
454 render_levels = m.render_levels
455 else: bool_multires = False
456 break
457 elif m.type in not_allowed:
458 #ob0.data = old_me0
459 #bpy.data.meshes.remove(me0)
460 return "modifiers_error"
462 before = _ob0.copy()
463 #if ob0.type == 'MESH': before.data = me0
464 before_mod = list(before.modifiers)
465 before_mod.reverse()
466 for m in before_mod:
467 if m.type in ('SUBSURF', 'MULTIRES') and m.show_viewport:
468 before.modifiers.remove(m)
469 break
470 else: before.modifiers.remove(m)
472 before_subsurf = simple_to_mesh(before)
474 before_bm = bmesh.new()
475 before_bm.from_mesh(before_subsurf)
476 before_bm.faces.ensure_lookup_table()
477 for f in before_bm.faces:
478 if len(f.loops) != 4:
479 return "topology_error"
480 before_bm.edges.ensure_lookup_table()
481 for e in before_bm.edges:
482 if len(e.link_faces) == 0:
483 return "wires_error"
484 before_bm.verts.ensure_lookup_table()
485 for v in before_bm.verts:
486 if len(v.link_faces) == 0:
487 return "verts_error"
489 me0 = ob0.data
490 verts0 = me0.vertices # Collect generator vertices
492 if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
494 # set Shape Keys to zero
495 if bool_shapekeys:
496 try:
497 original_key_values = []
498 for sk in _ob1.data.shape_keys.key_blocks:
499 original_key_values.append(sk.value)
500 sk.value = 0
501 except:
502 bool_shapekeys = False
504 if not com_modifiers and not bool_shapekeys:
505 mod_visibility = []
506 for m in _ob1.modifiers:
507 mod_visibility.append(m.show_viewport)
508 m.show_viewport = False
509 com_modifiers = True
511 ob1 = convert_object_to_mesh(_ob1, com_modifiers, False)
512 me1 = ob1.data
514 if mode != 'BOUNDS':
515 bpy.context.object.active_shape_key_index = 0
516 # Bound X
517 if bounds_x != 'EXTEND':
518 if mode == 'GLOBAL':
519 planes_co = ((0,0,0),(1,1,1))
520 plane_no = (1,0,0)
521 if mode == 'LOCAL':
522 planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0)))
523 plane_no = planes_co[0]-planes_co[1]
524 bpy.ops.object.mode_set(mode='EDIT')
525 for co in planes_co:
526 bpy.ops.mesh.select_all(action='SELECT')
527 bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
528 bpy.ops.mesh.mark_seam()
529 bpy.ops.object.mode_set(mode='OBJECT')
530 _faces = ob1.data.polygons
531 if mode == 'GLOBAL':
532 for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]:
533 f.select = True
534 for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]:
535 f.select = True
536 else:
537 for f in [f for f in _faces if f.center.x > 1]:
538 f.select = True
539 for f in [f for f in _faces if f.center.x < 0]:
540 f.select = True
541 bpy.ops.object.mode_set(mode='EDIT')
542 bpy.ops.mesh.select_mode(type='FACE')
543 if bounds_x == 'CLIP':
544 bpy.ops.mesh.delete(type='FACE')
545 bpy.ops.object.mode_set(mode='OBJECT')
546 if bounds_x == 'CYCLIC':
547 bpy.ops.mesh.split()
548 bpy.ops.object.mode_set(mode='OBJECT')
549 # Bound Y
550 if bounds_y != 'EXTEND':
551 if mode == 'GLOBAL':
552 planes_co = ((0,0,0),(1,1,1))
553 plane_no = (0,1,0)
554 if mode == 'LOCAL':
555 planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0)))
556 plane_no = planes_co[0]-planes_co[1]
557 bpy.ops.object.mode_set(mode='EDIT')
558 for co in planes_co:
559 bpy.ops.mesh.select_all(action='SELECT')
560 bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
561 bpy.ops.mesh.mark_seam()
562 bpy.ops.object.mode_set(mode='OBJECT')
563 _faces = ob1.data.polygons
564 if mode == 'GLOBAL':
565 for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]:
566 f.select = True
567 for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]:
568 f.select = True
569 else:
570 for f in [f for f in _faces if f.center.y > 1]:
571 f.select = True
572 for f in [f for f in _faces if f.center.y < 0]:
573 f.select = True
575 bpy.ops.object.mode_set(mode='EDIT')
576 bpy.ops.mesh.select_mode(type='FACE')
577 if bounds_y == 'CLIP':
578 bpy.ops.mesh.delete(type='FACE')
579 bpy.ops.object.mode_set(mode='OBJECT')
580 if bounds_y == 'CYCLIC':
581 bpy.ops.mesh.split()
582 bpy.ops.object.mode_set(mode='OBJECT')
583 bpy.ops.object.mode_set(mode='OBJECT')
585 # Component statistics
586 n_verts = len(me1.vertices)
588 # Create empty lists
589 new_verts = []
590 new_edges = []
591 new_faces = []
592 new_verts_np = np.array(())
594 # Component bounding box
595 min_c = Vector((0, 0, 0))
596 max_c = Vector((0, 0, 0))
597 first = True
598 for v in me1.vertices:
599 vert = v.co
600 if vert[0] < min_c[0] or first:
601 min_c[0] = vert[0]
602 if vert[1] < min_c[1] or first:
603 min_c[1] = vert[1]
604 if vert[2] < min_c[2] or first:
605 min_c[2] = vert[2]
606 if vert[0] > max_c[0] or first:
607 max_c[0] = vert[0]
608 if vert[1] > max_c[1] or first:
609 max_c[1] = vert[1]
610 if vert[2] > max_c[2] or first:
611 max_c[2] = vert[2]
612 first = False
613 bb = max_c - min_c
615 # adaptive XY
616 verts1 = []
617 for v in me1.vertices:
618 if mode == 'BOUNDS':
619 vert = v.co - min_c # (ob1.matrix_world * v.co) - min_c
620 vert[0] = (vert[0] / bb[0] if bb[0] != 0 else 0.5)
621 vert[1] = (vert[1] / bb[1] if bb[1] != 0 else 0.5)
622 vert[2] = (vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
623 elif mode == 'LOCAL':
624 vert = v.co.xyz
625 vert[2] *= zscale
626 #vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
627 elif mode == 'GLOBAL':
628 vert = ob1.matrix_world @ v.co
629 vert[2] *= zscale
630 try:
631 for sk in me1.shape_keys.key_blocks:
632 sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
633 except: pass
634 #verts1.append(vert)
635 v.co = vert
637 # Bounds X, Y
638 if mode != 'BOUNDS':
639 if bounds_x == 'CYCLIC':
640 move_verts = []
641 for f in [f for f in me1.polygons if (f.center).x > 1]:
642 for v in f.vertices:
643 if v not in move_verts: move_verts.append(v)
644 for v in move_verts:
645 me1.vertices[v].co.x -= 1
646 try:
647 _ob1.active_shape_key_index = 0
648 for sk in me1.shape_keys.key_blocks:
649 sk.data[v].co.x -= 1
650 except: pass
651 move_verts = []
652 for f in [f for f in me1.polygons if (f.center).x < 0]:
653 for v in f.vertices:
654 if v not in move_verts: move_verts.append(v)
655 for v in move_verts:
656 me1.vertices[v].co.x += 1
657 try:
658 _ob1.active_shape_key_index = 0
659 for sk in me1.shape_keys.key_blocks:
660 sk.data[v].co.x += 1
661 except: pass
662 if bounds_y == 'CYCLIC':
663 move_verts = []
664 for f in [f for f in me1.polygons if (f.center).y > 1]:
665 for v in f.vertices:
666 if v not in move_verts: move_verts.append(v)
667 for v in move_verts:
668 me1.vertices[v].co.y -= 1
669 try:
670 _ob1.active_shape_key_index = 0
671 for sk in me1.shape_keys.key_blocks:
672 sk.data[v].co.y -= 1
673 except: pass
674 move_verts = []
675 for f in [f for f in me1.polygons if (f.center).y < 0]:
676 for v in f.vertices:
677 if v not in move_verts: move_verts.append(v)
678 for v in move_verts:
679 me1.vertices[v].co.y += 1
680 try:
681 _ob1.active_shape_key_index = 0
682 for sk in me1.shape_keys.key_blocks:
683 sk.data[v].co.y += 1
684 except: pass
685 verts1 = [v.co for v in me1.vertices]
687 patch_faces = 4**levels
688 sides = int(sqrt(patch_faces))
689 sides0 = sides-2
690 patch_faces0 = int((sides-2)**2)
691 n_patches = int(len(me0.polygons)/patch_faces)
692 if len(me0.polygons)%patch_faces != 0:
693 #ob0.data = old_me0
694 return "topology_error"
696 new_verts = []
697 new_edges = []
698 new_faces = []
700 for o in bpy.context.view_layer.objects: o.select_set(False)
701 new_patch = None
703 # All vertex group
704 if bool_vertex_group:
705 try:
706 weight = []
707 for vg in ob0.vertex_groups:
708 _weight = []
709 for v in me0.vertices:
710 try:
711 _weight.append(vg.weight(v.index))
712 except:
713 _weight.append(0)
714 weight.append(_weight)
715 except:
716 bool_vertex_group = False
718 # Adaptive Z
719 if scale_mode == 'ADAPTIVE':
720 if mode == 'BOUNDS': com_area = (bb[0]*bb[1])
721 else: com_area = 1
722 mult = 1/com_area*patch_faces
723 verts_area = []
724 bm = bmesh.new()
725 bm.from_mesh(me0)
726 bm.verts.ensure_lookup_table()
727 for v in bm.verts:
728 area = 0
729 faces = v.link_faces
730 for f in faces:
731 area += f.calc_area()
732 area/=len(faces)
733 area*=mult
734 verts_area.append(sqrt(area))
736 random.seed(rand_seed)
737 bool_correct = False
739 _faces = [[[0] for ii in range(sides)] for jj in range(sides)]
740 _verts = [[[0] for ii in range(sides+1)] for jj in range(sides+1)]
742 for i in range(n_patches):
743 poly = me0.polygons[i*patch_faces]
744 if bool_selection and not poly.select: continue
745 if bool_material_id and not poly.material_index == material_id: continue
747 bool_correct = True
748 new_patch = bpy.data.objects.new("patch", me1.copy())
749 bpy.context.collection.objects.link(new_patch)
751 new_patch.select_set(True)
752 bpy.context.view_layer.objects.active = new_patch
754 for area in bpy.context.screen.areas:
755 for space in area.spaces:
756 try: new_patch.local_view_set(space, True)
757 except: pass
759 # Vertex Group
760 if bool_vertex_group:
761 for vg in ob0.vertex_groups:
762 new_patch.vertex_groups.new(name=vg.name)
764 # find patch faces
765 faces = _faces.copy()
766 verts = _verts.copy()
767 shift1 = sides
768 shift2 = sides*2-1
769 shift3 = sides*3-2
770 for j in range(patch_faces):
771 if j < patch_faces0:
772 if levels == 0:
773 u = j%sides0
774 v = j//sides0
775 else:
776 u = j%sides0+1
777 v = j//sides0+1
778 elif j < patch_faces0 + shift1:
779 u = j-patch_faces0
780 v = 0
781 elif j < patch_faces0 + shift2:
782 u = sides-1
783 v = j-(patch_faces0 + sides)+1
784 elif j < patch_faces0 + shift3:
785 jj = j-(patch_faces0 + shift2)
786 u = sides-jj-2
787 v = sides-1
788 else:
789 jj = j-(patch_faces0 + shift3)
790 u = 0
791 v = sides-jj-2
792 face = me0.polygons[j+i*patch_faces]
793 faces[u][v] = face
794 verts[u][v] = verts0[face.vertices[0]]
795 if u == sides-1:
796 verts[sides][v] = verts0[face.vertices[1]]
797 if v == sides-1:
798 verts[u][sides] = verts0[face.vertices[3]]
799 if u == v == sides-1:
800 verts[sides][sides] = verts0[face.vertices[2]]
802 # Random rotation
803 if rotation_mode == 'RANDOM':
804 rand = random.randint(0, 3)
805 if rand == 1:
806 verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
807 elif rand == 2:
808 verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
809 elif rand == 3:
810 verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
812 # UV rotation
813 elif rotation_mode == 'UV' and ob0.type == 'MESH':
814 if len(ob0.data.uv_layers) > 0:
815 uv0 = me0.uv_layers.active.data[faces[0][0].index*4].uv
816 uv1 = me0.uv_layers.active.data[faces[0][-1].index*4 + 3].uv
817 uv2 = me0.uv_layers.active.data[faces[-1][-1].index*4 + 2].uv
818 uv3 = me0.uv_layers.active.data[faces[-1][0].index*4 + 1].uv
819 v01 = (uv0 + uv1)
820 v32 = (uv3 + uv2)
821 v0132 = v32 - v01
822 v0132.normalize()
823 v12 = (uv1 + uv2)
824 v03 = (uv0 + uv3)
825 v1203 = v03 - v12
826 v1203.normalize()
828 vertUV = []
829 dot1203 = v1203.x
830 dot0132 = v0132.x
831 if(abs(dot1203) < abs(dot0132)):
832 if (dot0132 > 0):
833 pass
834 else:
835 verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
836 else:
837 if(dot1203 < 0):
838 verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
839 else:
840 verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
842 step = 1/sides
843 for vert, patch_vert in zip(verts1, new_patch.data.vertices):
844 # grid coordinates
845 u = int(vert[0]//step)
846 v = int(vert[1]//step)
847 u1 = min(u+1, sides)
848 v1 = min(v+1, sides)
849 if mode != 'BOUNDS':
850 if u > sides-1:
851 u = sides-1
852 u1 = sides
853 if u < 0:
854 u = 0
855 u1 = 1
856 if v > sides-1:
857 v = sides-1
858 v1 = sides
859 if v < 0:
860 v = 0
861 v1 = 1
862 v00 = verts[u][v]
863 v10 = verts[u1][v]
864 v01 = verts[u][v1]
865 v11 = verts[u1][v1]
866 # factor coordinates
867 fu = (vert[0]-u*step)/step
868 fv = (vert[1]-v*step)/step
869 fw = vert.z
870 # interpolate Z scaling factor
871 fvec2d = Vector((fu,fv,0))
872 if scale_mode == 'ADAPTIVE':
873 a00 = verts_area[v00.index]
874 a10 = verts_area[v10.index]
875 a01 = verts_area[v01.index]
876 a11 = verts_area[v11.index]
877 fw*=lerp2(a00,a10,a01,a11,fvec2d)
878 # build factor vector
879 fvec = Vector((fu,fv,fw))
880 # interpolate vertex on patch
881 patch_vert.co = lerp3(v00, v10, v01, v11, fvec)
883 # Vertex Group
884 if bool_vertex_group:
885 for _weight, vg in zip(weight, new_patch.vertex_groups):
886 w00 = _weight[v00.index]
887 w10 = _weight[v10.index]
888 w01 = _weight[v01.index]
889 w11 = _weight[v11.index]
890 wuv = lerp2(w00,w10,w01,w11, fvec2d)
891 vg.add([patch_vert.index], wuv, "ADD")
893 if bool_shapekeys:
894 for sk in ob1.data.shape_keys.key_blocks:
895 source = sk.data
896 for sk_v, _v in zip(source, me1.vertices):
897 if mode == 'BOUNDS':
898 sk_vert = sk_v.co - min_c # (ob1.matrix_world * v.co) - min_c
899 sk_vert[0] = (sk_vert[0] / bb[0] if bb[0] != 0 else 0.5)
900 sk_vert[1] = (sk_vert[1] / bb[1] if bb[1] != 0 else 0.5)
901 sk_vert[2] = (sk_vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
902 elif mode == 'LOCAL':
903 sk_vert = sk_v.co#.xyzco
904 #sk_vert[2] *= zscale
905 #sk_vert[2] = (sk_vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
906 elif mode == 'GLOBAL':
907 #sk_vert = ob1.matrix_world @ sk_v.co
908 sk_vert = sk_v.co
909 #sk_vert[2] *= zscale
911 # grid coordinates
912 u = int(sk_vert[0]//step)
913 v = int(sk_vert[1]//step)
914 u1 = min(u+1, sides)
915 v1 = min(v+1, sides)
916 if mode != 'BOUNDS':
917 if u > sides-1:
918 u = sides-1
919 u1 = sides
920 if u < 0:
921 u = 0
922 u1 = 1
923 if v > sides-1:
924 v = sides-1
925 v1 = sides
926 if v < 0:
927 v = 0
928 v1 = 1
929 v00 = verts[u][v]
930 v10 = verts[u1][v]
931 v01 = verts[u][v1]
932 v11 = verts[u1][v1]
933 # factor coordinates
934 fu = (sk_vert[0]-u*step)/step
935 fv = (sk_vert[1]-v*step)/step
936 fw = sk_vert.z
938 if scale_mode == 'ADAPTIVE':
939 a00 = verts_area[v00.index]
940 a10 = verts_area[v10.index]
941 a01 = verts_area[v01.index]
942 a11 = verts_area[v11.index]
943 fw*=lerp2(a00,a10,a01,a11,Vector((fu,fv,0)))
945 fvec = Vector((fu,fv,fw))
946 sk_co = lerp3(v00, v10, v01, v11, fvec)
948 new_patch.data.shape_keys.key_blocks[sk.name].data[_v.index].co = sk_co
950 #if ob0.type == 'MESH': ob0.data = old_me0
951 if not bool_correct: return 0
953 bpy.ops.object.join()
955 if bool_shapekeys:
956 # set original values and combine Shape Keys and Vertex Groups
957 for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
958 sk.value = val
959 new_patch.data.shape_keys.key_blocks[sk.name].value = val
960 if bool_vertex_group:
961 for sk in new_patch.data.shape_keys.key_blocks:
962 for vg in new_patch.vertex_groups:
963 if sk.name == vg.name:
964 sk.vertex_group = vg.name
966 new_name = ob0.name + "_" + ob1.name
967 new_patch.name = "tessellate_temp"
969 if bool_multires:
970 for m in ob0.modifiers:
971 if m.type == 'MULTIRES' and m.name == multires_name:
972 m.levels = levels
973 m.sculpt_levels = sculpt_levels
974 m.render_levels = render_levels
975 # restore original modifiers visibility for component object
976 try:
977 for m, vis in zip(_ob1.modifiers, mod_visibility):
978 m.show_viewport = vis
979 except: pass
981 bpy.data.objects.remove(before)
982 bpy.data.objects.remove(ob0)
983 bpy.data.objects.remove(ob1)
984 return new_patch
986 def tessellate_original(_ob0, _ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
987 scale_mode, rotation_mode, rand_seed, fill_mode,
988 bool_vertex_group, bool_selection, bool_shapekeys,
989 bool_material_id, material_id, normals_mode, bounds_x, bounds_y):
991 if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
992 random.seed(rand_seed)
994 if bool_shapekeys:
995 try:
996 original_key_values = []
997 for sk in _ob1.data.shape_keys.key_blocks:
998 original_key_values.append(sk.value)
999 sk.value = 0
1000 except:
1001 bool_shapekeys = False
1003 ob0 = convert_object_to_mesh(_ob0, gen_modifiers, True)
1004 me0 = ob0.data
1005 ob1 = convert_object_to_mesh(_ob1, com_modifiers, True)
1006 me1 = ob1.data
1008 base_polygons = []
1009 base_face_normals = []
1011 n_faces0 = len(me0.polygons)
1013 # Check if zero faces are selected
1014 if (bool_selection and ob0.type == 'MESH') or bool_material_id:
1015 for p in me0.polygons:
1016 if (bool_selection and ob0.type == 'MESH'):
1017 is_sel = p.select
1018 else: is_sel = True
1019 if bool_material_id:
1020 is_mat = p.material_index == material_id
1021 else: is_mat = True
1022 if is_sel and is_mat:
1023 base_polygons.append(p)
1024 base_face_normals.append(p.normal)
1025 else:
1026 base_polygons = me0.polygons
1027 base_face_normals = [p.normal for p in me0.polygons]
1029 # numpy test: slower
1030 #base_face_normals = np.zeros(n_faces0*3)
1031 #me0.polygons.foreach_get("normal", base_face_normals)
1032 #base_face_normals = base_face_normals.reshape((n_faces0,3))
1034 if len(base_polygons) == 0:
1035 return 0
1037 if mode != 'BOUNDS':
1039 bpy.ops.object.select_all(action='DESELECT')
1040 for o in bpy.context.view_layer.objects: o.select_set(False)
1041 bpy.context.view_layer.objects.active = ob1
1042 ob1.select_set(True)
1043 bpy.context.object.active_shape_key_index = 0
1044 # Bound X
1045 if bounds_x != 'EXTEND':
1046 if mode == 'GLOBAL':
1047 planes_co = ((0,0,0),(1,1,1))
1048 plane_no = (1,0,0)
1049 if mode == 'LOCAL':
1050 planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0)))
1051 plane_no = planes_co[0]-planes_co[1]
1052 bpy.ops.object.mode_set(mode='EDIT')
1053 for co in planes_co:
1054 bpy.ops.mesh.select_all(action='SELECT')
1055 bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
1056 bpy.ops.mesh.mark_seam()
1057 bpy.ops.object.mode_set(mode='OBJECT')
1058 _faces = ob1.data.polygons
1059 if mode == 'GLOBAL':
1060 for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]:
1061 f.select = True
1062 for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]:
1063 f.select = True
1064 else:
1065 for f in [f for f in _faces if f.center.x > 1]:
1066 f.select = True
1067 for f in [f for f in _faces if f.center.x < 0]:
1068 f.select = True
1069 bpy.ops.object.mode_set(mode='EDIT')
1070 bpy.ops.mesh.select_mode(type='FACE')
1071 if bounds_x == 'CLIP':
1072 bpy.ops.mesh.delete(type='FACE')
1073 bpy.ops.object.mode_set(mode='OBJECT')
1074 if bounds_x == 'CYCLIC':
1075 bpy.ops.mesh.split()
1076 bpy.ops.object.mode_set(mode='OBJECT')
1077 # Bound Y
1078 if bounds_y != 'EXTEND':
1079 if mode == 'GLOBAL':
1080 planes_co = ((0,0,0),(1,1,1))
1081 plane_no = (0,1,0)
1082 if mode == 'LOCAL':
1083 planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0)))
1084 plane_no = planes_co[0]-planes_co[1]
1085 bpy.ops.object.mode_set(mode='EDIT')
1086 for co in planes_co:
1087 bpy.ops.mesh.select_all(action='SELECT')
1088 bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
1089 bpy.ops.mesh.mark_seam()
1090 bpy.ops.object.mode_set(mode='OBJECT')
1091 _faces = ob1.data.polygons
1092 if mode == 'GLOBAL':
1093 for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]:
1094 f.select = True
1095 for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]:
1096 f.select = True
1097 else:
1098 for f in [f for f in _faces if f.center.y > 1]:
1099 f.select = True
1100 for f in [f for f in _faces if f.center.y < 0]:
1101 f.select = True
1103 bpy.ops.object.mode_set(mode='EDIT')
1104 bpy.ops.mesh.select_mode(type='FACE')
1105 if bounds_y == 'CLIP':
1106 bpy.ops.mesh.delete(type='FACE')
1107 bpy.ops.object.mode_set(mode='OBJECT')
1108 if bounds_y == 'CYCLIC':
1109 bpy.ops.mesh.split()
1110 bpy.ops.object.mode_set(mode='OBJECT')
1111 bpy.ops.object.mode_set(mode='OBJECT')
1112 #ob1 = new_ob1
1114 me1 = ob1.data
1116 verts0 = me0.vertices # Collect generator vertices
1118 # Component statistics
1119 n_verts1 = len(me1.vertices)
1120 n_edges1 = len(me1.edges)
1121 n_faces1 = len(me1.polygons)
1123 # Create empty lists
1124 new_verts = []
1125 new_edges = []
1126 new_faces = []
1127 new_verts_np = np.array(())
1129 # Component Coordinates
1130 co1 = [0]*n_verts1*3
1132 if mode == 'GLOBAL':
1133 for v in me1.vertices:
1134 v.co = ob1.matrix_world @ v.co
1135 try:
1136 for sk in me1.shape_keys.key_blocks:
1137 sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
1138 except: pass
1139 if mode != 'BOUNDS':
1140 if bounds_x == 'CYCLIC':
1141 move_verts = []
1142 for f in [f for f in me1.polygons if (f.center).x > 1]:
1143 for v in f.vertices:
1144 if v not in move_verts: move_verts.append(v)
1145 for v in move_verts:
1146 me1.vertices[v].co.x -= 1
1147 try:
1148 _ob1.active_shape_key_index = 0
1149 for sk in me1.shape_keys.key_blocks:
1150 sk.data[v].co.x -= 1
1151 except: pass
1152 move_verts = []
1153 for f in [f for f in me1.polygons if (f.center).x < 0]:
1154 for v in f.vertices:
1155 if v not in move_verts: move_verts.append(v)
1156 for v in move_verts:
1157 me1.vertices[v].co.x += 1
1158 try:
1159 _ob1.active_shape_key_index = 0
1160 for sk in me1.shape_keys.key_blocks:
1161 sk.data[v].co.x += 1
1162 except: pass
1163 if bounds_y == 'CYCLIC':
1164 move_verts = []
1165 for f in [f for f in me1.polygons if (f.center).y > 1]:
1166 for v in f.vertices:
1167 if v not in move_verts: move_verts.append(v)
1168 for v in move_verts:
1169 me1.vertices[v].co.y -= 1
1170 try:
1171 #new_ob1.active_shape_key_index = 0
1172 for sk in me1.shape_keys.key_blocks:
1173 sk.data[v].co.y -= 1
1174 except: pass
1175 move_verts = []
1176 for f in [f for f in me1.polygons if (f.center).y < 0]:
1177 for v in f.vertices:
1178 if v not in move_verts: move_verts.append(v)
1179 for v in move_verts:
1180 me1.vertices[v].co.y += 1
1181 try:
1182 #new_ob1.active_shape_key_index = 0
1183 for sk in me1.shape_keys.key_blocks:
1184 sk.data[v].co.y += 1
1185 except: pass
1188 me1.vertices.foreach_get("co", co1)
1189 co1 = np.array(co1)
1190 vx = co1[0::3].reshape((n_verts1,1))
1191 vy = co1[1::3].reshape((n_verts1,1))
1192 vz = co1[2::3].reshape((n_verts1,1))
1193 min_c = Vector((vx.min(), vy.min(), vz.min())) # Min BB Corner
1194 max_c = Vector((vx.max(), vy.max(), vz.max())) # Max BB Corner
1195 bb = max_c - min_c # Bounding Box
1197 # Component Coordinates
1198 if mode == 'BOUNDS':
1199 vx = (vx - min_c[0]) / bb[0] if bb[0] != 0 else 0.5
1200 vy = (vy - min_c[1]) / bb[1] if bb[1] != 0 else 0.5
1201 vz = ((vz - min_c[2]) + (-0.5 + offset * 0.5) * bb[2]) * zscale
1202 else:
1203 vz *= zscale
1205 # Component polygons
1206 fs1 = [[i for i in p.vertices] for p in me1.polygons]
1207 new_faces = fs1[:]
1209 # Component edges
1210 es1 = np.array([[i for i in e.vertices] for e in me1.edges])
1211 #es1 = [[i for i in e.vertices] for e in me1.edges if e.is_loose]
1212 new_edges = es1[:]
1214 # SHAPE KEYS
1215 if bool_shapekeys:
1216 basis = True #com_modifiers
1217 vx_key = []
1218 vy_key = []
1219 vz_key = []
1220 sk_np = []
1221 for sk in ob1.data.shape_keys.key_blocks:
1222 do_shapekeys = True
1223 # set all keys to 0
1224 for _sk in ob1.data.shape_keys.key_blocks: _sk.value = 0
1225 sk.value = 1
1227 if basis:
1228 basis = False
1229 continue
1231 # Apply component modifiers
1232 if com_modifiers:
1233 sk_ob = convert_object_to_mesh(_ob1)
1234 sk_data = sk_ob.data
1235 source = sk_data.vertices
1236 else:
1237 source = sk.data
1239 shapekeys = []
1240 for v in source:
1241 if mode == 'BOUNDS':
1242 vert = v.co - min_c
1243 vert[0] = vert[0] / bb[0]
1244 vert[1] = vert[1] / bb[1]
1245 vert[2] = (vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
1246 elif mode == 'LOCAL':
1247 vert = v.co.xyz
1248 vert[2] *= zscale
1249 #vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \
1250 # zscale
1251 elif mode == 'GLOBAL':
1252 vert = v.co.xyz
1253 #vert = ob1.matrix_world @ v.co
1254 vert[2] *= zscale
1255 shapekeys.append(vert)
1257 # Component vertices
1258 key1 = np.array([v for v in shapekeys]).reshape(len(shapekeys), 3, 1)
1259 vx_key.append(key1[:, 0])
1260 vy_key.append(key1[:, 1])
1261 vz_key.append(key1[:, 2])
1262 #sk_np.append([])
1264 # All vertex group
1265 if bool_vertex_group:
1266 try:
1267 weight = []
1268 vertex_groups = ob0.vertex_groups
1269 for vg in vertex_groups:
1270 _weight = []
1271 for v in me0.vertices:
1272 try:
1273 _weight.append(vg.weight(v.index))
1274 except:
1275 _weight.append(0)
1276 weight.append(_weight)
1277 except:
1278 bool_vertex_group = False
1280 # Adaptive Z
1281 if scale_mode == 'ADAPTIVE':
1282 if mode == 'BOUNDS': com_area = (bb[0]*bb[1])
1283 else: com_area = 1
1284 if com_area == 0: mult = 1
1285 else: mult = 1/com_area
1286 verts_area = []
1287 bm = bmesh.new()
1288 bm.from_mesh(me0)
1289 bm.verts.ensure_lookup_table()
1290 for v in bm.verts:
1291 area = 0
1292 faces = v.link_faces
1293 for f in faces:
1294 area += f.calc_area()
1295 try:
1296 area/=len(faces)
1297 area*=mult
1298 verts_area.append(sqrt(area))
1299 except:
1300 verts_area.append(1)
1302 # FAN tessellation mode
1303 if fill_mode == 'FAN':
1304 fan_verts = [v.co.to_tuple() for v in me0.vertices]
1305 fan_polygons = []
1306 fan_select = []
1307 fan_material = []
1308 fan_normals = []
1309 # selected_faces = []
1310 for p in base_polygons:
1311 fan_center = Vector((0, 0, 0))
1312 center_area = 0
1313 for v in p.vertices:
1314 fan_center += me0.vertices[v].co
1315 if scale_mode == 'ADAPTIVE':
1316 center_area += verts_area[v]
1317 fan_center /= len(p.vertices)
1318 center_area /= len(p.vertices)
1320 last_vert = len(fan_verts)
1321 fan_verts.append(fan_center.to_tuple())
1322 #fan_verts.append(fan_center)
1323 if scale_mode == 'ADAPTIVE':
1324 verts_area.append(center_area)
1326 # Vertex Group
1327 if bool_vertex_group:
1328 for w in weight:
1329 center_weight = sum([w[i] for i in p.vertices]) / len(p.vertices)
1330 w.append(center_weight)
1332 for i in range(len(p.vertices)):
1333 fan_polygons.append((p.vertices[i],
1334 p.vertices[(i + 1) % len(p.vertices)],
1335 last_vert, last_vert))
1337 if bool_material_id: fan_material.append(p.material_index)
1338 if bool_selection: fan_select.append(p.select)
1339 if normals_mode == 'FACES':
1340 fan_normals.append(p.normal)
1342 fan_me = bpy.data.meshes.new('Fan.Mesh')
1343 fan_me.from_pydata(tuple(fan_verts), [], tuple(fan_polygons))
1344 me0 = fan_me.copy()
1345 bpy.data.meshes.remove(fan_me)
1346 verts0 = me0.vertices
1347 base_polygons = me0.polygons
1348 if normals_mode == 'FACES': base_face_normals = fan_normals
1350 count = 0 # necessary for UV calculation
1352 # TESSELLATION
1353 j = 0
1354 jj = -1
1355 bool_correct = False
1357 # optimization test
1358 n_faces = len(base_polygons)
1359 _vs0 = [0]*n_faces
1360 _nvs0 = [0]*n_faces
1361 _sz = [0]*n_faces
1362 _w0 = [[0]*n_faces]*len(ob0.vertex_groups)
1363 np_faces = [np.array(p) for p in fs1]
1364 new_faces = [0]*n_faces*n_faces1
1365 face1_count = 0
1367 for p in base_polygons:
1369 bool_correct = True
1370 if rotation_mode == 'UV' and ob0.type != 'MESH':
1371 rotation_mode = 'DEFAULT'
1373 # Random rotation
1374 if rotation_mode == 'RANDOM':
1375 shifted_vertices = []
1376 n_poly_verts = len(p.vertices)
1377 rand = random.randint(0, n_poly_verts)
1378 for i in range(n_poly_verts):
1379 shifted_vertices.append(p.vertices[(i + rand) % n_poly_verts])
1380 if scale_mode == 'ADAPTIVE':
1381 verts_area0 = np.array([verts_area[i] for i in shifted_vertices])
1382 vs0 = np.array([verts0[i].co for i in shifted_vertices])
1383 nvs0 = np.array([verts0[i].normal for i in shifted_vertices])
1384 if normals_mode == 'VERTS':
1385 nvs0 = np.array([verts0[i].normal for i in shifted_vertices])
1386 # vertex weight
1387 if bool_vertex_group:
1388 ws0 = []
1389 for w in weight:
1390 _ws0 = []
1391 for i in shifted_vertices:
1392 try:
1393 _ws0.append(w[i])
1394 except:
1395 _ws0.append(0)
1396 ws0.append(np.array(_ws0))
1398 # UV rotation
1399 elif rotation_mode == 'UV':
1400 if len(ob0.data.uv_layers) > 0 and fill_mode != 'FAN':
1401 i = p.index
1402 if bool_material_id:
1403 count = sum([len(p.vertices) for p in me0.polygons[:i]])
1404 #if i == 0: count = 0
1405 v01 = (me0.uv_layers.active.data[count].uv +
1406 me0.uv_layers.active.data[count + 1].uv)
1407 if len(p.vertices) > 3:
1408 v32 = (me0.uv_layers.active.data[count + 3].uv +
1409 me0.uv_layers.active.data[count + 2].uv)
1410 else:
1411 v32 = (me0.uv_layers.active.data[count].uv +
1412 me0.uv_layers.active.data[count + 2].uv)
1413 v0132 = v32 - v01
1414 v0132.normalize()
1416 v12 = (me0.uv_layers.active.data[count + 1].uv +
1417 me0.uv_layers.active.data[count + 2].uv)
1418 if len(p.vertices) > 3:
1419 v03 = (me0.uv_layers.active.data[count].uv +
1420 me0.uv_layers.active.data[count + 3].uv)
1421 else:
1422 v03 = (me0.uv_layers.active.data[count].uv +
1423 me0.uv_layers.active.data[count].uv)
1424 v1203 = v03 - v12
1425 v1203.normalize()
1427 vertUV = []
1428 dot1203 = v1203.x
1429 dot0132 = v0132.x
1430 if(abs(dot1203) < abs(dot0132)):
1431 if (dot0132 > 0):
1432 vertUV = p.vertices[1:] + p.vertices[:1]
1433 else:
1434 vertUV = p.vertices[3:] + p.vertices[:3]
1435 else:
1436 if(dot1203 < 0):
1437 vertUV = p.vertices[:]
1438 else:
1439 vertUV = p.vertices[2:] + p.vertices[:2]
1440 vs0 = np.array([verts0[i].co for i in vertUV])
1441 nvs0 = np.array([verts0[i].normal for i in vertUV])
1443 # Vertex weight
1444 if bool_vertex_group:
1445 ws0 = []
1446 for w in weight:
1447 _ws0 = []
1448 for i in vertUV:
1449 try:
1450 _ws0.append(w[i])
1451 except:
1452 _ws0.append(0)
1453 ws0.append(np.array(_ws0))
1455 count += len(p.vertices)
1456 else: rotation_mode = 'DEFAULT'
1458 # Default rotation
1459 if rotation_mode == 'DEFAULT':
1460 vs0 = np.array([verts0[i].co for i in p.vertices])
1461 nvs0 = np.array([verts0[i].normal for i in p.vertices])
1462 # Vertex weight
1463 if bool_vertex_group:
1464 ws0 = []
1465 for w in weight:
1466 _ws0 = []
1467 for i in p.vertices:
1468 try:
1469 _ws0.append(w[i])
1470 except:
1471 _ws0.append(0)
1472 ws0.append(np.array(_ws0))
1474 # optimization test
1475 _vs0[j] = (vs0[0], vs0[1], vs0[2], vs0[-1])
1476 if normals_mode == 'VERTS':
1477 _nvs0[j] = (nvs0[0], nvs0[1], nvs0[2], nvs0[-1])
1478 #else:
1479 # _nvs0[j] = base_face_normals[j]
1482 # vertex z to normal
1483 if scale_mode == 'ADAPTIVE':
1484 poly_faces = (p.vertices[0], p.vertices[1], p.vertices[2], p.vertices[-1])
1485 if rotation_mode == 'RANDOM': sz = verts_area0
1486 else: sz = np.array([verts_area[i] for i in poly_faces])
1488 _sz[j] = sz
1490 if bool_vertex_group:
1491 vg_count = 0
1492 for _ws0 in ws0:
1493 _w0[vg_count][j] = (_ws0[0], _ws0[1], _ws0[2], _ws0[-1])
1494 vg_count += 1
1496 for p in fs1:
1497 new_faces[face1_count] = [i + n_verts1 * j for i in p]
1498 face1_count += 1
1500 j += 1
1502 # build edges list
1503 n_edges1 = new_edges.shape[0]
1504 new_edges = new_edges.reshape((1, n_edges1, 2))
1505 new_edges = new_edges.repeat(n_faces,axis=0)
1506 new_edges = new_edges.reshape((n_edges1*n_faces, 2))
1507 increment = np.arange(n_faces)*n_verts1
1508 increment = increment.repeat(n_edges1, axis=0)
1509 increment = increment.reshape((n_faces*n_edges1,1))
1510 new_edges = new_edges + increment
1512 # optimization test
1513 _vs0 = np.array(_vs0)
1514 _sz = np.array(_sz)
1516 _vs0_0 = _vs0[:,0].reshape((n_faces,1,3))
1517 _vs0_1 = _vs0[:,1].reshape((n_faces,1,3))
1518 _vs0_2 = _vs0[:,2].reshape((n_faces,1,3))
1519 _vs0_3 = _vs0[:,3].reshape((n_faces,1,3))
1521 # remapped vertex coordinates
1522 v0 = _vs0_0 + (_vs0_1 - _vs0_0) * vx
1523 v1 = _vs0_3 + (_vs0_2 - _vs0_3) * vx
1524 v2 = v0 + (v1 - v0) * vy
1526 # remapped vertex normal
1527 if normals_mode == 'VERTS':
1528 _nvs0 = np.array(_nvs0)
1529 _nvs0_0 = _nvs0[:,0].reshape((n_faces,1,3))
1530 _nvs0_1 = _nvs0[:,1].reshape((n_faces,1,3))
1531 _nvs0_2 = _nvs0[:,2].reshape((n_faces,1,3))
1532 _nvs0_3 = _nvs0[:,3].reshape((n_faces,1,3))
1533 nv0 = _nvs0_0 + (_nvs0_1 - _nvs0_0) * vx
1534 nv1 = _nvs0_3 + (_nvs0_2 - _nvs0_3) * vx
1535 nv2 = nv0 + (nv1 - nv0) * vy
1536 else:
1537 nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
1539 if bool_vertex_group:
1540 n_vg = len(_w0)
1541 w = np.array(_w0)
1542 #for w in _w0:
1543 #w = np.array(w)
1544 w_0 = w[:,:,0].reshape((n_vg, n_faces,1,1))
1545 w_1 = w[:,:,1].reshape((n_vg, n_faces,1,1))
1546 w_2 = w[:,:,2].reshape((n_vg, n_faces,1,1))
1547 w_3 = w[:,:,3].reshape((n_vg, n_faces,1,1))
1548 # remapped weight
1549 w0 = w_0 + (w_1 - w_0) * vx
1550 w1 = w_3 + (w_2 - w_3) * vx
1551 w = w0 + (w1 - w0) * vy
1552 w = w.reshape((n_vg, n_faces*n_verts1))
1553 #w = w2.tolist()
1555 if scale_mode == 'ADAPTIVE':
1556 _sz_0 = _sz[:,0].reshape((n_faces,1,1))
1557 _sz_1 = _sz[:,1].reshape((n_faces,1,1))
1558 _sz_2 = _sz[:,2].reshape((n_faces,1,1))
1559 _sz_3 = _sz[:,3].reshape((n_faces,1,1))
1560 # remapped z scale
1561 sz0 = _sz_0 + (_sz_1 - _sz_0) * vx
1562 sz1 = _sz_3 + (_sz_2 - _sz_3) * vx
1563 sz2 = sz0 + (sz1 - sz0) * vy
1564 v3 = v2 + nv2 * vz * sz2
1565 else:
1566 v3 = v2 + nv2 * vz
1568 new_verts_np = v3.reshape((n_faces*n_verts1,3))
1570 if bool_shapekeys:
1571 n_sk = len(vx_key)
1572 sk_np = [0]*n_sk
1573 for i in range(n_sk):
1574 vx = np.array(vx_key)
1575 vy = np.array(vy_key)
1576 vz = np.array(vz_key)
1578 # remapped vertex coordinates
1579 v0 = _vs0_0 + (_vs0_1 - _vs0_0) * vx
1580 v1 = _vs0_3 + (_vs0_2 - _vs0_3) * vx
1581 v2 = v0 + (v1 - v0) * vy
1583 # remapped vertex normal
1584 if normals_mode == 'VERTS':
1585 nv0 = _nvs0_0 + (_nvs0_1 - _nvs0_0) * vx
1586 nv1 = _nvs0_3 + (_nvs0_2 - _nvs0_3) * vx
1587 nv2 = nv0 + (nv1 - nv0) * vy
1588 else:
1589 nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
1591 if scale_mode == 'ADAPTIVE':
1592 # remapped z scale
1593 sz0 = _sz_0 + (_sz_1 - _sz_0) * vx
1594 sz1 = _sz_3 + (_sz_2 - _sz_3) * vx
1595 sz2 = sz0 + (sz1 - sz0) * vy
1596 v3 = v2 + nv2 * vz * sz2
1597 else:
1598 v3 = v2 + nv2 * vz
1600 sk_np[i] = v3.reshape((n_faces*n_verts1,3))
1602 #if ob0.type == 'MESH': ob0.data = old_me0
1604 if not bool_correct: return 0
1606 new_verts = new_verts_np.tolist()
1607 new_name = ob0.name + "_" + ob1.name
1608 new_me = bpy.data.meshes.new(new_name)
1609 new_me.from_pydata(new_verts, new_edges.tolist(), new_faces)
1610 new_me.update(calc_edges=True)
1611 new_ob = bpy.data.objects.new("tessellate_temp", new_me)
1613 # vertex group
1614 if bool_vertex_group and False:
1615 for vg in ob0.vertex_groups:
1616 new_ob.vertex_groups.new(name=vg.name)
1617 for i in range(len(vg_np[vg.index])):
1618 new_ob.vertex_groups[vg.name].add([i], vg_np[vg.index][i],"ADD")
1619 # vertex group
1620 if bool_vertex_group:
1621 for vg in ob0.vertex_groups:
1622 new_ob.vertex_groups.new(name=vg.name)
1623 for i in range(len(w[vg.index])):
1624 new_ob.vertex_groups[vg.name].add([i], w[vg.index,i],"ADD")
1626 if bool_shapekeys:
1627 basis = com_modifiers
1628 sk_count = 0
1629 for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
1630 sk.value = val
1631 new_ob.shape_key_add(name=sk.name)
1632 new_ob.data.shape_keys.key_blocks[sk.name].value = val
1633 # set shape keys vertices
1634 sk_data = new_ob.data.shape_keys.key_blocks[sk.name].data
1635 if sk_count == 0:
1636 sk_count += 1
1637 continue
1638 for id in range(len(sk_data)):
1639 sk_data[id].co = sk_np[sk_count-1][id]
1640 sk_count += 1
1641 if bool_vertex_group:
1642 for sk in new_ob.data.shape_keys.key_blocks:
1643 for vg in new_ob.vertex_groups:
1644 if sk.name == vg.name:
1645 sk.vertex_group = vg.name
1647 # EDGES SEAMS
1648 edge_data = [0]*n_edges1
1649 me1.edges.foreach_get("use_seam",edge_data)
1650 if any(edge_data):
1651 edge_data = edge_data*n_faces
1652 new_ob.data.edges.foreach_set("use_seam",edge_data)
1654 # EDGES SHARP
1655 edge_data = [0]*n_edges1
1656 me1.edges.foreach_get("use_edge_sharp",edge_data)
1657 if any(edge_data):
1658 edge_data = edge_data*n_faces
1659 new_ob.data.edges.foreach_set("use_edge_sharp",edge_data)
1661 bpy.ops.object.select_all(action='DESELECT')
1662 bpy.context.collection.objects.link(new_ob)
1663 new_ob.select_set(True)
1664 bpy.context.view_layer.objects.active = new_ob
1666 # EDGES BEVEL
1667 edge_data = [0]*n_edges1
1668 me1.edges.foreach_get("bevel_weight",edge_data)
1669 if any(edge_data):
1670 bpy.ops.object.mode_set(mode='EDIT')
1671 bpy.ops.mesh.select_all(action='SELECT')
1672 bpy.ops.transform.edge_bevelweight(value=1)
1673 bpy.ops.object.mode_set(mode='OBJECT')
1674 edge_data = edge_data*n_faces
1675 new_ob.data.edges.foreach_set("bevel_weight",edge_data)
1677 # EDGE CREASES
1678 edge_data = [0]*n_edges1
1679 me1.edges.foreach_get("crease",edge_data)
1680 if any(edge_data):
1681 bpy.ops.object.mode_set(mode='EDIT')
1682 bpy.ops.mesh.select_all(action='SELECT')
1683 bpy.ops.transform.edge_crease(value=1)
1684 bpy.ops.object.mode_set(mode='OBJECT')
1685 edge_data = edge_data*n_faces
1686 new_ob.data.edges.foreach_set('crease', edge_data)
1688 # MATERIALS
1689 for slot in ob1.material_slots: new_ob.data.materials.append(slot.material)
1692 polygon_materials = [0]*n_faces1
1693 me1.polygons.foreach_get("material_index", polygon_materials)
1694 polygon_materials *= n_faces
1695 new_ob.data.polygons.foreach_set("material_index", polygon_materials)
1696 new_ob.data.update() ###
1698 try:
1699 bpy.data.objects.remove(new_ob1)
1700 except: pass
1702 bpy.data.objects.remove(ob0)
1703 bpy.data.meshes.remove(me0)
1704 bpy.data.objects.remove(ob1)
1705 bpy.data.meshes.remove(me1)
1706 return new_ob
1709 class tessellate(Operator):
1710 bl_idname = "object.tessellate"
1711 bl_label = "Tessellate"
1712 bl_description = ("Create a copy of selected object on the active object's "
1713 "faces, adapting the shape to the different faces")
1714 bl_options = {'REGISTER', 'UNDO'}
1717 object_name : StringProperty(
1718 name="",
1719 description="Name of the generated object"
1721 zscale : FloatProperty(
1722 name="Scale",
1723 default=1,
1724 soft_min=0,
1725 soft_max=10,
1726 description="Scale factor for the component thickness"
1728 scale_mode : EnumProperty(
1729 items=(
1730 ('CONSTANT', "Constant", "Uniform thickness"),
1731 ('ADAPTIVE', "Proportional", "Preserve component's proportions")
1733 default='ADAPTIVE',
1734 name="Z-Scale according to faces size"
1736 offset : FloatProperty(
1737 name="Surface Offset",
1738 default=1,
1739 min=-1, max=1,
1740 soft_min=-1,
1741 soft_max=1,
1742 description="Surface offset"
1744 mode : EnumProperty(
1745 items=(
1746 ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
1747 ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
1748 ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
1749 default='BOUNDS',
1750 name="Component Mode"
1752 rotation_mode : EnumProperty(
1753 items=(('RANDOM', "Random", "Random faces rotation"),
1754 ('UV', "Active UV", "Face rotation is based on UV coordinates"),
1755 ('DEFAULT', "Default", "Default rotation")),
1756 default='DEFAULT',
1757 name="Component Rotation"
1759 fill_mode : EnumProperty(
1760 items=(
1761 ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
1762 ('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
1763 ('PATCH', 'Patch', 'Curved tessellation according to the last ' +
1764 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
1765 'patches.\nAfter the last Subsurf (or Multires) only ' +
1766 'deformation\nmodifiers can be used')),
1767 default='QUAD',
1768 name="Fill Mode"
1770 combine_mode : EnumProperty(
1771 items=(
1772 ('LAST', 'Last', 'Show only the last iteration'),
1773 ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
1774 ('ALL', 'All', 'Combine the result of all iterations')),
1775 default='LAST',
1776 name="Combine Mode",
1778 gen_modifiers : BoolProperty(
1779 name="Generator Modifiers",
1780 default=False,
1781 description="Apply Modifiers and Shape Keys to the base object"
1783 com_modifiers : BoolProperty(
1784 name="Component Modifiers",
1785 default=False,
1786 description="Apply Modifiers and Shape Keys to the component object"
1788 merge : BoolProperty(
1789 name="Merge",
1790 default=False,
1791 description="Merge vertices in adjacent duplicates"
1793 merge_thres : FloatProperty(
1794 name="Distance",
1795 default=0.001,
1796 soft_min=0,
1797 soft_max=10,
1798 description="Limit below which to merge vertices"
1800 bool_random : BoolProperty(
1801 name="Randomize",
1802 default=False,
1803 description="Randomize component rotation"
1805 random_seed : IntProperty(
1806 name="Seed",
1807 default=0,
1808 soft_min=0,
1809 soft_max=10,
1810 description="Random seed"
1812 bool_vertex_group : BoolProperty(
1813 name="Map Vertex Groups",
1814 default=False,
1815 description="Transfer all Vertex Groups from Base object"
1817 bool_selection : BoolProperty(
1818 name="On selected Faces",
1819 default=False,
1820 description="Create Tessellation only on selected faces"
1822 bool_shapekeys : BoolProperty(
1823 name="Use Shape Keys",
1824 default=False,
1825 description="Transfer Component's Shape Keys. If the name of Vertex "
1826 "Groups and Shape Keys are the same, they will be "
1827 "automatically combined"
1829 bool_smooth : BoolProperty(
1830 name="Smooth Shading",
1831 default=False,
1832 description="Output faces with smooth shading rather than flat shaded"
1834 bool_materials : BoolProperty(
1835 name="Transfer Materials",
1836 default=True,
1837 description="Preserve component's materials"
1839 generator : StringProperty(
1840 name="",
1841 description="Base object for the tessellation",
1842 default = ""
1844 component : StringProperty(
1845 name="",
1846 description="Component object for the tessellation",
1847 default = ""
1849 bool_material_id : BoolProperty(
1850 name="Tessellation on Material ID",
1851 default=False,
1852 description="Apply the component only on the selected Material"
1854 bool_dissolve_seams : BoolProperty(
1855 name="Dissolve Seams",
1856 default=False,
1857 description="Dissolve all seam edges"
1859 material_id : IntProperty(
1860 name="Material ID",
1861 default=0,
1862 min=0,
1863 description="Material ID"
1865 iterations : IntProperty(
1866 name="Iterations",
1867 default=1,
1868 min=1,
1869 soft_max=5,
1870 description="Automatically repeat the Tessellation using the "
1871 + "generated geometry as new base object.\nUsefull for "
1872 + "for branching systems. Dangerous!"
1874 bool_combine : BoolProperty(
1875 name="Combine unused",
1876 default=False,
1877 description="Combine the generated geometry with unused faces"
1879 bool_advanced : BoolProperty(
1880 name="Advanced Settings",
1881 default=False,
1882 description="Show more settings"
1884 normals_mode : EnumProperty(
1885 items=(
1886 ('VERTS', 'Along Normals', 'Consistent direction based on vertices normal'),
1887 ('FACES', 'Individual Faces', 'Based on individual faces normal')),
1888 default='VERTS',
1889 name="Direction"
1891 bool_multi_components : BoolProperty(
1892 name="Multi Components",
1893 default=False,
1894 description="Combine different components according to materials name"
1896 bounds_x : EnumProperty(
1897 items=(
1898 ('EXTEND', 'Extend', 'Default X coordinates'),
1899 ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
1900 ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
1901 default='EXTEND',
1902 name="Bounds X",
1904 bounds_y : EnumProperty(
1905 items=(
1906 ('EXTEND', 'Extend', 'Default Y coordinates'),
1907 ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
1908 ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
1909 default='EXTEND',
1910 name="Bounds Y",
1912 cap_faces : BoolProperty(
1913 name="Cap Holes",
1914 default=False,
1915 description="Cap open edges loops"
1917 open_edges_crease : FloatProperty(
1918 name="Open Edges Crease",
1919 default=0,
1920 min=0,
1921 max=1,
1922 description="Automatically set crease for open edges"
1924 working_on : ""
1926 def draw(self, context):
1927 allowed_obj = ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
1929 try:
1930 bool_working = self.working_on == self.object_name and \
1931 self.working_on != ""
1932 except:
1933 bool_working = False
1936 bool_working = False
1937 bool_allowed = False
1938 ob0 = None
1939 ob1 = None
1941 sel = bpy.context.selected_objects
1942 if len(sel) == 1:
1943 try:
1944 ob0 = sel[0].tissue_tessellate.generator
1945 ob1 = sel[0].tissue_tessellate.component
1946 self.generator = ob0.name
1947 self.component = ob1.name
1948 bool_working = True
1949 bool_allowed = True
1950 except:
1951 pass
1953 if len(sel) == 2:
1954 bool_allowed = True
1955 for o in sel:
1956 if o.type not in allowed_obj:
1957 bool_allowed = False
1959 if len(sel) != 2 and not bool_working:
1960 layout = self.layout
1961 layout.label(icon='INFO')
1962 layout.label(text="Please, select two different objects")
1963 layout.label(text="Select first the Component object, then select")
1964 layout.label(text="the Base object.")
1965 elif not bool_allowed and not bool_working:
1966 layout = self.layout
1967 layout.label(icon='INFO')
1968 layout.label(text="Only Mesh, Curve, Surface or Text objects are allowed")
1969 else:
1970 if ob0 == ob1 == None:
1971 ob0 = bpy.context.active_object
1972 self.generator = ob0.name
1973 for o in sel:
1974 if o != ob0:
1975 ob1 = o
1976 self.component = o.name
1977 self.no_component = False
1978 break
1980 # new object name
1981 if self.object_name == "":
1982 if self.generator == "":
1983 self.object_name = "Tessellation"
1984 else:
1985 #self.object_name = self.generator + "_Tessellation"
1986 self.object_name = "Tessellation"
1988 layout = self.layout
1989 # Base and Component
1990 col = layout.column(align=True)
1991 row = col.row(align=True)
1992 row.label(text="BASE : " + self.generator)
1993 row.label(text="COMPONENT : " + self.component)
1995 # Base Modifiers
1996 row = col.row(align=True)
1997 col2 = row.column(align=True)
1998 col2.prop(self, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
1999 base = bpy.data.objects[self.generator]
2000 try:
2001 if not (base.modifiers or base.data.shape_keys):
2002 col2.enabled = False
2003 self.gen_modifiers = False
2004 except:
2005 col2.enabled = False
2006 self.gen_modifiers = False
2008 # Component Modifiers
2009 row.separator()
2010 col3 = row.column(align=True)
2011 col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
2012 component = bpy.data.objects[self.component]
2013 try:
2014 if not (component.modifiers or component.data.shape_keys):
2015 col3.enabled = False
2016 self.com_modifiers = False
2017 except:
2018 col3.enabled = False
2019 self.com_modifiers = False
2020 col.separator()
2021 # Fill and Rotation
2022 row = col.row(align=True)
2023 row.label(text="Fill Mode:")
2024 row.label(text="Rotation:")
2025 row = col.row(align=True)
2026 #col2 = row.column(align=True)
2027 row.prop(
2028 self, "fill_mode", text="", icon='NONE', expand=False,
2029 slider=True, toggle=False, icon_only=False, event=False,
2030 full_event=False, emboss=True, index=-1)
2032 # Rotation
2033 row.separator()
2034 col2 = row.column(align=True)
2035 col2.prop(
2036 self, "rotation_mode", text="", icon='NONE', expand=False,
2037 slider=True, toggle=False, icon_only=False, event=False,
2038 full_event=False, emboss=True, index=-1)
2039 if self.rotation_mode == 'RANDOM':
2040 col2.prop(self, "random_seed")
2042 if self.rotation_mode == 'UV':
2043 uv_error = False
2044 if self.fill_mode == 'FAN':
2045 row = col.row(align=True)
2046 row.label(text="UV rotation doesn't work in FAN mode",
2047 icon='ERROR')
2048 uv_error = True
2050 if ob0.type != 'MESH':
2051 row = col.row(align=True)
2052 row.label(
2053 text="UV rotation supported only for Mesh objects",
2054 icon='ERROR')
2055 uv_error = True
2056 else:
2057 if len(ob0.data.uv_layers) == 0:
2058 row = col.row(align=True)
2059 check_name = self.generator
2060 row.label(text="'" + check_name +
2061 "' doesn't have UV Maps", icon='ERROR')
2062 uv_error = True
2063 if uv_error:
2064 row = col.row(align=True)
2065 row.label(text="Default rotation will be used instead",
2066 icon='INFO')
2068 # Component XY
2069 row = col.row(align=True)
2070 row.label(text="Component Coordinates:")
2071 row = col.row(align=True)
2072 row.prop(
2073 self, "mode", text="Component XY", icon='NONE', expand=True,
2074 slider=False, toggle=False, icon_only=False, event=False,
2075 full_event=False, emboss=True, index=-1)
2077 if self.mode != 'BOUNDS':
2078 col.separator()
2079 row = col.row(align=True)
2080 row.label(text="X:")
2081 row.prop(
2082 self, "bounds_x", text="Bounds X", icon='NONE', expand=True,
2083 slider=False, toggle=False, icon_only=False, event=False,
2084 full_event=False, emboss=True, index=-1)
2086 row = col.row(align=True)
2087 row.label(text="Y:")
2088 row.prop(
2089 self, "bounds_y", text="Bounds X", icon='NONE', expand=True,
2090 slider=False, toggle=False, icon_only=False, event=False,
2091 full_event=False, emboss=True, index=-1)
2093 # Component Z
2094 col.label(text="Thickness:")
2095 row = col.row(align=True)
2096 row.prop(
2097 self, "scale_mode", text="Scale Mode", icon='NONE', expand=True,
2098 slider=False, toggle=False, icon_only=False, event=False,
2099 full_event=False, emboss=True, index=-1)
2100 col.prop(
2101 self, "zscale", text="Scale", icon='NONE', expand=False,
2102 slider=True, toggle=False, icon_only=False, event=False,
2103 full_event=False, emboss=True, index=-1)
2104 if self.mode == 'BOUNDS':
2105 col.prop(
2106 self, "offset", text="Offset", icon='NONE', expand=False,
2107 slider=True, toggle=False, icon_only=False, event=False,
2108 full_event=False, emboss=True, index=-1)
2110 # Direction
2111 row = col.row(align=True)
2112 row.label(text="Direction:")
2113 row = col.row(align=True)
2114 row.prop(
2115 self, "normals_mode", text="Direction", icon='NONE', expand=True,
2116 slider=False, toggle=False, icon_only=False, event=False,
2117 full_event=False, emboss=True, index=-1)
2118 row.enabled = self.fill_mode != 'PATCH'
2120 # Merge
2121 col = layout.column(align=True)
2122 row = col.row(align=True)
2123 row.prop(self, "merge")
2124 if self.merge:
2125 row.prop(self, "merge_thres")
2126 row = col.row(align=True)
2128 row = col.row(align=True)
2129 row.prop(self, "bool_smooth")
2130 if self.merge:
2131 col2 = row.column(align=True)
2132 col2.prop(self, "bool_dissolve_seams")
2133 #if ob1.type != 'MESH': col2.enabled = False
2135 row = col.row(align=True)
2136 row.prop(self, "cap_faces")
2137 if self.cap_faces:
2138 col2 = row.column(align=True)
2139 col2.prop(self, "open_edges_crease", text="Crease")
2141 # Advanced Settings
2142 col = layout.column(align=True)
2143 col.separator()
2144 col.separator()
2145 row = col.row(align=True)
2146 row.prop(self, "bool_advanced", icon='SETTINGS')
2147 if self.bool_advanced:
2148 allow_multi = False
2149 allow_shapekeys = not self.com_modifiers
2150 for m in ob0.data.materials:
2151 try:
2152 o = bpy.data.objects[m.name]
2153 allow_multi = True
2154 try:
2155 if o.data.shape_keys is None: continue
2156 elif len(o.data.shape_keys.key_blocks) < 2: continue
2157 else: allow_shapekeys = not self.com_modifiers
2158 except: pass
2159 except: pass
2160 # DATA #
2161 col = layout.column(align=True)
2162 col.label(text="Morphing:")
2163 # vertex group + shape keys
2164 row = col.row(align=True)
2165 col2 = row.column(align=True)
2166 col2.prop(self, "bool_vertex_group", icon='GROUP_VERTEX')
2167 #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
2168 try:
2169 if len(ob0.vertex_groups) == 0:
2170 col2.enabled = False
2171 except:
2172 col2.enabled = False
2173 row.separator()
2174 col2 = row.column(align=True)
2175 row2 = col2.row(align=True)
2176 row2.prop(self, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
2177 row2.enabled = allow_shapekeys
2179 # LIMITED TESSELLATION
2180 col = layout.column(align=True)
2181 col.label(text="Limited Tessellation:")
2182 row = col.row(align=True)
2183 col2 = row.column(align=True)
2184 col2.prop(self, "bool_multi_components", icon='MOD_TINT')
2185 if not allow_multi:
2186 col2.enabled = False
2187 self.bool_multi_components = False
2188 col.separator()
2189 row = col.row(align=True)
2190 col2 = row.column(align=True)
2191 col2.prop(self, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
2192 #if self.bool_material_id or self.bool_selection or self.bool_multi_components:
2193 #col2 = row.column(align=True)
2194 # col2.prop(self, "bool_combine")
2195 row.separator()
2196 if ob0.type != 'MESH':
2197 col2.enabled = False
2198 col2 = row.column(align=True)
2199 col2.prop(self, "bool_material_id", icon='MATERIAL_DATA', text="Material ID")
2200 if self.bool_material_id and not self.bool_multi_components:
2201 #col2 = row.column(align=True)
2202 col2.prop(self, "material_id")
2203 col2.enabled = not self.bool_multi_components
2205 col.separator()
2206 row = col.row(align=True)
2207 row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
2208 row.prop(self, 'iterations', text='Repeat', icon='SETTINGS')
2210 col.separator()
2211 row = col.row(align=True)
2212 row.label(text='Combine Iterations:')
2213 row = col.row(align=True)
2214 row.prop(
2215 self, "combine_mode", icon='NONE', expand=True,
2216 slider=False, toggle=False, icon_only=False, event=False,
2217 full_event=False, emboss=True, index=-1)
2219 def execute(self, context):
2220 allowed_obj = ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT')
2221 try:
2222 ob0 = bpy.data.objects[self.generator]
2223 ob1 = bpy.data.objects[self.component]
2224 except:
2225 return {'CANCELLED'}
2227 self.object_name = "Tessellation"
2228 # Check if existing object with same name
2229 names = [o.name for o in bpy.data.objects]
2230 if self.object_name in names:
2231 count_name = 1
2232 while True:
2233 test_name = self.object_name + '.{:03d}'.format(count_name)
2234 if not (test_name in names):
2235 self.object_name = test_name
2236 break
2237 count_name += 1
2239 if ob1.type not in allowed_obj:
2240 message = "Component must be Mesh, Curve, Surface, Text or Meta object!"
2241 self.report({'ERROR'}, message)
2242 self.component = None
2244 if ob0.type not in allowed_obj:
2245 message = "Generator must be Mesh, Curve, Surface, Text or Meta object!"
2246 self.report({'ERROR'}, message)
2247 self.generator = ""
2249 if True:#self.component not in ("",None) and self.generator not in ("",None):
2250 if bpy.ops.object.select_all.poll():
2251 bpy.ops.object.select_all(action='TOGGLE')
2252 bpy.ops.object.mode_set(mode='OBJECT')
2254 #data0 = ob0.to_mesh(False)
2255 #data0 = ob0.data.copy()
2256 bool_update = False
2257 if bpy.context.object == ob0:
2258 auto_layer_collection()
2259 #new_ob = bpy.data.objects.new(self.object_name, data0)
2260 new_ob = convert_object_to_mesh(ob0,False,False)
2261 new_ob.data.name = self.object_name
2262 #bpy.context.collection.objects.link(new_ob)
2263 #bpy.context.view_layer.objects.active = new_ob
2264 new_ob.name = self.object_name
2265 #new_ob.select_set(True)
2266 else:
2267 new_ob = bpy.context.object
2268 bool_update = True
2269 new_ob = store_parameters(self, new_ob)
2270 try: bpy.ops.object.update_tessellate()
2271 except RuntimeError as e:
2272 bpy.data.objects.remove(new_ob)
2273 self.report({'ERROR'}, str(e))
2274 return {'CANCELLED'}
2275 if not bool_update:
2276 self.object_name = new_ob.name
2277 #self.working_on = self.object_name
2278 new_ob.location = ob0.location
2279 new_ob.matrix_world = ob0.matrix_world
2281 return {'FINISHED'}
2283 def invoke(self, context, event):
2284 return context.window_manager.invoke_props_dialog(self)
2287 class update_tessellate(Operator):
2288 bl_idname = "object.update_tessellate"
2289 bl_label = "Refresh"
2290 bl_description = ("Fast update the tessellated mesh according to base and "
2291 "component changes")
2292 bl_options = {'REGISTER', 'UNDO'}
2294 go = False
2296 @classmethod
2297 def poll(cls, context):
2298 #try:
2299 try: #context.object == None: return False
2300 return context.object.tissue_tessellate.generator != None and \
2301 context.object.tissue_tessellate.component != None
2302 except:
2303 return False
2305 @staticmethod
2306 def check_gen_comp(checking):
2307 # note pass the stored name key in here to check it out
2308 return checking in bpy.data.objects.keys()
2310 def execute(self, context):
2311 start_time = time.time()
2313 ob = bpy.context.object
2314 if not self.go:
2315 generator = ob.tissue_tessellate.generator
2316 component = ob.tissue_tessellate.component
2317 zscale = ob.tissue_tessellate.zscale
2318 scale_mode = ob.tissue_tessellate.scale_mode
2319 rotation_mode = ob.tissue_tessellate.rotation_mode
2320 offset = ob.tissue_tessellate.offset
2321 merge = ob.tissue_tessellate.merge
2322 merge_thres = ob.tissue_tessellate.merge_thres
2323 gen_modifiers = ob.tissue_tessellate.gen_modifiers
2324 com_modifiers = ob.tissue_tessellate.com_modifiers
2325 bool_random = ob.tissue_tessellate.bool_random
2326 random_seed = ob.tissue_tessellate.random_seed
2327 fill_mode = ob.tissue_tessellate.fill_mode
2328 bool_vertex_group = ob.tissue_tessellate.bool_vertex_group
2329 bool_selection = ob.tissue_tessellate.bool_selection
2330 bool_shapekeys = ob.tissue_tessellate.bool_shapekeys
2331 mode = ob.tissue_tessellate.mode
2332 bool_smooth = ob.tissue_tessellate.bool_smooth
2333 bool_materials = ob.tissue_tessellate.bool_materials
2334 bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams
2335 bool_material_id = ob.tissue_tessellate.bool_material_id
2336 material_id = ob.tissue_tessellate.material_id
2337 iterations = ob.tissue_tessellate.iterations
2338 bool_combine = ob.tissue_tessellate.bool_combine
2339 normals_mode = ob.tissue_tessellate.normals_mode
2340 bool_advanced = ob.tissue_tessellate.bool_advanced
2341 bool_multi_components = ob.tissue_tessellate.bool_multi_components
2342 combine_mode = ob.tissue_tessellate.combine_mode
2343 bounds_x = ob.tissue_tessellate.bounds_x
2344 bounds_y = ob.tissue_tessellate.bounds_y
2345 cap_faces = ob.tissue_tessellate.cap_faces
2346 open_edges_crease = ob.tissue_tessellate.open_edges_crease
2348 try:
2349 generator.name
2350 component.name
2351 except:
2352 self.report({'ERROR'},
2353 "Active object must be Tessellate before Update")
2354 return {'CANCELLED'}
2356 # Solve Local View issues
2357 local_spaces = []
2358 local_ob0 = []
2359 local_ob1 = []
2360 for area in bpy.context.screen.areas:
2361 for space in area.spaces:
2362 try:
2363 if ob.local_view_get(space):
2364 local_spaces.append(space)
2365 local_ob0 = ob0.local_view_get(space)
2366 ob0.local_view_set(space, True)
2367 local_ob1 = ob1.local_view_get(space)
2368 ob1.local_view_set(space, True)
2369 except:
2370 pass
2372 starting_mode = bpy.context.object.mode
2373 #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT'
2374 bpy.ops.object.mode_set(mode='OBJECT')
2376 ob0 = generator
2377 ob1 = component
2378 auto_layer_collection()
2380 ob0_hide = ob0.hide_get()
2381 ob0_hidev = ob0.hide_viewport
2382 ob0_hider = ob0.hide_render
2383 ob1_hide = ob1.hide_get()
2384 ob1_hidev = ob1.hide_viewport
2385 ob1_hider = ob1.hide_render
2386 ob0.hide_set(False)
2387 ob0.hide_viewport = False
2388 ob0.hide_render = False
2389 ob1.hide_set(False)
2390 ob1.hide_viewport = False
2391 ob1.hide_render = False
2393 if ob0.type == 'META':
2394 base_ob = convert_object_to_mesh(ob0, False, True)
2395 else:
2396 base_ob = ob0.copy()
2397 base_ob.data = ob0.data.copy()
2398 bpy.context.collection.objects.link(base_ob)
2400 # In Blender 2.80 cache of copied objects is lost, must be re-baked
2401 bool_update_cloth = False
2402 for m in base_ob.modifiers:
2403 if m.type == 'CLOTH':
2404 m.point_cache.frame_end = bpy.context.scene.frame_current
2405 bool_update_cloth = True
2406 if bool_update_cloth:
2407 bpy.ops.ptcache.free_bake_all()
2408 bpy.ops.ptcache.bake_all()
2410 #new_ob.location = ob.location
2411 #new_ob.matrix_world = ob.matrix_world
2412 base_ob.modifiers.update()
2413 bpy.ops.object.select_all(action='DESELECT')
2414 iter_objects = [base_ob]
2415 #base_ob = new_ob#.copy()
2417 for iter in range(iterations):
2418 same_iteration = []
2419 matched_materials = []
2420 if bool_multi_components: mat_iter = len(base_ob.material_slots)
2421 else: mat_iter = 1
2422 for m_id in range(mat_iter):
2423 if bool_multi_components:
2424 try:
2425 mat = base_ob.material_slots[m_id].material
2426 ob1 = bpy.data.objects[mat.name]
2427 material_id = m_id
2428 matched_materials.append(m_id)
2429 bool_material_id = True
2430 except:
2431 continue
2432 if com_modifiers:
2433 data1 = simple_to_mesh(ob1)
2434 else: data1 = ob1.data.copy()
2435 n_edges1 = len(data1.edges)
2437 if iter != 0: gen_modifiers = True
2438 if fill_mode == 'PATCH':
2439 new_ob = tessellate_patch(
2440 base_ob, ob1, offset, zscale, com_modifiers, mode, scale_mode,
2441 rotation_mode, random_seed, bool_vertex_group,
2442 bool_selection, bool_shapekeys, bool_material_id, material_id,
2443 bounds_x, bounds_y
2445 else:
2446 new_ob = tessellate_original(
2447 base_ob, ob1, offset, zscale, gen_modifiers,
2448 com_modifiers, mode, scale_mode, rotation_mode,
2449 random_seed, fill_mode, bool_vertex_group,
2450 bool_selection, bool_shapekeys, bool_material_id,
2451 material_id, normals_mode, bounds_x, bounds_y
2453 if type(new_ob) is bpy.types.Object:
2454 bpy.context.view_layer.objects.active = new_ob
2455 else:
2456 continue
2457 n_components = int(len(new_ob.data.edges) / n_edges1)
2458 # SELECTION
2459 if bool_selection:
2460 try:
2461 # create selection list
2462 polygon_selection = [p.select for p in ob1.data.polygons] * int(
2463 len(new_ob.data.polygons) / len(ob1.data.polygons))
2464 new_ob.data.polygons.foreach_set("select", polygon_selection)
2465 except:
2466 pass
2468 if type(new_ob) == str: break
2470 if bool_multi_components and type(new_ob) not in (int,str):
2471 same_iteration.append(new_ob)
2472 new_ob.select_set(True)
2473 bpy.context.view_layer.objects.active = new_ob
2475 if type(new_ob) == str: break
2477 #bpy.data.objects.remove(base_ob)
2478 if bool_multi_components:
2479 bpy.context.view_layer.update()
2480 bpy.context.view_layer.objects.active.select_set(True)
2481 for o in bpy.data.objects:
2482 if o in same_iteration:
2483 o.select_set(True)
2484 o.location = ob.location
2485 else:
2486 try:
2487 o.select_set(False)
2488 except: pass
2489 bpy.ops.object.join()
2490 new_ob = bpy.context.view_layer.objects.active
2491 new_ob.select_set(True)
2492 new_ob.data.update()
2494 #try:
2495 # combine object
2496 if (bool_selection or bool_material_id) and combine_mode == 'UNUSED':
2497 # remove faces from last mesh
2498 bm = bmesh.new()
2500 last_mesh = iter_objects[-1].data.copy()
2502 bm.from_mesh(last_mesh)
2503 bm.faces.ensure_lookup_table()
2504 if bool_multi_components:
2505 remove_materials = matched_materials
2506 elif bool_material_id:
2507 remove_materials = [material_id]
2508 else: remove_materials = []
2509 if bool_selection:
2510 remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select]
2511 else:
2512 remove_faces = [f for f in bm.faces if f.material_index in remove_materials]
2513 bmesh.ops.delete(bm, geom=remove_faces, context='FACES')
2514 bm.to_mesh(last_mesh)
2515 last_mesh.update()
2517 if len(last_mesh.vertices) > 0:
2518 iter_objects[-1].data = last_mesh.copy()
2519 iter_objects[-1].data.update()
2520 else:
2521 bpy.data.objects.remove(iter_objects[-1])
2522 iter_objects = iter_objects[:-1]
2524 base_ob = convert_object_to_mesh(new_ob,True,True)
2525 #bpy.context.collection.objects.unlink(base_ob)
2526 if iter < iterations-1: new_ob.data = base_ob.data
2528 iter_objects.append(new_ob)
2529 new_ob.location = ob.location
2530 new_ob.matrix_world = ob.matrix_world
2531 try:
2532 bpy.data.objects.remove(bpy.data.objects['_Tessellation_Base'])
2533 except: pass
2534 base_ob.name = "_Tessellation_Base"
2535 elif combine_mode == 'ALL':
2536 base_ob = new_ob.copy()
2537 iter_objects.append(new_ob)
2538 new_ob.location = ob.location
2539 new_ob.matrix_world = ob.matrix_world
2540 else:
2541 if base_ob != new_ob:
2542 bpy.data.objects.remove(base_ob)
2543 base_ob = new_ob
2544 iter_objects = [new_ob]
2546 if new_ob == 0:
2547 #for m, vis in zip(ob.modifiers, mod_visibility): m.show_viewport = vis
2548 message = "Zero faces selected in the Base mesh!"
2549 bpy.ops.object.mode_set(mode=starting_mode)
2550 self.report({'ERROR'}, message)
2551 return {'CANCELLED'}
2552 errors = {}
2553 errors["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \
2554 "after the last Subsurf (or Multires) are not allowed."
2555 errors["topology_error"] = "Make sure that the topology of the mesh before \n" \
2556 "the last Subsurf (or Multires) is quads only."
2557 errors["wires_error"] = "Please remove all wire edges in the base object."
2558 errors["verts_error"] = "Please remove all floating vertices in the base object"
2559 if new_ob in errors:
2560 for o in iter_objects: bpy.data.objects.remove(o)
2561 bpy.context.view_layer.objects.active = ob
2562 ob.select_set(True)
2563 message = errors[new_ob]
2564 ob.tissue_tessellate.error_message = message
2565 bpy.ops.object.mode_set(mode=starting_mode)
2566 self.report({'ERROR'}, message)
2567 return {'CANCELLED'}
2569 new_ob.location = ob.location
2570 new_ob.matrix_world = ob.matrix_world
2572 ### REPEAT
2573 if combine_mode != 'LAST' and len(iter_objects)>0:
2574 if base_ob not in iter_objects: bpy.data.objects.remove(base_ob)
2575 for o in iter_objects:
2576 o.location = ob.location
2577 o.select_set(True)
2578 bpy.ops.object.join()
2579 new_ob.data.update()
2581 # update data and preserve name
2582 if ob.type != 'MESH':
2583 loc, matr = ob.location, ob.matrix_world
2584 ob = convert_object_to_mesh(ob,False,True)
2585 ob.location, ob.matrix_world = loc, matr
2586 data_name = ob.data.name
2587 old_data = ob.data
2588 ob.data = new_ob.data
2589 bpy.data.meshes.remove(old_data)
2590 ob.data.name = data_name
2592 # copy vertex group
2593 if bool_vertex_group:
2594 for vg in new_ob.vertex_groups:
2595 if not vg.name in ob.vertex_groups.keys():
2596 ob.vertex_groups.new(name=vg.name)
2597 new_vg = ob.vertex_groups[vg.name]
2598 for i in range(len(ob.data.vertices)):
2599 try:
2600 weight = vg.weight(i)
2601 except:
2602 weight = 0
2603 new_vg.add([i], weight, 'REPLACE')
2605 selected_objects = [o for o in bpy.context.selected_objects]
2606 for o in selected_objects: o.select_set(False)
2608 ob.select_set(True)
2609 bpy.context.view_layer.objects.active = ob
2610 bpy.data.objects.remove(new_ob)
2612 if merge:
2613 bpy.ops.object.mode_set(mode='EDIT')
2614 bpy.ops.mesh.select_mode(
2615 use_extend=False, use_expand=False, type='VERT')
2616 bpy.ops.mesh.select_non_manifold(
2617 extend=False, use_wire=False, use_boundary=True,
2618 use_multi_face=False, use_non_contiguous=False, use_verts=False)
2620 bpy.ops.mesh.remove_doubles(
2621 threshold=merge_thres, use_unselected=False)
2623 bpy.ops.object.mode_set(mode='OBJECT')
2624 if bool_dissolve_seams:
2625 bpy.ops.object.mode_set(mode='EDIT')
2626 bpy.ops.mesh.select_mode(type='EDGE')
2627 bpy.ops.mesh.select_all(action='DESELECT')
2628 bpy.ops.object.mode_set(mode='OBJECT')
2629 for e in ob.data.edges:
2630 e.select = e.use_seam
2631 bpy.ops.object.mode_set(mode='EDIT')
2632 bpy.ops.mesh.dissolve_edges()
2633 if cap_faces:
2634 bpy.ops.object.mode_set(mode='EDIT')
2635 bpy.ops.mesh.select_mode(
2636 use_extend=False, use_expand=False, type='EDGE')
2637 bpy.ops.mesh.select_non_manifold(
2638 extend=False, use_wire=False, use_boundary=True,
2639 use_multi_face=False, use_non_contiguous=False, use_verts=False)
2640 bpy.ops.mesh.edge_face_add()
2641 if open_edges_crease != 0:
2642 bpy.ops.transform.edge_crease(value=open_edges_crease)
2644 bpy.ops.object.mode_set(mode='EDIT')
2645 bpy.ops.object.mode_set(mode='OBJECT')
2647 if bool_smooth: bpy.ops.object.shade_smooth()
2648 ####values = [True] * len(ob.data.polygons)
2649 ####ob.data.polygons.foreach_set("use_smooth", values)
2651 #for m, vis in zip(ob.modifiers, mod_visibility): m.show_viewport = vis
2653 end_time = time.time()
2654 print('Tissue: object "{}" tessellated in {:.4f} sec'.format(ob.name, end_time-start_time))
2656 for mesh in bpy.data.meshes:
2657 if not mesh.users: bpy.data.meshes.remove(mesh)
2659 for o in selected_objects:
2660 try: o.select_set(True)
2661 except: pass
2663 bpy.ops.object.mode_set(mode=starting_mode)
2665 # clean objects
2666 for o in bpy.data.objects:
2667 if o.name not in context.view_layer.objects and "temp" in o.name:
2668 bpy.data.objects.remove(o)
2670 ob.tissue_tessellate.error_message = ""
2672 # Restore Base visibility
2673 ob0.hide_set(ob0_hide)
2674 ob0.hide_viewport = ob0_hidev
2675 ob0.hide_render = ob0_hider
2676 # Restore Component visibility
2677 ob1.hide_set(ob1_hide)
2678 ob1.hide_viewport = ob1_hidev
2679 ob1.hide_render = ob1_hider
2680 # Restore Local visibility
2681 for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1):
2682 ob0.local_view_set(space, local0)
2683 ob1.local_view_set(space, local1)
2685 return {'FINISHED'}
2687 def check(self, context):
2688 return True
2690 class TISSUE_PT_tessellate(Panel):
2691 bl_label = "Tissue Tools"
2692 bl_category = "Edit"
2693 bl_space_type = "VIEW_3D"
2694 bl_region_type = "UI"
2695 bl_options = {'DEFAULT_CLOSED'}
2697 @classmethod
2698 def poll(cls, context):
2699 return context.mode in {'OBJECT', 'EDIT_MESH'}
2701 def draw(self, context):
2702 layout = self.layout
2704 col = layout.column(align=True)
2705 col.label(text="Tessellate:")
2706 col.operator("object.tessellate")
2707 col.operator("object.dual_mesh_tessellated")
2708 col.separator()
2709 #col = layout.column(align=True)
2710 #col.label(text="Tessellate Edit:")
2711 #col.operator("object.settings_tessellate")
2712 col.operator("object.update_tessellate", icon='FILE_REFRESH')
2714 #col = layout.column(align=True)
2715 col.operator("mesh.rotate_face", icon='NDOF_TURN')
2717 col.separator()
2718 col.label(text="Other:")
2719 col.operator("object.dual_mesh")
2720 col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
2722 act = context.active_object
2723 if act and act.type == 'MESH':
2724 col.operator("object.uv_to_mesh", icon="UV")
2727 class TISSUE_PT_tessellate_object(Panel):
2728 bl_space_type = 'PROPERTIES'
2729 bl_region_type = 'WINDOW'
2730 bl_context = "data"
2731 bl_label = "Tissue - Tessellate"
2732 bl_options = {'DEFAULT_CLOSED'}
2734 @classmethod
2735 def poll(cls, context):
2736 try: return context.object.type == 'MESH'
2737 except: return False
2739 def draw(self, context):
2740 ob = context.object
2741 props = ob.tissue_tessellate
2742 allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
2744 try:
2745 bool_tessellated = props.generator or props.component != None
2746 ob0 = props.generator
2747 ob1 = props.component
2748 except: bool_tessellated = False
2749 layout = self.layout
2750 if not bool_tessellated:
2751 layout.label(text="The selected object is not a Tessellated object",
2752 icon='INFO')
2753 else:
2754 if props.error_message != "":
2755 layout.label(text=props.error_message,
2756 icon='ERROR')
2757 col = layout.column(align=True)
2758 row = col.row(align=True)
2760 set_tessellate_handler(self,context)
2761 set_animatable_fix_handler(self,context)
2762 row.prop(props, "bool_run", text="Animatable")
2763 row.operator("object.update_tessellate", icon='FILE_REFRESH')
2765 col = layout.column(align=True)
2766 row = col.row(align=True)
2767 row.label(text="BASE :")
2768 row.label(text="COMPONENT :")
2769 row = col.row(align=True)
2771 col2 = row.column(align=True)
2772 col2.prop_search(props, "generator", context.scene, "objects")
2773 row.separator()
2774 col2 = row.column(align=True)
2775 col2.prop_search(props, "component", context.scene, "objects")
2776 row = col.row(align=True)
2777 col2 = row.column(align=True)
2778 col2.prop(props, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
2779 row.separator()
2780 try:
2781 if not (ob0.modifiers or ob0.data.shape_keys) or props.fill_mode == 'PATCH':
2782 col2.enabled = False
2783 except:
2784 col2.enabled = False
2785 col2 = row.column(align=True)
2786 col2.prop(props, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
2787 try:
2788 if not (props.component.modifiers or props.component.data.shape_keys):
2789 col2.enabled = False
2790 except:
2791 col2.enabled = False
2792 col.separator()
2794 # Fill and Rotation
2795 row = col.row(align=True)
2796 row.label(text="Fill Mode:")
2797 row.separator()
2798 row.label(text="Rotation:")
2799 row = col.row(align=True)
2801 # fill
2802 row.prop(props, "fill_mode", text="", icon='NONE', expand=False,
2803 slider=True, toggle=False, icon_only=False, event=False,
2804 full_event=False, emboss=True, index=-1)
2805 row.separator()
2807 # rotation
2808 col2 = row.column(align=True)
2809 col2.prop(props, "rotation_mode", text="", icon='NONE', expand=False,
2810 slider=True, toggle=False, icon_only=False, event=False,
2811 full_event=False, emboss=True, index=-1)
2813 if props.rotation_mode == 'RANDOM':
2814 #row = col.row(align=True)
2815 col2.prop(props, "random_seed")
2817 if props.rotation_mode == 'UV':
2818 uv_error = False
2819 if props.fill_mode == 'FAN':
2820 row = col.row(align=True)
2821 row.label(text="UV rotation doesn't work in FAN mode",
2822 icon='ERROR')
2823 uv_error = True
2824 if props.generator.type != 'MESH':
2825 row = col.row(align=True)
2826 row.label(
2827 text="UV rotation supported only for Mesh objects",
2828 icon='ERROR')
2829 uv_error = True
2830 else:
2831 if len(props.generator.data.uv_layers) == 0:
2832 row = col.row(align=True)
2833 row.label(text="'" + props.generator.name +
2834 " doesn't have UV Maps", icon='ERROR')
2835 uv_error = True
2836 if uv_error:
2837 row = col.row(align=True)
2838 row.label(text="Default rotation will be used instead",
2839 icon='INFO')
2841 # component XY
2842 row = col.row(align=True)
2843 row.label(text="Component Coordinates:")
2844 row = col.row(align=True)
2845 row.prop(props, "mode", expand=True)
2847 if props.mode != 'BOUNDS':
2848 col.separator()
2849 row = col.row(align=True)
2850 row.label(text="X:")
2851 row.prop(
2852 props, "bounds_x", text="Bounds X", icon='NONE', expand=True,
2853 slider=False, toggle=False, icon_only=False, event=False,
2854 full_event=False, emboss=True, index=-1)
2856 row = col.row(align=True)
2857 row.label(text="Y:")
2858 row.prop(
2859 props, "bounds_y", text="Bounds X", icon='NONE', expand=True,
2860 slider=False, toggle=False, icon_only=False, event=False,
2861 full_event=False, emboss=True, index=-1)
2863 # component Z
2864 col.label(text="Thickness:")
2865 row = col.row(align=True)
2866 row.prop(props, "scale_mode", expand=True)
2867 col.prop(props, "zscale", text="Scale", icon='NONE', expand=False,
2868 slider=True, toggle=False, icon_only=False, event=False,
2869 full_event=False, emboss=True, index=-1)
2870 if props.mode == 'BOUNDS':
2871 col.prop(props, "offset", text="Offset", icon='NONE', expand=False,
2872 slider=True, toggle=False, icon_only=False, event=False,
2873 full_event=False, emboss=True, index=-1)
2875 # Direction
2876 row = col.row(align=True)
2877 row.label(text="Direction:")
2878 row = col.row(align=True)
2879 row.prop(
2880 props, "normals_mode", text="Direction", icon='NONE', expand=True,
2881 slider=False, toggle=False, icon_only=False, event=False,
2882 full_event=False, emboss=True, index=-1)
2883 row.enabled = props.fill_mode != 'PATCH'
2885 # merge
2886 col = layout.column(align=True)
2887 row = col.row(align=True)
2888 row.prop(props, "merge")
2889 if props.merge:
2890 row.prop(props, "merge_thres")
2891 row = col.row(align=True)
2892 row.prop(props, "bool_smooth")
2893 if props.merge:
2894 col2 = row.column(align=True)
2895 col2.prop(props, "bool_dissolve_seams")
2896 #if props.component.type != 'MESH': col2.enabled = False
2898 row = col.row(align=True)
2899 row.prop(props, "cap_faces")
2900 if props.cap_faces:
2901 col2 = row.column(align=True)
2902 col2.prop(props, "open_edges_crease", text="Crease")
2904 # Advanced Settings
2905 col = layout.column(align=True)
2906 col.separator()
2907 col.separator()
2908 row = col.row(align=True)
2909 row.prop(props, "bool_advanced", icon='SETTINGS')
2910 if props.bool_advanced:
2911 allow_multi = False
2912 allow_shapekeys = not props.com_modifiers
2913 for m in ob0.data.materials:
2914 try:
2915 o = bpy.data.objects[m.name]
2916 allow_multi = True
2917 try:
2918 if o.data.shape_keys is None: continue
2919 elif len(o.data.shape_keys.key_blocks) < 2: continue
2920 else: allow_shapekeys = not props.com_modifiers
2921 except: pass
2922 except: pass
2923 # DATA #
2924 col = layout.column(align=True)
2925 col.label(text="Morphing:")
2926 row = col.row(align=True)
2927 col2 = row.column(align=True)
2928 col2.prop(props, "bool_vertex_group", icon='GROUP_VERTEX')
2929 #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
2930 try:
2931 if len(props.generator.vertex_groups) == 0:
2932 col2.enabled = False
2933 except:
2934 col2.enabled = False
2935 row.separator()
2936 col2 = row.column(align=True)
2937 row2 = col2.row(align=True)
2938 row2.prop(props, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
2939 row2.enabled = allow_shapekeys
2941 # LIMITED TESSELLATION
2942 col = layout.column(align=True)
2943 col.label(text="Limited Tessellation:")
2944 row = col.row(align=True)
2945 col2 = row.column(align=True)
2946 col2.prop(props, "bool_multi_components", icon='MOD_TINT')
2947 if not allow_multi:
2948 col2.enabled = False
2949 col.separator()
2950 row = col.row(align=True)
2951 col2 = row.column(align=True)
2952 col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
2953 #if props.bool_material_id or props.bool_selection or props.bool_multi_components:
2954 #col2 = row.column(align=True)
2955 # col2.prop(props, "bool_combine")
2956 row.separator()
2957 if props.generator.type != 'MESH':
2958 col2.enabled = False
2959 col2 = row.column(align=True)
2960 col2.prop(props, "bool_material_id", icon='MATERIAL_DATA', text="Material ID")
2961 if props.bool_material_id and not props.bool_multi_components:
2962 #col2 = row.column(align=True)
2963 col2.prop(props, "material_id")
2964 if props.bool_multi_components:
2965 col2.enabled = False
2967 # TRANSFER DATA ### OFF
2968 if props.fill_mode != 'PATCH' and False:
2969 col = layout.column(align=True)
2970 col.label(text="Component Data:")
2971 row = col.row(align=True)
2972 col2 = row.column(align=True)
2973 col2.prop(props, "bool_materials", icon='MATERIAL_DATA')
2974 row.separator()
2975 col2 = row.column(align=True)
2976 if props.fill_mode == 'PATCH':
2977 col.enabled = False
2978 col.label(text='Not needed in Patch mode', icon='INFO')
2980 col.separator()
2981 row = col.row(align=True)
2982 row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
2983 row.prop(props, 'iterations', text='Repeat', icon='SETTINGS')
2984 col.separator()
2985 row = col.row(align=True)
2986 row.label(text='Combine Iterations:')
2987 row = col.row(align=True)
2988 row.prop(
2989 props, "combine_mode", text="Combine:",icon='NONE', expand=True,
2990 slider=False, toggle=False, icon_only=False, event=False,
2991 full_event=False, emboss=True, index=-1)
2993 class rotate_face(Operator):
2994 bl_idname = "mesh.rotate_face"
2995 bl_label = "Rotate Faces"
2996 bl_description = "Rotate selected faces and update tessellated meshes"
2997 bl_options = {'REGISTER', 'UNDO'}
2999 @classmethod
3000 def poll(cls, context):
3001 return context.mode == 'EDIT_MESH'
3003 def execute(self, context):
3004 ob = bpy.context.active_object
3005 me = ob.data
3007 bm = bmesh.from_edit_mesh(me)
3008 mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
3010 for face in bm.faces:
3011 if (face.select):
3012 vs = face.verts[:]
3013 vs2 = vs[1:]+vs[:1]
3014 material_index = face.material_index
3015 bm.faces.remove(face)
3016 f2 = bm.faces.new(vs2)
3017 f2.select = True
3018 f2.material_index = material_index
3019 bm.normal_update()
3021 # trigger UI update
3022 bmesh.update_edit_mesh(me)
3023 ob.select_set(False)
3025 # update tessellated meshes
3026 bpy.ops.object.mode_set(mode='OBJECT')
3027 for o in [obj for obj in bpy.data.objects if
3028 obj.tissue_tessellate.generator == ob and obj.visible_get()]:
3029 bpy.context.view_layer.objects.active = o
3030 bpy.ops.object.update_tessellate()
3031 o.select_set(False)
3032 ob.select_set(True)
3033 bpy.context.view_layer.objects.active = ob
3034 bpy.ops.object.mode_set(mode='EDIT')
3035 context.tool_settings.mesh_select_mode = mesh_select_mode
3037 return {'FINISHED'}