1 # SPDX-License-Identifier: GPL-2.0-or-later
4 # (c) Alessandro Zomparelli #
7 # http://www.co-de-it.com/ #
9 # ############################################################################ #
13 from bpy
.types
import Operator
14 from bpy
.props
import (
22 from bpy
.types
import (
29 from mathutils
import Vector
33 update_curve_from_pydata
,
35 convert_object_to_mesh
,
43 def anim_curve_active(self
, context
):
45 props
= ob
.tissue_to_curve
48 if not ob
.tissue
.bool_lock
:
49 bpy
.ops
.object.tissue_convert_to_curve_update()
53 class tissue_to_curve_prop(PropertyGroup
):
54 object : PointerProperty(
55 type=bpy
.types
.Object
,
57 description
="Source object",
58 update
= anim_curve_active
60 bool_smooth
: BoolProperty(
61 name
="Smooth Shading",
63 description
="Output faces with smooth shading rather than flat shaded",
64 update
= anim_curve_active
66 bool_lock
: BoolProperty(
68 description
="Prevent automatic update on settings changes or if other objects have it in the hierarchy",
70 update
= anim_curve_active
72 bool_dependencies
: BoolProperty(
73 name
="Update Dependencies",
74 description
="Automatically updates source object as well, when possible",
76 update
= anim_curve_active
78 bool_run
: BoolProperty(
79 name
="Animatable Curve",
80 description
="Automatically recompute the conversion when the frame is changed",
83 use_modifiers
: BoolProperty(
86 description
="Automatically apply Modifiers and Shape Keys",
87 update
= anim_curve_active
89 subdivision_mode
: EnumProperty(
93 ('INNER', "Inner", "")
96 name
="Subdivided Edges",
97 update
= anim_curve_active
99 use_endpoint_u
: BoolProperty(
102 description
="Make all open nurbs curve meet the endpoints",
103 update
= anim_curve_active
105 clean_distance
: FloatProperty(
106 name
="Merge Distance", default
=0, min=0, soft_max
=10,
107 description
="Merge Distance",
108 update
= anim_curve_active
110 nurbs_order
: IntProperty(
111 name
="Order", default
=4, min=2, max=6,
112 description
="Nurbs order",
113 update
= anim_curve_active
115 system
: IntProperty(
116 name
="System", default
=0, min=0,
117 description
="Particle system index",
118 update
= anim_curve_active
120 bounds_selection
: EnumProperty(
123 ('BOUNDS', "Boundaries", ""),
124 ('INNER', "Inner", "")
127 name
="Boundary Selection",
128 update
= anim_curve_active
130 periodic_selection
: EnumProperty(
133 ('OPEN', "Open", ""),
134 ('CLOSED', "Closed", "")
137 name
="Periodic Selection",
138 update
= anim_curve_active
140 spline_type
: EnumProperty(
142 ('POLY', "Poly", ""),
143 ('BEZIER', "Bezier", ""),
144 ('NURBS', "NURBS", "")
148 update
= anim_curve_active
152 ('LOOPS', "Loops", ""),
153 ('EDGES', "Edges", ""),
154 ('PARTICLES', "Particles", "")
157 name
="Conversion Mode",
158 update
= anim_curve_active
160 vertex_group
: StringProperty(
161 name
="Radius", default
='',
162 description
="Vertex Group used for variable radius",
163 update
= anim_curve_active
165 invert_vertex_group
: BoolProperty(default
=False,
166 description
='Inverte the value of the Vertex Group',
167 update
= anim_curve_active
169 vertex_group_factor
: FloatProperty(
174 description
="Depth bevel factor to use for zero vertex group influence",
175 update
= anim_curve_active
177 only_sharp
: BoolProperty(
179 name
="Only Sharp Edges",
180 description
='Convert only Sharp edges',
181 update
= anim_curve_active
183 pattern_depth
: FloatProperty(
188 description
="Displacement pattern depth",
189 update
= anim_curve_active
191 pattern_offset
: FloatProperty(
196 description
="Displacement pattern offset",
197 update
= anim_curve_active
199 pattern0
: IntProperty(
204 description
="Pattern step 0",
205 update
= anim_curve_active
207 pattern1
: IntProperty(
212 description
="Pattern step 1",
213 update
= anim_curve_active
216 class tissue_convert_to_curve(Operator
):
217 bl_idname
= "object.tissue_convert_to_curve"
218 bl_label
= "Tissue Convert to Curve"
219 bl_description
= "Convert selected mesh to Curve object"
220 bl_options
= {'REGISTER', 'UNDO'}
222 object : StringProperty(
224 description
="Source object",
227 bool_smooth
: BoolProperty(
228 name
="Smooth Shading",
230 description
="Output faces with smooth shading rather than flat shaded"
232 use_modifiers
: BoolProperty(
233 name
="Use Modifiers",
235 description
="Automatically apply Modifiers and Shape Keys"
237 subdivision_mode
: EnumProperty(
240 ('CAGE', "Cage", ""),
241 ('INNER', "Inner", "")
244 name
="Subdivided Edges"
246 use_endpoint_u
: BoolProperty(
249 description
="Make all open nurbs curve meet the endpoints"
251 nurbs_order
: IntProperty(
252 name
="Order", default
=4, min=2, max=6,
253 description
="Nurbs order"
255 system
: IntProperty(
256 name
="System", default
=0, min=0,
257 description
="Particle system index"
259 clean_distance
: FloatProperty(
260 name
="Merge Distance", default
=0, min=0, soft_max
=10,
261 description
="Merge Distance"
263 spline_type
: EnumProperty(
265 ('POLY', "Poly", ""),
266 ('BEZIER', "Bezier", ""),
267 ('NURBS', "NURBS", "")
272 bounds_selection
: EnumProperty(
275 ('BOUNDS', "Boundaries", ""),
276 ('INNER', "Inner", "")
279 name
="Boundary Selection"
281 periodic_selection
: EnumProperty(
284 ('OPEN', "Open", ""),
285 ('CLOSED', "Closed", "")
288 name
="Periodic Selection"
292 ('LOOPS', "Loops", ""),
293 ('EDGES', "Edges", ""),
294 ('PARTICLES', "Particles", "")
297 name
="Conversion Mode"
299 vertex_group
: StringProperty(
300 name
="Radius", default
='',
301 description
="Vertex Group used for variable radius"
303 invert_vertex_group
: BoolProperty(default
=False,
304 description
='Inverte the value of the Vertex Group'
306 vertex_group_factor
: FloatProperty(
311 description
="Depth bevel factor to use for zero vertex group influence"
313 only_sharp
: BoolProperty(
315 name
="Only Sharp Edges",
316 description
='Convert only Sharp edges'
318 pattern_depth
: FloatProperty(
323 description
="Displacement pattern depth"
325 pattern_offset
: FloatProperty(
330 description
="Displacement pattern offset"
332 pattern0
: IntProperty(
337 description
="Pattern step 0"
339 pattern1
: IntProperty(
344 description
="Pattern step 1"
348 def poll(cls
, context
):
350 #bool_tessellated = context.object.tissue_tessellate.generator != None
352 return ob
.type in ('MESH','CURVE','SURFACE','FONT') and ob
.mode
== 'OBJECT'# and bool_tessellated
356 def invoke(self
, context
, event
):
357 self
.object = context
.object.name
358 return context
.window_manager
.invoke_props_dialog(self
)
360 def draw(self
, context
):
362 ob0
= bpy
.data
.objects
[self
.object]
363 #props = ob.tissue_to_curve
365 col
= layout
.column(align
=True)
366 row
= col
.row(align
=True)
367 #row.label(text='Object: ' + self.object)
368 #row.prop_search(self, "object", context.scene, "objects")
369 #row.prop(self, "use_modifiers")#, icon='MODIFIER', text='')
371 col
.label(text
='Conversion Mode:')
372 row
= col
.row(align
=True)
374 self
, "mode", text
="Conversion Mode", icon
='NONE', expand
=True,
375 slider
=False, toggle
=False, icon_only
=False, event
=False,
376 full_event
=False, emboss
=True, index
=-1)
377 if self
.mode
== 'PARTICLES':
379 col
.prop(self
, "system")
381 if self
.mode
in ('LOOPS', 'EDGES'):
382 row
= col
.row(align
=True)
383 row
.prop(self
, "use_modifiers")
384 col2
= row
.column(align
=True)
385 if self
.use_modifiers
:
386 col2
.prop(self
, "subdivision_mode", text
='', icon
='NONE', expand
=False,
387 slider
=True, toggle
=False, icon_only
=False, event
=False,
388 full_event
=False, emboss
=True, index
=-1)
390 for m
in bpy
.data
.objects
[self
.object].modifiers
:
391 if m
.type in ('SUBSURF','MULTIRES'): col2
.enabled
= True
393 row
= col
.row(align
=True)
394 row
.label(text
='Filter Edges:')
395 col2
= row
.column(align
=True)
396 col2
.prop(self
, "bounds_selection", text
='', icon
='NONE', expand
=False,
397 slider
=True, toggle
=False, icon_only
=False, event
=False,
398 full_event
=False, emboss
=True, index
=-1)
399 col2
.prop(self
, 'only_sharp')
401 if self
.mode
== 'LOOPS':
402 row
= col
.row(align
=True)
403 row
.label(text
='Filter Loops:')
404 row
.prop(self
, "periodic_selection", text
='', icon
='NONE', expand
=False,
405 slider
=True, toggle
=False, icon_only
=False, event
=False,
406 full_event
=False, emboss
=True, index
=-1)
408 col
.label(text
='Spline Type:')
409 row
= col
.row(align
=True)
411 self
, "spline_type", text
="Spline Type", icon
='NONE', expand
=True,
412 slider
=False, toggle
=False, icon_only
=False, event
=False,
413 full_event
=False, emboss
=True, index
=-1)
414 if self
.spline_type
== 'NURBS':
416 col
.label(text
='Nurbs splines:')
417 row
= col
.row(align
=True)
418 row
.prop(self
, "use_endpoint_u")
419 row
.prop(self
, "nurbs_order")
421 col
.prop(self
, "bool_smooth")
422 if ob0
.type == 'MESH' and self
.mode
!= 'PARTICLES':
424 col
.label(text
='Variable Radius:')
425 row
= col
.row(align
=True)
426 row
.prop_search(self
, 'vertex_group', ob0
, "vertex_groups", text
='')
427 row
.prop(self
, "invert_vertex_group", text
="", toggle
=True, icon
='ARROW_LEFTRIGHT')
428 row
.prop(self
, "vertex_group_factor")
430 col
.label(text
='Clean curves:')
431 col
.prop(self
, "clean_distance")
433 col
.label(text
='Displacement Pattern:')
434 row
= col
.row(align
=True)
435 row
.prop(self
, "pattern0")
436 row
.prop(self
, "pattern1")
437 row
= col
.row(align
=True)
438 row
.prop(self
, "pattern_depth")
439 row
.prop(self
, "pattern_offset")
441 def execute(self
, context
):
442 ob
= context
.active_object
444 crv
= bpy
.data
.curves
.new(ob
.name
+ '_Curve', type='CURVE')
445 crv
.dimensions
= '3D'
446 new_ob
= bpy
.data
.objects
.new(ob
.name
+ '_Curve', crv
)
447 bpy
.context
.collection
.objects
.link(new_ob
)
448 context
.view_layer
.objects
.active
= new_ob
450 new_ob
.select_set(True)
452 new_ob
.matrix_world
= ob
.matrix_world
454 new_ob
.tissue
.tissue_type
= 'TO_CURVE'
455 new_ob
.tissue
.bool_lock
= True
457 props
= new_ob
.tissue_to_curve
459 props
.use_modifiers
= self
.use_modifiers
460 props
.subdivision_mode
= self
.subdivision_mode
461 props
.clean_distance
= self
.clean_distance
462 props
.spline_type
= self
.spline_type
463 props
.mode
= self
.mode
464 props
.use_endpoint_u
= self
.use_endpoint_u
465 props
.nurbs_order
= self
.nurbs_order
466 props
.vertex_group
= self
.vertex_group
467 props
.vertex_group_factor
= self
.vertex_group_factor
468 props
.invert_vertex_group
= self
.invert_vertex_group
469 props
.bool_smooth
= self
.bool_smooth
470 props
.system
= self
.system
471 props
.periodic_selection
= self
.periodic_selection
472 props
.bounds_selection
= self
.bounds_selection
473 props
.only_sharp
= self
.only_sharp
474 props
.pattern0
= self
.pattern0
475 props
.pattern1
= self
.pattern1
476 props
.pattern_depth
= self
.pattern_depth
477 props
.pattern_offset
= self
.pattern_offset
479 new_ob
.tissue
.bool_lock
= False
481 bpy
.ops
.object.tissue_convert_to_curve_update()
485 class tissue_convert_to_curve_update(Operator
):
486 bl_idname
= "object.tissue_convert_to_curve_update"
487 bl_label
= "Tissue Update Curve"
488 bl_description
= "Update Curve object"
489 bl_options
= {'REGISTER', 'UNDO'}
492 def poll(cls
, context
):
495 bool_curve
= ob
.tissue_to_curve
.object != None
496 return ob
.type == 'CURVE' and ob
.mode
== 'OBJECT' and bool_curve
500 def execute(self
, context
):
501 start_time
= time
.time()
504 props
= ob
.tissue_to_curve
506 if props
.mode
== 'PARTICLES':
507 eval_ob
= ob0
.evaluated_get(context
.evaluated_depsgraph_get())
508 system_id
= min(props
.system
, len(eval_ob
.particle_systems
))
509 psystem
= eval_ob
.particle_systems
[system_id
]
510 ob
.data
.splines
.clear()
511 particles
= psystem
.particles
512 for id,p
in enumerate(particles
):
513 s
= ob
.data
.splines
.new('POLY')
514 if psystem
.settings
.type == 'HAIR':
515 n_pts
= len(p
.hair_keys
)
517 p
.hair_keys
.foreach_get('co',pts
)
518 co
= np
.array(pts
).reshape((-1,3))
520 n_pts
= 2**psystem
.settings
.display_step
+ 1
522 for i
in range(n_pts
):
523 vec
= psystem
.co_hair(eval_ob
, particle_no
=id,step
=i
)
524 vec
= ob0
.matrix_world
.inverted() @ vec
527 w
= np
.ones(n_pts
).reshape((n_pts
,1))
528 co
= np
.concatenate((co
,w
),axis
=1).reshape((n_pts
*4))
529 s
.points
.add(n_pts
-1)
530 s
.points
.foreach_set('co',co
)
534 ob0
= convert_object_to_mesh(ob0
, apply_modifiers
=props
.use_modifiers
)
536 n_verts
= len(me
.vertices
)
537 verts
= [0]*n_verts
*3
538 me
.vertices
.foreach_get('co',verts
)
539 verts
= np
.array(verts
).reshape((-1,3))
541 normals
= [0]*n_verts
*3
542 me
.vertices
.foreach_get('normal',normals
)
543 normals
= np
.array(normals
).reshape((-1,3))
544 #tilt = np.degrees(np.arcsin(normals[:,2]))
545 #tilt = np.arccos(normals[:,2])/2
547 verts
= np
.array(verts
).reshape((-1,3))
548 if props
.mode
in ('LOOPS','EDGES'):
551 bm
.verts
.ensure_lookup_table()
552 bm
.edges
.ensure_lookup_table()
553 bm
.faces
.ensure_lookup_table()
554 todo_edges
= list(bm
.edges
)
555 if props
.use_modifiers
and props
.subdivision_mode
!= 'ALL':
556 me0
, subs
= get_mesh_before_subs(_ob0
)
557 n_edges0
= len(me0
.edges
)
558 bpy
.data
.meshes
.remove(me0
)
559 if props
.subdivision_mode
== 'CAGE':
560 todo_edges
= todo_edges
[:n_edges0
*(2**subs
)]
561 elif props
.subdivision_mode
== 'INNER':
562 todo_edges
= todo_edges
[n_edges0
*(2**subs
):]
568 edge
= me
.edges
[e
.index
]
569 if edge
.use_edge_sharp
:
570 _todo_edges
.append(e
)
571 sharp_verts
.append(edge
.vertices
[0])
572 sharp_verts
.append(edge
.vertices
[1])
573 todo_edges
= _todo_edges
575 if props
.bounds_selection
== 'BOUNDS': todo_edges
= [e
for e
in todo_edges
if len(e
.link_faces
)<2]
576 elif props
.bounds_selection
== 'INNER': todo_edges
= [e
for e
in todo_edges
if len(e
.link_faces
)>1]
578 if props
.mode
== 'EDGES':
579 ordered_points
= [[e
.verts
[0].index
, e
.verts
[1].index
] for e
in todo_edges
]
580 elif props
.mode
== 'LOOPS':
581 vert_loops
, edge_loops
= loops_from_bmesh(todo_edges
)
584 for loop
in vert_loops
:
587 if v
.index
in sharp_verts
:
588 loop_points
.append(v
.index
)
590 if len(loop_points
)>1:
591 ordered_points
.append(loop_points
)
593 if len(loop_points
)>1:
594 ordered_points
.append(loop_points
)
595 #ordered_points = [[v.index for v in loop if v.index in sharp_verts] for loop in vert_loops]
597 ordered_points
= [[v
.index
for v
in loop
] for loop
in vert_loops
]
598 if props
.periodic_selection
== 'CLOSED':
599 ordered_points
= [points
for points
in ordered_points
if points
[0] == points
[-1]]
600 elif props
.periodic_selection
== 'OPEN':
601 ordered_points
= [points
for points
in ordered_points
if points
[0] != points
[-1]]
604 ordered_points
= find_curves(edges
, n_verts
)
606 bpy
.data
.objects
.remove(ob0
)
610 weight
= get_weight_numpy(ob0
.vertex_groups
[props
.vertex_group
], n_verts
)
611 if props
.invert_vertex_group
: weight
= 1-weight
612 fact
= props
.vertex_group_factor
614 weight
= weight
*(1-fact
) + fact
621 for points in ordered_points:
622 if points[0] == points[-1]: # Closed curve
623 pts0 = [points[-1]] + points[:-1] # i-1
625 pts2 = points[1:] + [points[0]] # 1+1
627 pts0 = [points[0]] + points[:-1] # i-1
629 pts2 = points[1:] + [points[-1]] # i+1
631 for i0, i1, i2 in zip(pts0, pts1, pts2):
632 pt0 = Vector(verts[i0])
633 pt1 = Vector(verts[i1])
634 pt2 = Vector(verts[i2])
635 tan1 = (pt1-pt0).normalized()
636 tan2 = (pt2-pt1).normalized()
637 vec_tan = -(tan1 + tan2).normalized()
638 vec2 = vec_tan.cross(Vector((0,0,1)))
639 vec_z = vec_tan.cross(vec2)
641 if vec_z.length == 0:
643 ang = vec_z.angle(nor)
644 if nor[2] < 0: ang = 2*pi-ang
645 #if vec_tan[0] > vec_tan[1] and nor[0]>0: ang = -ang
646 #if vec_tan[0] > vec_tan[2] and nor[0]>0: ang = -ang
647 #if vec_tan[0] < vec_tan[1] and nor[1]>0: ang = -ang
648 #if nor[0]*nor[1]*nor[2] < 0: ang = -ang
649 if nor[2] == 0: ang = -5*pi/4
650 #ang = max(ang, np.arccos(nor[2]))
651 curve_tilt.append(ang)
652 #curve_tilt.append(np.arccos(nor[2]))
653 tilt.append(curve_tilt)
655 depth
= props
.pattern_depth
656 offset
= props
.pattern_offset
657 pattern
= [props
.pattern0
,props
.pattern1
]
658 update_curve_from_pydata(ob
.data
, verts
, normals
, weight
, ordered_points
, merge_distance
=props
.clean_distance
, pattern
=pattern
, depth
=depth
, offset
=offset
)
661 bpy
.data
.objects
.remove(ob0
)
662 for s
in ob
.data
.splines
:
663 s
.type = props
.spline_type
664 if s
.type == 'NURBS':
665 s
.use_endpoint_u
= props
.use_endpoint_u
666 s
.order_u
= props
.nurbs_order
667 ob
.data
.splines
.update()
668 if not props
.bool_smooth
: bpy
.ops
.object.shade_flat()
670 end_time
= time
.time()
671 print('Tissue: object "{}" converted to Curve in {:.4f} sec'.format(ob
.name
, end_time
-start_time
))
676 class TISSUE_PT_convert_to_curve(Panel
):
677 bl_space_type
= 'PROPERTIES'
678 bl_region_type
= 'WINDOW'
680 bl_label
= "Tissue Convert to Curve"
681 bl_options
= {'DEFAULT_CLOSED'}
684 def poll(cls
, context
):
686 #bool_curve = context.object.tissue_to_curve.object != None
688 return ob
.type == 'CURVE' and ob
.tissue
.tissue_type
== 'TO_CURVE'
692 def draw(self
, context
):
694 props
= ob
.tissue_to_curve
697 #layout.use_property_split = True
698 #layout.use_property_decorate = False
699 col
= layout
.column(align
=True)
700 row
= col
.row(align
=True)
701 #col.operator("object.tissue_convert_to_curve_update", icon='FILE_REFRESH', text='Refresh')
702 row
.operator("object.tissue_update_tessellate_deps", icon
='FILE_REFRESH', text
='Refresh') ####
703 lock_icon
= 'LOCKED' if ob
.tissue
.bool_lock
else 'UNLOCKED'
704 #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
705 deps_icon
= 'LINKED' if ob
.tissue
.bool_dependencies
else 'UNLINKED'
706 row
.prop(ob
.tissue
, "bool_dependencies", text
="", icon
=deps_icon
)
707 row
.prop(ob
.tissue
, "bool_lock", text
="", icon
=lock_icon
)
708 col2
= row
.column(align
=True)
709 col2
.prop(ob
.tissue
, "bool_run", text
="",icon
='TIME')
710 col2
.enabled
= not ob
.tissue
.bool_lock
713 row
= col
.row(align
=True)
714 row
.prop_search(props
, "object", context
.scene
, "objects")
715 row
.prop(props
, "use_modifiers", icon
='MODIFIER', text
='')
717 col
.label(text
='Conversion Mode:')
718 row
= col
.row(align
=True)
720 props
, "mode", icon
='NONE', expand
=True,
721 slider
=False, toggle
=False, icon_only
=False, event
=False,
722 full_event
=False, emboss
=True, index
=-1)
723 if props
.mode
== 'PARTICLES':
725 col
.prop(props
, "system")
728 if props
.mode
in ('LOOPS', 'EDGES'):
729 row
= col
.row(align
=True)
730 row
.prop(props
, "use_modifiers")
731 col2
= row
.column(align
=True)
732 if props
.use_modifiers
:
733 col2
.prop(props
, "subdivision_mode", text
='', icon
='NONE', expand
=False,
734 slider
=True, toggle
=False, icon_only
=False, event
=False,
735 full_event
=False, emboss
=True, index
=-1)
737 for m
in props
.object.modifiers
:
738 if m
.type in ('SUBSURF','MULTIRES'): col2
.enabled
= True
740 row
= col
.row(align
=True)
741 row
.label(text
='Filter Edges:')
742 col2
= row
.column(align
=True)
743 col2
.prop(props
, "bounds_selection", text
='', icon
='NONE', expand
=False,
744 slider
=True, toggle
=False, icon_only
=False, event
=False,
745 full_event
=False, emboss
=True, index
=-1)
746 col2
.prop(props
, 'only_sharp')
748 if props
.mode
== 'LOOPS':
749 row
= col
.row(align
=True)
750 row
.label(text
='Filter Loops:')
751 row
.prop(props
, "periodic_selection", text
='', icon
='NONE', expand
=False,
752 slider
=True, toggle
=False, icon_only
=False, event
=False,
753 full_event
=False, emboss
=True, index
=-1)
756 col
.label(text
='Spline Type:')
757 row
= col
.row(align
=True)
759 props
, "spline_type", text
="Spline Type", icon
='NONE', expand
=True,
760 slider
=False, toggle
=False, icon_only
=False, event
=False,
761 full_event
=False, emboss
=True, index
=-1)
762 if props
.spline_type
== 'NURBS':
764 col
.label(text
='Nurbs Splines:')
765 row
= col
.row(align
=True)
766 row
.prop(props
, "use_endpoint_u")
767 row
.prop(props
, "nurbs_order")
769 col
.prop(props
, "bool_smooth")
770 if props
.object.type == 'MESH':
772 col
.label(text
='Variable Radius:')
773 row
= col
.row(align
=True)
774 row
.prop_search(props
, 'vertex_group', props
.object, "vertex_groups", text
='')
775 row
.prop(props
, "invert_vertex_group", text
="", toggle
=True, icon
='ARROW_LEFTRIGHT')
776 row
.prop(props
, "vertex_group_factor")
778 col
.label(text
='Clean Curves:')
779 col
.prop(props
, "clean_distance")
781 col
.label(text
='Displacement Pattern:')
782 row
= col
.row(align
=True)
783 row
.prop(props
, "pattern0")
784 row
.prop(props
, "pattern1")
785 row
= col
.row(align
=True)
786 row
.prop(props
, "pattern_depth")
787 row
.prop(props
, "pattern_offset")