Export_3ds: Added distance cue chunk export
[blender-addons.git] / mesh_tissue / contour_curves.py
blobab11a993fd0091548b50bf090050e2e448833217
1 # SPDX-FileCopyrightText: 2017 Alessandro Zomparelli
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 #-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
6 # #
7 # Vertex Color to Vertex Group allow you to convert colors channles to weight #
8 # maps. #
9 # The main purpose is to use vertex colors to store information when importing #
10 # files from other softwares. The script works with the active vertex color #
11 # slot. #
12 # For use the command "Vertex Clors to Vertex Groups" use the search bar #
13 # (space bar). #
14 # #
15 # (c) Alessandro Zomparelli #
16 # (2017) #
17 # #
18 # http://www.co-de-it.com/ #
19 # #
20 ################################################################################
22 import bpy, bmesh, os
23 import numpy as np
24 import math, timeit, time
25 from math import pi
26 from mathutils import Vector
27 from numpy import *
29 from bpy.types import (
30 Operator,
31 Panel,
32 PropertyGroup,
35 from bpy.props import (
36 BoolProperty,
37 EnumProperty,
38 FloatProperty,
39 IntProperty,
40 StringProperty,
41 FloatVectorProperty,
42 IntVectorProperty,
43 PointerProperty
46 from .utils import *
48 def anim_contour_curves(self, context):
49 ob = context.object
50 props = ob.tissue_contour_curves
51 if not (ob.tissue.bool_lock or ob.tissue.bool_hold):
52 #try:
53 props.object.name
54 bpy.ops.object.tissue_update_contour_curves()
55 #except: pass
57 class tissue_contour_curves_prop(PropertyGroup):
58 object : PointerProperty(
59 type=bpy.types.Object,
60 name="Object",
61 description="Source object",
62 update = anim_contour_curves
65 use_modifiers : BoolProperty(
66 name="Use Modifiers", default=True,
67 description="Apply all the modifiers",
68 update = anim_contour_curves
71 variable_bevel : BoolProperty(
72 name="Variable Bevel", default=False,
73 description="Variable Bevel",
74 update = anim_contour_curves
77 min_value : FloatProperty(
78 name="Offset Value", default=0., #soft_min=0, soft_max=1,
79 description="Offset contouring values",
80 update = anim_contour_curves
83 range_value : FloatProperty(
84 name="Range Values", default=100, #soft_min=0, soft_max=1,
85 description="Maximum range of contouring values",
86 update = anim_contour_curves
89 n_curves : IntProperty(
90 name="Curves", default=1000, soft_min=1, soft_max=200,
91 description="Number of Contour Curves",
92 update = anim_contour_curves
95 in_displace : FloatProperty(
96 name="Displace A", default=0, soft_min=-10, soft_max=10,
97 description="Pattern displace strength",
98 update = anim_contour_curves
101 out_displace : FloatProperty(
102 name="Displace B", default=2, soft_min=-10, soft_max=10,
103 description="Pattern displace strength",
104 update = anim_contour_curves
107 in_steps : IntProperty(
108 name="Steps A", default=1, min=0, soft_max=10,
109 description="Number of layers to move inwards",
110 update = anim_contour_curves
113 out_steps : IntProperty(
114 name="Steps B", default=1, min=0, soft_max=10,
115 description="Number of layers to move outwards",
116 update = anim_contour_curves
119 displace_x : BoolProperty(
120 name="Use X", default=True,
121 description="Displace along X axis",
122 update = anim_contour_curves
125 displace_y : BoolProperty(
126 name="Use Y", default=True,
127 description="Displace along Y axis",
128 update = anim_contour_curves
131 displace_z : BoolProperty(
132 name="Use Z", default=True,
133 description="Displace along Z axis",
134 update = anim_contour_curves
137 merge : BoolProperty(
138 name="Merge Vertices", default=True,
139 description="Merge points",
140 update = anim_contour_curves
143 merge_thres : FloatProperty(
144 name="Merge Threshold", default=0.01, min=0, soft_max=1,
145 description="Minimum Curve Radius",
146 update = anim_contour_curves
149 bevel_depth : FloatProperty(
150 name="Bevel Depth", default=0, min=0, soft_max=1,
151 description="",
152 update = anim_contour_curves
155 min_bevel_depth : FloatProperty(
156 name="Min Bevel Depth", default=0.05, min=0, soft_max=1,
157 description="",
158 update = anim_contour_curves
161 max_bevel_depth : FloatProperty(
162 name="Max Bevel Depth", default=0.20, min=0, soft_max=1,
163 description="",
164 update = anim_contour_curves
167 remove_open_curves : BoolProperty(
168 name="Remove Open Curves", default=False,
169 description="Remove Open Curves",
170 update = anim_contour_curves
173 vertex_group_pattern : StringProperty(
174 name="Displace", default='',
175 description="Vertex Group used for pattern displace",
176 update = anim_contour_curves
179 vertex_group_bevel : StringProperty(
180 name="Bevel", default='',
181 description="Variable Bevel depth",
182 update = anim_contour_curves
185 object_name : StringProperty(
186 name="Active Object", default='',
187 description="",
188 update = anim_contour_curves
191 vertex_group_contour : StringProperty(
192 name="Contour", default="",
193 description="Vertex Group used for contouring",
194 update = anim_contour_curves
197 clean_distance : FloatProperty(
198 name="Clean Distance", default=0.005, min=0, soft_max=10,
199 description="Remove short segments",
200 update = anim_contour_curves
203 spiralized: BoolProperty(
204 name='Spiralized', default=False,
205 description='Create a Spiral Contour. Works better with dense meshes.',
206 update = anim_contour_curves
209 spiral_axis: FloatVectorProperty(
210 name="Spiral Axis", default=(0,0,1),
211 description="Axis of the Spiral (in local coordinates)",
212 update = anim_contour_curves
215 spiral_rotation : FloatProperty(
216 name="Spiral Rotation", default=0, min=0, max=2*pi,
217 description="",
218 update = anim_contour_curves
221 contour_mode : EnumProperty(
222 items=(
223 ('VECTOR', "Vector", "Orient the Contour to a given vector starting from the origin of the object"),
224 ('OBJECT', "Object", "Orient the Contour to a target object's Z"),
225 ('WEIGHT', "Weight", "Contour based on a Vertex Group"),
226 ('ATTRIBUTE', "Attribute", "Contour based on an Attribute (Vertex > Float)"),
227 ('GEODESIC', "Geodesic Distance", "Contour based on the geodesic distance from the chosen vertices"),
228 ('TOPOLOGY', "Topology Distance", "Contour based on the topology distance from the chosen vertices")
230 default='VECTOR',
231 name="Mode used for the Contour Curves",
232 update = anim_contour_curves
235 contour_vector : FloatVectorProperty(
236 name='Vector', description='Constant Vector', default=(0.0, 0.0, 1.0),
237 update = anim_contour_curves
240 contour_vector_object : PointerProperty(
241 type=bpy.types.Object,
242 name="",
243 description="Target Object",
244 update = anim_contour_curves
247 contour_offset : FloatProperty(
248 name="Offset", default=0.05, min=0.000001, soft_min=0.01, soft_max=10,
249 description="Contour offset along the Vector",
250 update = anim_contour_curves
253 seeds_mode : EnumProperty(
254 items=(
255 ('BOUND', "Boundary Edges", "Compute the distance starting from the boundary edges"),
256 ('SHARP', "Sharp Edges", "Compute the distance starting from the sharp edges"),
257 ('WEIGHT', "Weight", "Compute the distance starting from the selected vertex group")
259 default='BOUND',
260 name="Seeds used for computing the distance",
261 update = anim_contour_curves
264 vertex_group_seed : StringProperty(
265 name="Seeds", default="",
266 description="Vertex Group used for computing the distance",
267 update = anim_contour_curves
270 spline_type : EnumProperty(
271 items=(
272 ('POLY', "Poly", "Generate Poly curves"),
273 ('NURBS', "NURBS", "Generate NURBS curves")
275 default='POLY',
276 name="Spline type",
277 update = anim_contour_curves
280 contour_attribute : StringProperty(
281 name="Contour Attribute", default='',
282 description="Vertex > Float attribute used for contouring",
283 update = anim_contour_curves
287 class tissue_weight_contour_curves_pattern(Operator):
288 bl_idname = "object.tissue_weight_contour_curves_pattern"
289 bl_label = "Contour Curves"
290 bl_description = ("")
291 bl_options = {'REGISTER', 'UNDO'}
293 object : StringProperty(
294 name="Object",
295 description="Source object",
296 default = ""
299 use_modifiers : BoolProperty(
300 name="Use Modifiers", default=True,
301 description="Apply all the modifiers"
304 variable_bevel : BoolProperty(
305 name="Variable Bevel", default=False,
306 description="Variable Bevel"
309 min_value : FloatProperty(
310 name="Offset Value", default=0.,
311 description="Offset contouring values"
314 range_value : FloatProperty(
315 name="Range Values", default=100,
316 description="Maximum range of contouring values"
319 n_curves : IntProperty(
320 name="Curves", default=1000, soft_min=1, soft_max=200,
321 description="Number of Contour Curves"
324 min_rad = 1
325 max_rad = 1
327 in_displace : FloatProperty(
328 name="Displace A", default=0, soft_min=-10, soft_max=10,
329 description="Pattern displace strength"
332 out_displace : FloatProperty(
333 name="Displace B", default=2, soft_min=-10, soft_max=10,
334 description="Pattern displace strength"
337 in_steps : IntProperty(
338 name="Steps A", default=1, min=0, soft_max=10,
339 description="Number of layers to move inwards"
342 out_steps : IntProperty(
343 name="Steps B", default=1, min=0, soft_max=10,
344 description="Number of layers to move outwards"
347 displace_x : BoolProperty(
348 name="Use X", default=True,
349 description="Displace along X axis"
352 displace_y : BoolProperty(
353 name="Use Y", default=True,
354 description="Displace along Y axis"
357 displace_z : BoolProperty(
358 name="Use Z", default=True,
359 description="Displace along Z axis"
362 merge : BoolProperty(
363 name="Merge Vertices", default=True,
364 description="Merge points"
367 merge_thres : FloatProperty(
368 name="Merge Threshold", default=0.01, min=0, soft_max=1,
369 description="Minimum Curve Radius"
372 bevel_depth : FloatProperty(
373 name="Bevel Depth", default=0, min=0, soft_max=1,
374 description=""
377 min_bevel_depth : FloatProperty(
378 name="Min Bevel Depth", default=0.05, min=0, soft_max=1,
379 description=""
382 max_bevel_depth : FloatProperty(
383 name="Max Bevel Depth", default=0.20, min=0, soft_max=1,
384 description=""
387 remove_open_curves : BoolProperty(
388 name="Remove Open Curves", default=False,
389 description="Remove Open Curves"
392 vertex_group_pattern : StringProperty(
393 name="Displace", default='',
394 description="Vertex Group used for pattern displace"
397 vertex_group_bevel : StringProperty(
398 name="Bevel", default='',
399 description="Variable Bevel depth"
402 object_name : StringProperty(
403 name="Active Object", default='',
404 description=""
407 contour_attribute : StringProperty(
408 name="Contour Attribute", default='',
409 description="Vertex > Float attribute used for contouring"
412 try: vg_name = bpy.context.object.vertex_groups.active.name
413 except: vg_name = ''
415 vertex_group_contour : StringProperty(
416 name="Contour", default=vg_name,
417 description="Vertex Group used for contouring"
420 clean_distance : FloatProperty(
421 name="Clean Distance", default=0.005, min=0, soft_max=10,
422 description="Remove short segments"
425 spiralized: BoolProperty(
426 name='Spiralized', default=False,
427 description='Create a Spiral Contour. Works better with dense meshes.'
430 spiral_axis: FloatVectorProperty(
431 name="Spiral Axis", default=(0,0,1),
432 description="Axis of the Spiral (in local coordinates)"
435 spiral_rotation : FloatProperty(
436 name="Spiral Rotation", default=0, min=0, max=2*pi,
437 description=""
440 bool_hold : BoolProperty(
441 name="Hold",
442 description="Wait...",
443 default=False
446 contour_mode : EnumProperty(
447 items=(
448 ('VECTOR', "Vector", "Orient the Contour to a given vector starting from the origin of the object"),
449 ('OBJECT', "Object", "Orient the Contour to a target object's Z"),
450 ('WEIGHT', "Weight", "Contour based on a Vertex Group"),
451 ('ATTRIBUTE', "Attribute", "Contour based on an Attribute (Vertex > Float)"),
452 ('GEODESIC', "Geodesic Distance", "Contour based on the geodesic distance from the chosen vertices"),
453 ('TOPOLOGY', "Topology Distance", "Contour based on the topology distance from the chosen vertices")
455 default='VECTOR',
456 name="Mode used for the Contour Curves"
459 contour_vector : FloatVectorProperty(
460 name='Vector', description='Constant Vector', default=(0.0, 0.0, 1.0)
463 contour_vector_object : StringProperty(
464 name="Object",
465 description="Target object",
466 default = ""
469 contour_offset : FloatProperty(
470 name="Offset", default=0.05, min=0.000001, soft_min=0.01, soft_max=10,
471 description="Contour offset along the Vector"
474 seeds_mode : EnumProperty(
475 items=(
476 ('BOUND', "Boundary Edges", "Compute the distance starting from the boundary edges"),
477 ('SHARP', "Sharp Edges", "Compute the distance starting from the sharp edges"),
478 ('WEIGHT', "Weight", "Compute the distance starting from the selected vertex group")
480 default='BOUND',
481 name="Seeds used for computing the distance"
484 vertex_group_seed : StringProperty(
485 name="Seeds", default=vg_name,
486 description="Vertex Group used for computing the distance"
489 spline_type : EnumProperty(
490 items=(
491 ('POLY', "Poly", "Generate Poly curves"),
492 ('NURBS', "NURBS", "Generate NURBS curves")
494 default='POLY',
495 name="Spline type"
498 def invoke(self, context, event):
499 self.object = context.object.name
500 return context.window_manager.invoke_props_dialog(self, width=250)
502 def draw(self, context):
503 ob = context.object
504 ob0 = bpy.data.objects[self.object]
506 if self.contour_mode == 'WEIGHT':
507 try:
508 if self.vertex_group_contour not in [vg.name for vg in ob.vertex_groups]:
509 self.vertex_group_contour = ob.vertex_groups.active.name
510 except:
511 self.contour_mode == 'VECTOR'
513 if not self.bool_hold:
514 self.object = ob.name
515 self.bool_hold = True
517 layout = self.layout
518 col = layout.column(align=True)
519 col.prop(self, "use_modifiers")
520 col.label(text="Contour Curves:")
522 row = col.row()
523 row.prop(self, "spline_type", icon='NONE', expand=True,
524 slider=True, toggle=False, icon_only=False, event=False,
525 full_event=False, emboss=True, index=-1)
526 col.separator()
527 col.prop(self, "contour_mode", text="Mode")
529 if self.contour_mode == 'VECTOR':
530 row = col.row()
531 row.prop(self,'contour_vector')
532 elif self.contour_mode == 'WEIGHT':
533 col.prop_search(self, 'vertex_group_contour', ob, "vertex_groups", text='Group')
534 elif self.contour_mode == 'ATTRIBUTE':
535 col.prop_search(self, 'contour_attribute', ob0.data, "attributes", text='Attribute')
536 is_attribute = True
537 if self.contour_attribute in ob0.data.attributes:
538 attr = ob0.data.attributes[self.contour_attribute]
539 is_attribute = attr.data_type == 'FLOAT' and attr.domain == 'POINT'
540 else:
541 is_attribute = False
542 if not is_attribute:
543 col.label(text="Please select a (Vertex > Float) Attribute for contouring.", icon='ERROR')
544 elif self.contour_mode in ('TOPOLOGY','GEODESIC'):
545 col.prop(self, "seeds_mode", text="Seeds")
546 if self.seeds_mode == 'WEIGHT':
547 col.prop_search(self, 'vertex_group_seed', ob, "vertex_groups", text='Group')
548 elif self.contour_mode == 'OBJECT':
549 col.prop_search(self, "contour_vector_object", context.scene, "objects", text='Object')
550 col.separator()
552 if self.contour_mode == 'OBJECT':
553 col.prop(self,'contour_offset')
554 col.prop(self,'n_curves', text='Max Curves')
555 elif self.contour_mode in ('VECTOR', 'GEODESIC', 'ATTRIBUTE'):
556 col.prop(self,'contour_offset')
557 row = col.row(align=True)
558 row.prop(self,'min_value')
559 row.prop(self,'range_value')
560 col.prop(self,'n_curves', text='Max Curves')
561 elif self.contour_mode in ('TOPOLOGY', 'WEIGHT'):
562 row = col.row(align=True)
563 row.prop(self,'min_value')
564 row.prop(self,'range_value')
565 col.prop(self,'n_curves')
567 col.separator()
568 col.label(text='Curves Bevel:')
569 col.prop(self,'variable_bevel')
570 row = col.row(align=True)
571 row.prop(self,'min_bevel_depth')
572 row.prop(self,'max_bevel_depth')
573 row2 = col.row(align=True)
574 row2.prop_search(self, 'vertex_group_bevel', ob, "vertex_groups", text='')
575 if not self.variable_bevel:
576 row.enabled = row2.enabled = False
577 col.separator()
579 col.label(text="Displace Pattern:")
580 col.prop_search(self, 'vertex_group_pattern', ob, "vertex_groups", text='')
581 if self.vertex_group_pattern != '':
582 col.separator()
583 row = col.row(align=True)
584 row.prop(self,'in_steps')
585 row.prop(self,'out_steps')
586 row = col.row(align=True)
587 row.prop(self,'in_displace')
588 row.prop(self,'out_displace')
589 col.separator()
590 row = col.row(align=True)
591 row.label(text="Axis")
592 row.prop(self,'displace_x', text="X", toggle=1)
593 row.prop(self,'displace_y', text="Y", toggle=1)
594 row.prop(self,'displace_z', text="Z", toggle=1)
595 col.separator()
597 col.label(text='Clean Curves:')
598 col.prop(self,'clean_distance')
599 col.prop(self,'remove_open_curves')
601 def execute(self, context):
602 ob0 = bpy.context.object
604 self.object_name = "Contour Curves"
605 # Check if existing object with same name
606 names = [o.name for o in bpy.data.objects]
607 if self.object_name in names:
608 count_name = 1
609 while True:
610 test_name = self.object_name + '.{:03d}'.format(count_name)
611 if not (test_name in names):
612 self.object_name = test_name
613 break
614 count_name += 1
616 if bpy.ops.object.select_all.poll():
617 bpy.ops.object.select_all(action='DESELECT')
618 bpy.ops.object.mode_set(mode='OBJECT')
620 bool_update = False
621 if context.object == ob0:
622 auto_layer_collection()
623 curve = bpy.data.curves.new(self.object_name,'CURVE')
624 new_ob = bpy.data.objects.new(self.object_name,curve)
625 bpy.context.collection.objects.link(new_ob)
626 bpy.context.view_layer.objects.active = new_ob
627 if bpy.ops.object.select_all.poll():
628 bpy.ops.object.select_all(action='DESELECT')
629 bpy.ops.object.mode_set(mode='OBJECT')
630 new_ob.select_set(True)
631 else:
632 new_ob = context.object
633 bool_update = True
635 # Store parameters
636 props = new_ob.tissue_contour_curves
637 new_ob.tissue.bool_hold = True
638 if self.object in bpy.data.objects.keys():
639 props.object = bpy.data.objects[self.object]
640 props.use_modifiers = self.use_modifiers
641 props.variable_bevel = self.variable_bevel
642 props.min_value = self.min_value
643 props.range_value = self.range_value
644 props.n_curves = self.n_curves
645 props.in_displace = self.in_displace
646 props.out_displace = self.out_displace
647 props.in_steps = self.in_steps
648 props.out_steps = self.out_steps
649 props.displace_x = self.displace_x
650 props.displace_y = self.displace_y
651 props.displace_z = self.displace_z
652 props.merge = self.merge
653 props.merge_thres = self.merge_thres
654 props.bevel_depth = self.bevel_depth
655 props.min_bevel_depth = self.min_bevel_depth
656 props.max_bevel_depth = self.max_bevel_depth
657 props.remove_open_curves = self.remove_open_curves
658 props.vertex_group_pattern = self.vertex_group_pattern
659 props.vertex_group_bevel = self.vertex_group_bevel
660 props.object_name = self.object_name
661 props.vertex_group_contour = self.vertex_group_contour
662 props.clean_distance = self.clean_distance
663 props.spiralized = self.spiralized
664 props.spiral_axis = self.spiral_axis
665 props.spiral_rotation = self.spiral_rotation
666 props.contour_mode = self.contour_mode
667 if self.contour_vector_object in bpy.data.objects.keys():
668 props.contour_vector_object = bpy.data.objects[self.contour_vector_object]
669 props.contour_vector = self.contour_vector
670 props.contour_offset = self.contour_offset
671 props.seeds_mode = self.seeds_mode
672 props.vertex_group_seed = self.vertex_group_seed
673 props.spline_type = self.spline_type
674 props.contour_attribute = self.contour_attribute
675 new_ob.tissue.bool_hold = False
677 new_ob.tissue.tissue_type = 'CONTOUR_CURVES'
678 try: bpy.ops.object.tissue_update_contour_curves()
679 except RuntimeError as e:
680 print("no update")
681 bpy.data.objects.remove(new_ob)
682 remove_temp_objects()
683 self.report({'ERROR'}, str(e))
684 return {'CANCELLED'}
685 if not bool_update:
686 self.object_name = new_ob.name
687 #self.working_on = self.object_name
688 new_ob.location = ob0.location
689 new_ob.matrix_world = ob0.matrix_world
691 # Assign collection of the base object
692 old_coll = new_ob.users_collection
693 if old_coll != ob0.users_collection:
694 for c in old_coll:
695 c.objects.unlink(new_ob)
696 for c in ob0.users_collection:
697 c.objects.link(new_ob)
698 context.view_layer.objects.active = new_ob
700 return {'FINISHED'}
702 class tissue_update_contour_curves(Operator):
703 bl_idname = "object.tissue_update_contour_curves"
704 bl_label = "Update Contour Curves"
705 bl_description = ("Update a previously generated Contour Curves object")
706 bl_options = {'REGISTER', 'UNDO'}
708 def execute(self, context):
709 ob = context.object
710 props = ob.tissue_contour_curves
711 _ob0 = props.object
712 n_curves = props.n_curves
713 tt0 = time.time()
714 tt1 = time.time()
715 tissue_time(None,'Tissue: Contour Curves of "{}"...'.format(ob.name), levels=0)
717 ob0 = convert_object_to_mesh(_ob0, apply_modifiers=props.use_modifiers)
718 ob0.name = "_tissue_tmp_ob0"
719 me0 = ob0.data
721 # generate new bmesh
722 bm = bmesh.new()
723 bm.from_mesh(me0)
724 n_verts = len(bm.verts)
725 vertices, normals = get_vertices_and_normals_numpy(me0)
727 if props.contour_mode == 'OBJECT':
728 try:
729 vec_ob = props.contour_vector_object
730 vec_ob_name = vec_ob.name
731 except:
732 bm.free()
733 bpy.data.objects.remove(ob0)
734 self.report({'ERROR'}, "Please select an target Object")
735 return {'CANCELLED'}
737 tt1 = tissue_time(tt1, "Load objects", levels=1)
739 # store weight values
740 if props.contour_mode in ('VECTOR','OBJECT'):
741 ob0_matrix = np.matrix(ob0.matrix_world.to_3x3().transposed())
742 global_verts = np.matmul(vertices,ob0_matrix)
743 global_verts += np.array(ob0.matrix_world.translation)
744 if props.contour_mode == 'OBJECT' and props.contour_vector_object:
745 vec_ob = props.contour_vector_object
746 global_verts -= np.array(vec_ob.matrix_world.translation)
747 vec_ob_matrix = np.matrix(vec_ob.matrix_world.to_3x3().inverted().transposed())
748 global_verts = np.matmul(global_verts,vec_ob_matrix)
749 weight = global_verts[:,2].A1
750 elif props.contour_mode == 'VECTOR':
751 vec = np.array(props.contour_vector)
752 vec_len = np.linalg.norm(vec)
753 if vec_len == 0:
754 vec = np.array((0,0,1))
755 vec_len = 1
756 else:
757 vec /= vec_len
758 vec_len = 1
759 global_verts = global_verts.A
760 projected_verts = global_verts * vec
761 projected_verts = np.sum(projected_verts,axis=1)[:,np.newaxis]
762 weight = projected_verts.reshape((-1))
763 elif props.contour_mode == 'WEIGHT':
764 try:
765 weight = get_weight_numpy(ob0.vertex_groups[props.vertex_group_contour], len(me0.vertices))
766 except:
767 bm.free()
768 bpy.data.objects.remove(ob0)
769 self.report({'ERROR'}, "Please select a Vertex Group for contouring")
770 return {'CANCELLED'}
771 elif props.contour_mode == 'ATTRIBUTE':
772 if props.contour_attribute in me0.attributes:
773 weight = [0]*n_verts
774 me0.attributes[props.contour_attribute].data.foreach_get('value',weight)
775 weight = np.array(weight)
776 else:
777 bm.free()
778 bpy.data.objects.remove(ob0)
779 self.report({'ERROR'}, "Please select a (Vertex > Float) Attribute for contouring")
780 return {'CANCELLED'}
781 elif props.contour_mode in ('GEODESIC','TOPOLOGY'):
782 cancel = False
783 weight = [None]*n_verts
784 seed_verts = []
785 bm.verts.ensure_lookup_table()
786 if props.seeds_mode == 'BOUND':
787 for v in bm.verts:
788 if v.is_boundary:
789 seed_verts.append(v)
790 weight[v.index] = 0
791 if props.seeds_mode == 'SHARP':
792 for e, bme in zip(me0.edges, bm.edges):
793 if e.use_edge_sharp:
794 seed_verts.append(bme.verts[0])
795 seed_verts.append(bme.verts[1])
796 seed_verts = list(set(seed_verts))
797 if len(seed_verts) == 0: cancel = True
798 for i in [v.index for v in seed_verts]:
799 weight[i] = 0
800 if props.seeds_mode == 'WEIGHT':
801 try:
802 seeds = get_weight_numpy(ob0.vertex_groups[props.vertex_group_seed], len(me0.vertices))
803 except:
804 bm.free()
805 bpy.data.objects.remove(ob0)
806 self.report({'ERROR'}, "Please select a Vertex Group as seed")
807 return {'CANCELLED'}
808 for i,v in enumerate(bm.verts):
809 if seeds[i]>0.999999:
810 seed_verts.append(v)
811 weight[i] = 0
812 if cancel or len(seed_verts)==0:
813 bm.free()
814 bpy.data.objects.remove(ob0)
815 self.report({'ERROR'}, "No seed vertices found")
816 return {'CANCELLED'}
818 weight = fill_neighbors_attribute(seed_verts, weight, props.contour_mode)
819 weight = np.array(weight)
820 print(weight[weight==None])
821 weight[weight==None] = 0
822 print(weight[weight==None])
824 try:
825 pattern_weight = get_weight_numpy(ob0.vertex_groups[props.vertex_group_pattern], len(me0.vertices))
826 except:
827 #self.report({'WARNING'}, "There is no Vertex Group assigned to the pattern displace")
828 pattern_weight = np.zeros(len(me0.vertices))
830 weight_bevel = False
831 if props.variable_bevel:
832 try:
833 bevel_weight = get_weight_numpy(ob0.vertex_groups[props.vertex_group_bevel], len(me0.vertices))
834 weight_bevel = True
835 except:
836 bevel_weight = np.ones(len(me0.vertices))
837 else:
838 bevel_weight = np.ones(len(me0.vertices))
840 total_verts = np.zeros((0,3))
841 total_radii = np.zeros((0,1))
842 total_edges_index = np.zeros((0)).astype('int')
843 total_segments = []# np.array([])
844 radius = []
846 tt1 = tissue_time(tt1, "Compute values", levels=1)
848 # start iterate contours levels
849 filtered_edges = get_edges_id_numpy(me0)
851 min_value = props.min_value
852 max_value = props.min_value + props.range_value
854 if props.contour_mode in ('VECTOR','OBJECT','GEODESIC','ATTRIBUTE'):
855 delta_iso = props.contour_offset
856 n_curves = min(int((np.max(weight)-props.min_value)/delta_iso)+1, props.n_curves)
857 else:
858 if n_curves == 1:
859 delta_iso = props.range_value/2
860 else:
861 delta_iso = props.range_value/(n_curves-1)
862 if props.contour_mode == 'TOPOLOGY':
863 weight = weight/np.max(weight)
865 if False:
866 edges_verts = get_attribute_numpy(me0.edges,"vertices",mult=2).astype('int')
867 edges_vec = vertices[edges_verts[:,0]]-vertices[edges_verts[:,1]]
868 #edges_vec = global_verts[edges_verts[:,0]]-global_verts[edges_verts[:,1]]
869 edges_length = np.linalg.norm(edges_vec,axis=1)
870 edges_vec /= edges_length[:,np.newaxis]
871 edges_dw = np.abs(weight[edges_verts[:,0]]-weight[edges_verts[:,1]])
872 edges_bevel = delta_iso*edges_length/edges_dw/2*0 + 1
875 # numpy method
876 faces_n_verts = get_attribute_numpy(me0.polygons, attribute='loop_total').astype('int')
877 faces_verts = get_attribute_numpy(me0.polygons, attribute='vertices', size=np.sum(faces_n_verts)).astype('int')
878 faces_weight = weight[faces_verts]
879 faces_weight = np.split(faces_weight, np.cumsum(faces_n_verts)[:-1])
881 faces_weight = [np.array([weight[v] for v in p.vertices]) for p in me0.polygons]
882 try:
883 fw_min = np.min(faces_weight, axis=1)
884 fw_max = np.max(faces_weight, axis=1)
885 except:
886 # necessary for irregular meshes
887 fw_min = np.array([min(fw) for fw in faces_weight])
888 fw_max = np.array([max(fw) for fw in faces_weight])
889 bm_faces = np.array(bm.faces)
891 tt1 = tissue_time(tt1, "Compute face values", levels=1)
892 for c in range(n_curves):
893 if delta_iso:
894 iso_val = c*delta_iso + min_value
895 else:
896 iso_val = min_value + range_value/2
897 if iso_val > max_value: break
899 # remove passed faces
900 bool_mask = iso_val <= fw_max
901 bm_faces = bm_faces[bool_mask]
902 fw_min = fw_min[bool_mask]
903 fw_max = fw_max[bool_mask]
905 # mask faces
906 bool_mask = fw_min <= iso_val
907 faces_mask = bm_faces[bool_mask]
909 count = len(total_verts)
911 if not weight_bevel and props.variable_bevel:
912 bevel_weight = np.full(n_verts, c/n_curves)
913 new_filtered_edges, edges_index, verts, bevel = contour_edges_pattern(props, c, len(total_verts), iso_val, vertices, normals, filtered_edges, weight, pattern_weight, bevel_weight)
914 #bevel = edges_bevel[edges_index][:,np.newaxis]
916 if len(edges_index) > 0:
917 if props.variable_bevel and props.max_bevel_depth != props.min_bevel_depth and False:
918 #min_radius = min(props.min_bevel_depth, props.max_bevel_depth)
919 #max_radius = max(props.min_bevel_depth, props.max_bevel_depth)
920 min_radius = props.min_bevel_depth
921 max_radius = props.max_bevel_depth
922 min_radius = min_radius / max(0.0001,max_radius)
923 radii = min_radius + bevel*(1 - min_radius)
924 else:
925 radii = bevel
926 else:
927 continue
929 if verts[0,0] == None: continue
930 else: filtered_edges = new_filtered_edges
931 edges_id = {}
932 for i, id in enumerate(edges_index): edges_id[id] = i + count
934 if len(verts) == 0: continue
936 # finding segments
937 segments = []
938 for f in faces_mask:
939 seg = []
940 for e in f.edges:
941 try:
942 #seg.append(new_ids[np.where(edges_index == e.index)[0][0]])
943 seg.append(edges_id[e.index])
944 if len(seg) == 2:
945 segments.append(seg)
946 seg = []
947 except: pass
949 total_segments = total_segments + segments
950 total_verts = np.concatenate((total_verts, verts))
951 total_radii = np.concatenate((total_radii, radii))
952 total_edges_index = np.concatenate((total_edges_index, edges_index))
953 tt1 = tissue_time(tt1, "Compute curves", levels=1)
955 if len(total_segments) > 0:
956 ordered_points, ordered_points_edge_id = find_curves_attribute(total_segments, len(total_verts), total_edges_index)
958 total_tangents = np.zeros((len(total_verts),3))
959 for curve in ordered_points:
960 np_curve = np.array(curve).astype('int')
961 curve_pts = np.array(total_verts[np_curve], dtype=np.float64)
962 tangents = np.roll(curve_pts,1) - np.roll(curve_pts,-1)
963 tangents /= np.linalg.norm(tangents,axis=1)[:,np.newaxis]
964 total_tangents[curve] = tangents
966 step_time = timeit.default_timer()
967 ob.data.splines.clear()
968 if props.variable_bevel:# and not weight_bevel:
969 total_radii = np.interp(total_radii, (total_radii.min(), total_radii.max()), (props.min_bevel_depth, props.max_bevel_depth))
970 ob.data = curve_from_pydata(total_verts, total_radii, ordered_points, ob0.name + '_ContourCurves', props.remove_open_curves, merge_distance=props.clean_distance, only_data=True, curve=ob.data, spline_type=props.spline_type)
971 #context.view_layer.objects.active = crv
972 if props.variable_bevel:
973 if not weight_bevel:
974 ob.data.bevel_depth = 1
975 else:
976 ob.data.bevel_depth = max(props.max_bevel_depth, props.min_bevel_depth)
977 tt1 = tissue_time(tt1, "Store curves data", levels=1)
978 else:
979 ob.data.splines.clear()
980 pass
981 bm.free()
982 for o in bpy.data.objects:
983 if '_tissue_tmp_' in o.name:
984 bpy.data.objects.remove(o)
986 tt0 = tissue_time(tt0, "Contour Curves", levels=0)
987 return {'FINISHED'}
990 class TISSUE_PT_contour_curves(Panel):
991 bl_space_type = 'PROPERTIES'
992 bl_region_type = 'WINDOW'
993 bl_context = "data"
994 bl_label = "Tissue Contour Curves"
995 bl_options = {'DEFAULT_CLOSED'}
997 @classmethod
998 def poll(cls, context):
999 try:
1000 #bool_curve = context.object.tissue_to_curve.object != None
1001 ob = context.object
1002 return ob.type == 'CURVE' and ob.tissue.tissue_type == 'CONTOUR_CURVES'
1003 except:
1004 return False
1006 def draw(self, context):
1007 ob = context.object
1008 props = ob.tissue_contour_curves
1009 ob0 = bpy.data.objects[props.object.name]
1011 layout = self.layout
1012 #layout.use_property_split = True
1013 #layout.use_property_decorate = False
1014 col = layout.column(align=True)
1015 row = col.row(align=True)
1016 #col.operator("object.tissue_update_convert_to_curve", icon='FILE_REFRESH', text='Refresh')
1017 row.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') ####
1018 lock_icon = 'LOCKED' if ob.tissue.bool_lock else 'UNLOCKED'
1019 #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
1020 deps_icon = 'LINKED' if ob.tissue.bool_dependencies else 'UNLINKED'
1021 row.prop(ob.tissue, "bool_dependencies", text="", icon=deps_icon)
1022 row.prop(ob.tissue, "bool_lock", text="", icon=lock_icon)
1023 col2 = row.column(align=True)
1024 col2.prop(ob.tissue, "bool_run", text="",icon='TIME')
1025 col2.enabled = not ob.tissue.bool_lock
1026 col2 = row.column(align=True)
1027 col2.operator("mesh.tissue_remove", text="", icon='X')
1029 col.separator()
1030 row = col.row(align=True)
1031 row.prop_search(props, "object", context.scene, "objects", text="")
1032 row.prop(props, "use_modifiers", icon='MODIFIER', text='')
1033 col.separator()
1034 col.label(text="Contour Curves:")
1035 row = col.row()
1036 row.prop(props, "spline_type", icon='NONE', expand=True,
1037 slider=True, toggle=False, icon_only=False, event=False,
1038 full_event=False, emboss=True, index=-1)
1039 col.separator()
1040 col.prop(props, "contour_mode", text="Mode")
1042 if props.contour_mode == 'VECTOR':
1043 row = col.row()
1044 row.prop(props,'contour_vector')
1045 elif props.contour_mode == 'WEIGHT':
1046 col.prop_search(props, 'vertex_group_contour', ob0, "vertex_groups", text='Group')
1047 elif props.contour_mode == 'ATTRIBUTE':
1048 col.prop_search(props, 'contour_attribute', ob0.data, "attributes", text='Attribute')
1049 is_attribute = True
1050 if props.contour_attribute in ob0.data.attributes:
1051 attr = ob0.data.attributes[props.contour_attribute]
1052 is_attribute = attr.data_type == 'FLOAT' and attr.domain == 'POINT'
1053 else:
1054 is_attribute = False
1055 if not is_attribute:
1056 col.label(text="Please select a (Vertex > Float) Attribute for contouring.", icon='ERROR')
1057 elif props.contour_mode in ('TOPOLOGY','GEODESIC'):
1058 col.prop(props, "seeds_mode", text="Seeds")
1059 if props.seeds_mode == 'WEIGHT':
1060 col.prop_search(props, 'vertex_group_seed', ob0, "vertex_groups", text='Group')
1061 elif props.contour_mode == 'OBJECT':
1062 col.prop_search(props, "contour_vector_object", context.scene, "objects", text='Object')
1063 col.separator()
1065 if props.contour_mode == 'OBJECT':
1066 col.prop(props,'contour_offset')
1067 col.prop(props,'n_curves', text='Max Curves')
1068 elif props.contour_mode in ('VECTOR','GEODESIC','ATTRIBUTE'):
1069 col.prop(props,'contour_offset')
1070 row = col.row(align=True)
1071 row.prop(props,'min_value')
1072 row.prop(props,'range_value')
1073 col.prop(props,'n_curves', text='Max Curves')
1074 elif props.contour_mode in ('TOPOLOGY', 'WEIGHT'):
1075 row = col.row(align=True)
1076 row.prop(props,'min_value')
1077 row.prop(props,'range_value')
1078 col.prop(props,'n_curves')
1080 col.separator()
1081 col.label(text='Curves Bevel:')
1082 col.prop(props,'variable_bevel')
1083 row = col.row(align=True)
1084 row.prop(props,'min_bevel_depth')
1085 row.prop(props,'max_bevel_depth')
1086 row2 = col.row(align=True)
1087 row2.prop_search(props, 'vertex_group_bevel', ob0, "vertex_groups", text='')
1088 if not props.variable_bevel:
1089 row.enabled = row2.enabled = False
1090 col.separator()
1092 col.label(text="Displace Pattern:")
1093 col.prop_search(props, 'vertex_group_pattern', ob0, "vertex_groups", text='')
1094 if props.vertex_group_pattern != '':
1095 col.separator()
1096 row = col.row(align=True)
1097 row.prop(props,'in_steps')
1098 row.prop(props,'out_steps')
1099 row = col.row(align=True)
1100 row.prop(props,'in_displace')
1101 row.prop(props,'out_displace')
1102 col.separator()
1103 row = col.row(align=True)
1104 row.label(text="Axis")
1105 row.prop(props,'displace_x', text="X", toggle=1)
1106 row.prop(props,'displace_y', text="Y", toggle=1)
1107 row.prop(props,'displace_z', text="Z", toggle=1)
1108 col.separator()
1109 row=col.row(align=True)
1111 col.label(text='Clean Curves:')
1112 col.prop(props,'clean_distance')
1113 col.prop(props,'remove_open_curves')
1115 def contour_edges_pattern(operator, c, verts_count, iso_val, vertices, normals, filtered_edges, weight, pattern_weight, bevel_weight):
1116 # vertices indexes
1117 id0 = filtered_edges[:,0]
1118 id1 = filtered_edges[:,1]
1119 # vertices weight
1120 w0 = weight[id0]
1121 w1 = weight[id1]
1122 # weight condition
1123 bool_w0 = w0 <= iso_val
1124 bool_w1 = w1 <= iso_val
1126 # mask all edges that have one weight value below the iso value
1127 mask_new_verts = np.logical_xor(bool_w0, bool_w1)
1128 if not mask_new_verts.any():
1129 return np.array([[None]]), {}, np.array([[None]]), np.array([[None]])
1131 id0 = id0[mask_new_verts]
1132 id1 = id1[mask_new_verts]
1133 # filter arrays
1134 v0 = vertices[id0]
1135 v1 = vertices[id1]
1136 n0 = normals[id0]
1137 n1 = normals[id1]
1138 w0 = w0[mask_new_verts]
1139 w1 = w1[mask_new_verts]
1140 pattern0 = pattern_weight[id0]
1141 pattern1 = pattern_weight[id1]
1142 try:
1143 bevel0 = bevel_weight[id0]
1144 bevel1 = bevel_weight[id1]
1145 except: pass
1147 param = (iso_val - w0)/(w1-w0)
1148 if c%(operator.in_steps + operator.out_steps) < operator.in_steps:
1149 mult = operator.in_displace
1150 else:
1151 mult = operator.out_displace
1152 pattern_value = pattern0 + (pattern1-pattern0)*param
1153 try:
1154 bevel_value = bevel0 + (bevel1-bevel0)*param
1155 bevel_value = np.expand_dims(bevel_value,axis=1)
1156 except: bevel_value = None
1157 disp = pattern_value * mult
1159 param = np.expand_dims(param,axis=1)
1160 disp = np.expand_dims(disp,axis=1)
1161 verts = v0 + (v1-v0)*param
1162 norm = n0 + (n1-n0)*param
1163 axis = np.array((operator.displace_x, operator.displace_y, operator.displace_z))
1164 norm[:] *= axis
1165 verts = verts + norm*disp
1167 # indexes of edges with new vertices
1168 edges_index = filtered_edges[mask_new_verts][:,2]
1170 # remove all edges completely below the iso value
1171 #mask_edges = np.logical_not(np.logical_and(bool_w0, bool_w1))
1172 #filtered_edges = filtered_edges[mask_edges]
1173 return filtered_edges.astype("int"), edges_index, verts, bevel_value