Import_3ds: Moved keyframe transform
[blender-addons.git] / ant_landscape / ant_functions.py
blob85116d3b1be70827dc325cbf62029374de3f5971
1 # SPDX-FileCopyrightText: 2017-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Another Noise Tool - Functions
6 # Jimmy Hazevoet
8 # ErosionR:
9 # Michel Anders, Ian Huish
11 # import modules
12 import bpy
13 from bpy.props import (
14 BoolProperty,
15 FloatProperty,
16 StringProperty,
17 EnumProperty,
18 IntProperty,
19 PointerProperty,
21 from math import (
22 sin, cos, pi,
24 from .ant_noise import noise_gen
26 # ------------------------------------------------------------
27 # Create a new mesh (object) from verts/edges/faces.
28 # verts/edges/faces ... List of vertices/edges/faces for the
29 # new mesh (as used in from_pydata).
30 # name ... Name of the new mesh (& object)
32 from bpy_extras import object_utils
35 def create_mesh_object(context, verts, edges, faces, name):
36 # Create new mesh
37 mesh = bpy.data.meshes.new(name)
38 # Make a mesh from a list of verts/edges/faces.
39 mesh.from_pydata(verts, [], faces)
40 # Update mesh geometry after adding stuff.
41 mesh.update()
42 return object_utils.object_data_add(context, mesh, operator=None)
45 # Generate XY Grid
46 def grid_gen(sub_d_x, sub_d_y, tri, meshsize_x, meshsize_y, props, water_plane, water_level):
47 verts = []
48 faces = []
49 vappend = verts.append
50 fappend = faces.append
51 for i in range(0, sub_d_x):
52 x = meshsize_x * (i / (sub_d_x - 1) - 1 / 2)
53 for j in range(0, sub_d_y):
54 y = meshsize_y * (j / (sub_d_y - 1) - 1 / 2)
55 if not water_plane:
56 z = noise_gen((x, y, 0), props)
57 else:
58 z = water_level
59 vappend((x, y, z))
61 if i > 0 and j > 0:
62 A = i * sub_d_y + (j - 1)
63 B = i * sub_d_y + j
64 C = (i - 1) * sub_d_y + j
65 D = (i - 1) * sub_d_y + (j - 1)
66 if not tri:
67 fappend((A, B, C, D))
68 else:
69 fappend((A, B, D))
70 fappend((B, C, D))
72 return verts, faces
75 # Generate UV Sphere
76 def sphere_gen(sub_d_x, sub_d_y, tri, meshsize, props, water_plane, water_level):
77 verts = []
78 faces = []
79 vappend = verts.append
80 fappend = faces.append
81 sub_d_x += 1
82 sub_d_y += 1
83 for i in range(0, sub_d_x):
84 for j in range(0, sub_d_y):
85 u = sin(j * pi * 2 / (sub_d_y - 1)) * cos(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
86 v = cos(j * pi * 2 / (sub_d_y - 1)) * cos(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
87 w = sin(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
88 if water_plane:
89 h = water_level
90 else:
91 h = noise_gen((u, v, w), props) / meshsize
92 vappend(((u + u * h), (v + v * h), (w + w * h)))
94 count = 0
95 for i in range(0, sub_d_y * (sub_d_x - 1)):
96 if count < sub_d_y - 1:
97 A = i + 1
98 B = i
99 C = (i + sub_d_y)
100 D = (i + sub_d_y) + 1
101 if tri:
102 fappend((A, B, D))
103 fappend((B, C, D))
104 else:
105 fappend((A, B, C, D))
106 count = count + 1
107 else:
108 count = 0
110 return verts, faces
113 # ------------------------------------------------------------
114 # Do refresh - redraw
115 class AntLandscapeRefresh(bpy.types.Operator):
116 bl_idname = "mesh.ant_landscape_refresh"
117 bl_label = "Refresh"
118 bl_description = "Refresh landscape with current settings"
119 bl_options = {'REGISTER', 'UNDO'}
121 @classmethod
122 def poll(cls, context):
123 ob = bpy.context.active_object
124 return (ob.ant_landscape and not ob.ant_landscape.sphere_mesh)
126 def execute(self, context):
127 # ant object items
128 obj = bpy.context.active_object
130 bpy.ops.object.mode_set(mode='EDIT')
131 bpy.ops.object.mode_set(mode='OBJECT')
133 keys = obj.ant_landscape.keys()
134 if keys:
135 ob = obj.ant_landscape
136 prop = []
137 for key in keys:
138 prop.append(getattr(ob, key))
140 # redraw verts
141 mesh = obj.data
143 if ob['vert_group'] != "" and ob['vert_group'] in obj.vertex_groups:
144 vertex_group = obj.vertex_groups[ob['vert_group']]
145 gi = vertex_group.index
146 for v in mesh.vertices:
147 for g in v.groups:
148 if g.group == gi:
149 v.co[2] = 0.0
150 v.co[2] = vertex_group.weight(v.index) * noise_gen(v.co, prop)
151 else:
152 for v in mesh.vertices:
153 v.co[2] = 0.0
154 v.co[2] = noise_gen(v.co, prop)
155 mesh.update()
156 else:
157 pass
159 return {'FINISHED'}
161 # ------------------------------------------------------------
162 # Do regenerate
165 class AntLandscapeRegenerate(bpy.types.Operator):
166 bl_idname = "mesh.ant_landscape_regenerate"
167 bl_label = "Regenerate"
168 bl_description = "Regenerate landscape with current settings"
169 bl_options = {'REGISTER', 'UNDO'}
171 @classmethod
172 def poll(cls, context):
173 ob = bpy.context.active_object
174 if ob.mode == 'EDIT':
175 return False
176 return ob.ant_landscape
178 def execute(self, context):
180 view_layer = bpy.context.view_layer
181 # ant object items
182 obj = bpy.context.active_object
184 keys = obj.ant_landscape.keys()
185 if keys:
186 ob = obj.ant_landscape
187 ant_props = []
188 for key in keys:
189 ant_props.append(getattr(ob, key))
191 new_name = obj.name
193 # Main function, create landscape mesh object
194 if ob['sphere_mesh']:
195 # sphere
196 verts, faces = sphere_gen(
197 ob['subdivision_y'],
198 ob['subdivision_x'],
199 ob['tri_face'],
200 ob['mesh_size'],
201 ant_props,
202 False,
203 0.0,
205 new_ob = create_mesh_object(context, verts, [], faces, new_name)
206 if ob['remove_double']:
207 new_ob.select_set(True)
208 bpy.ops.object.mode_set(mode='EDIT')
209 bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
210 bpy.ops.object.mode_set(mode='OBJECT')
211 else:
212 # grid
213 verts, faces = grid_gen(
214 ob['subdivision_x'],
215 ob['subdivision_y'],
216 ob['tri_face'],
217 ob['mesh_size_x'],
218 ob['mesh_size_y'],
219 ant_props,
220 False,
221 0.0,
223 new_ob = create_mesh_object(context, verts, [], faces, new_name)
225 new_ob.select_set(True)
227 if ob['smooth_mesh']:
228 bpy.ops.object.shade_smooth()
229 else:
230 bpy.ops.object.shade_flat()
232 # Landscape Material
233 if ob['land_material'] != "" and ob['land_material'] in bpy.data.materials:
234 mat = bpy.data.materials[ob['land_material']]
235 bpy.context.object.data.materials.append(mat)
237 # Water plane
238 if ob['water_plane']:
239 if ob['sphere_mesh']:
240 # sphere
241 verts, faces = sphere_gen(
242 ob['subdivision_y'],
243 ob['subdivision_x'],
244 ob['tri_face'],
245 ob['mesh_size'],
246 ant_props,
247 ob['water_plane'],
248 ob['water_level'],
250 wobj = create_mesh_object(context, verts, [], faces, new_name + "_plane")
251 if ob['remove_double']:
252 wobj.select_set(True)
253 bpy.ops.object.mode_set(mode='EDIT')
254 bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
255 bpy.ops.object.mode_set(mode='OBJECT')
256 else:
257 # grid
258 verts, faces = grid_gen(
261 ob['tri_face'],
262 ob['mesh_size_x'],
263 ob['mesh_size_y'],
264 ant_props,
265 ob['water_plane'],
266 ob['water_level'],
268 wobj = create_mesh_object(context, verts, [], faces, new_name + "_plane")
270 wobj.select_set(True)
272 if ob['smooth_mesh']:
273 bpy.ops.object.shade_smooth()
274 else:
275 bpy.ops.object.shade_flat()
277 # Water Material
278 if ob['water_material'] != "" and ob['water_material'] in bpy.data.materials:
279 mat = bpy.data.materials[ob['water_material']]
280 bpy.context.object.data.materials.append(mat)
282 # Loc Rot Scale
283 if ob['water_plane']:
284 wobj.location = obj.location
285 wobj.rotation_euler = obj.rotation_euler
286 wobj.scale = obj.scale
287 wobj.select_set(False)
289 new_ob.location = obj.location
290 new_ob.rotation_euler = obj.rotation_euler
291 new_ob.scale = obj.scale
293 # Store props
294 new_ob = store_properties(ob, new_ob)
296 # Delete old object
297 new_ob.select_set(False)
299 obj.select_set(True)
300 view_layer.objects.active = obj
301 bpy.ops.object.delete(use_global=False)
303 # Select landscape and make active
304 new_ob.select_set(True)
305 view_layer.objects.active = new_ob
307 return {'FINISHED'}
310 # ------------------------------------------------------------
311 # Z normal value to vertex group (Slope map)
312 class AntVgSlopeMap(bpy.types.Operator):
313 bl_idname = "mesh.ant_slope_map"
314 bl_label = "Weight from Slope"
315 bl_description = "A.N.T. Slope Map - z normal value to vertex group weight"
316 bl_options = {'REGISTER', 'UNDO'}
318 z_method: EnumProperty(
319 name="Method:",
320 default='SLOPE_Z',
321 items=[
322 ('SLOPE_Z', "Z Slope", "Slope for planar mesh"),
323 ('SLOPE_XYZ', "Sphere Slope", "Slope for spherical mesh")
325 group_name: StringProperty(
326 name="Vertex Group Name:",
327 default="Slope",
328 description="Name"
330 select_flat: BoolProperty(
331 name="Vert Select:",
332 default=True,
333 description="Select vertices on flat surface"
335 select_range: FloatProperty(
336 name="Vert Select Range:",
337 default=0.0,
338 min=0.0,
339 max=1.0,
340 description="Increase to select more vertices on slopes"
343 @classmethod
344 def poll(cls, context):
345 ob = context.object
346 return (ob and ob.type == 'MESH')
348 def invoke(self, context, event):
349 wm = context.window_manager
350 return wm.invoke_props_dialog(self)
352 def execute(self, context):
353 message = "Popup Values: %d, %f, %s, %s" % \
354 (self.select_flat, self.select_range, self.group_name, self.z_method)
355 self.report({'INFO'}, message)
357 bpy.ops.object.mode_set(mode='OBJECT')
358 ob = bpy.context.active_object
359 dim = ob.dimensions
361 if self.select_flat:
362 bpy.ops.object.mode_set(mode='EDIT')
363 bpy.ops.mesh.select_all(action='DESELECT')
364 bpy.context.tool_settings.mesh_select_mode = [True, False, False]
365 bpy.ops.object.mode_set(mode='OBJECT')
367 bpy.ops.object.vertex_group_add()
368 vg_normal = ob.vertex_groups.active
370 for v in ob.data.vertices:
371 if self.z_method == 'SLOPE_XYZ':
372 zval = (v.co.normalized() * v.normal.normalized()) * 2 - 1
373 else:
374 zval = v.normal[2]
376 vg_normal.add([v.index], zval, 'REPLACE')
378 if self.select_flat:
379 if zval >= (1.0 - self.select_range):
380 v.select = True
382 vg_normal.name = self.group_name
384 bpy.ops.paint.weight_paint_toggle()
385 return {'FINISHED'}
388 # ------------------------------------------------------------
389 # draw properties
391 def draw_ant_refresh(self, context):
392 layout = self.layout
393 if self.auto_refresh is False:
394 self.refresh = False
395 elif self.auto_refresh is True:
396 self.refresh = True
397 row = layout.box().row()
398 split = row.split()
399 split.scale_y = 1.5
400 split.prop(self, "auto_refresh", toggle=True, icon_only=True, icon='AUTO')
401 split.prop(self, "refresh", toggle=True, icon_only=True, icon='FILE_REFRESH')
404 def draw_ant_main(self, context, generate=True):
405 layout = self.layout
406 box = layout.box()
407 box.prop(self, "show_main_settings", toggle=True)
408 if self.show_main_settings:
409 if generate:
410 row = box.row(align=True)
411 split = row.split(align=True)
413 split.prop(self, "at_cursor", toggle=True, icon_only=True, icon='PIVOT_CURSOR')
415 split.prop(self, "smooth_mesh", toggle=True, icon_only=True, icon='SHADING_SOLID')
416 split.prop(self, "tri_face", toggle=True, text="Triangulate", icon='MESH_DATA')
418 if not self.sphere_mesh:
419 row = box.row(align=True)
420 row.prop(self, "sphere_mesh", toggle=True)
421 else:
422 row = box.row(align=True)
423 split = row.split(factor=0.5, align=True)
424 split.prop(self, "sphere_mesh", toggle=True)
425 split.prop(self, "remove_double", toggle=True)
427 box.prop(self, "ant_terrain_name")
428 box.prop_search(self, "land_material", bpy.data, "materials")
430 col = box.column(align=True)
431 col.prop(self, "subdivision_x")
432 col.prop(self, "subdivision_y")
433 col = box.column(align=True)
434 if self.sphere_mesh:
435 col.prop(self, "mesh_size")
436 else:
437 col.prop(self, "mesh_size_x")
438 col.prop(self, "mesh_size_y")
441 def draw_ant_noise(self, context, generate=True):
442 layout = self.layout
443 box = layout.box()
444 box.prop(self, "show_noise_settings", toggle=True)
445 if self.show_noise_settings:
446 box.prop(self, "noise_type")
447 if self.noise_type == "blender_texture":
448 box.prop_search(self, "texture_block", bpy.data, "textures")
449 else:
450 box.prop(self, "basis_type")
452 col = box.column(align=True)
453 col.prop(self, "random_seed")
454 col = box.column(align=True)
455 col.prop(self, "noise_offset_x")
456 col.prop(self, "noise_offset_y")
457 if self.sphere_mesh or generate == False:
458 col.prop(self, "noise_offset_z")
459 col.prop(self, "noise_size_x")
460 col.prop(self, "noise_size_y")
461 if self.sphere_mesh or generate == False:
462 col.prop(self, "noise_size_z")
464 col = box.column(align=True)
465 col.prop(self, "noise_size")
467 col = box.column(align=True)
468 if self.noise_type == "multi_fractal":
469 col.prop(self, "noise_depth")
470 col.prop(self, "dimension")
471 col.prop(self, "lacunarity")
472 elif self.noise_type == "ridged_multi_fractal":
473 col.prop(self, "noise_depth")
474 col.prop(self, "dimension")
475 col.prop(self, "lacunarity")
476 col.prop(self, "offset")
477 col.prop(self, "gain")
478 elif self.noise_type == "hybrid_multi_fractal":
479 col.prop(self, "noise_depth")
480 col.prop(self, "dimension")
481 col.prop(self, "lacunarity")
482 col.prop(self, "offset")
483 col.prop(self, "gain")
484 elif self.noise_type == "hetero_terrain":
485 col.prop(self, "noise_depth")
486 col.prop(self, "dimension")
487 col.prop(self, "lacunarity")
488 col.prop(self, "offset")
489 elif self.noise_type == "fractal":
490 col.prop(self, "noise_depth")
491 col.prop(self, "dimension")
492 col.prop(self, "lacunarity")
493 elif self.noise_type == "turbulence_vector":
494 col.prop(self, "noise_depth")
495 col.prop(self, "amplitude")
496 col.prop(self, "frequency")
497 col.separator()
498 row = col.row(align=True)
499 row.prop(self, "hard_noise", expand=True)
500 elif self.noise_type == "variable_lacunarity":
501 box.prop(self, "vl_basis_type")
502 box.prop(self, "distortion")
503 elif self.noise_type == "marble_noise":
504 box.prop(self, "marble_shape")
505 box.prop(self, "marble_bias")
506 box.prop(self, "marble_sharp")
507 col = box.column(align=True)
508 col.prop(self, "distortion")
509 col.prop(self, "noise_depth")
510 col.separator()
511 row = col.row(align=True)
512 row.prop(self, "hard_noise", expand=True)
513 elif self.noise_type == "shattered_hterrain":
514 col.prop(self, "noise_depth")
515 col.prop(self, "dimension")
516 col.prop(self, "lacunarity")
517 col.prop(self, "offset")
518 col.prop(self, "distortion")
519 elif self.noise_type == "strata_hterrain":
520 col.prop(self, "noise_depth")
521 col.prop(self, "dimension")
522 col.prop(self, "lacunarity")
523 col.prop(self, "offset")
524 col.prop(self, "distortion", text="Strata")
525 elif self.noise_type == "ant_turbulence":
526 col.prop(self, "noise_depth")
527 col.prop(self, "amplitude")
528 col.prop(self, "frequency")
529 col.prop(self, "distortion")
530 col.separator()
531 row = col.row(align=True)
532 row.prop(self, "hard_noise", expand=True)
533 elif self.noise_type == "vl_noise_turbulence":
534 col.prop(self, "noise_depth")
535 col.prop(self, "amplitude")
536 col.prop(self, "frequency")
537 col.prop(self, "distortion")
538 col.separator()
539 box.prop(self, "vl_basis_type")
540 col.separator()
541 row = col.row(align=True)
542 row.prop(self, "hard_noise", expand=True)
543 elif self.noise_type == "vl_hTerrain":
544 col.prop(self, "noise_depth")
545 col.prop(self, "dimension")
546 col.prop(self, "lacunarity")
547 col.prop(self, "offset")
548 col.prop(self, "distortion")
549 col.separator()
550 box.prop(self, "vl_basis_type")
551 elif self.noise_type == "distorted_heteroTerrain":
552 col.prop(self, "noise_depth")
553 col.prop(self, "dimension")
554 col.prop(self, "lacunarity")
555 col.prop(self, "offset")
556 col.prop(self, "distortion")
557 col.separator()
558 box.prop(self, "vl_basis_type")
559 elif self.noise_type == "double_multiFractal":
560 col.prop(self, "noise_depth")
561 col.prop(self, "dimension")
562 col.prop(self, "lacunarity")
563 col.prop(self, "offset")
564 col.prop(self, "gain")
565 col.separator()
566 box.prop(self, "vl_basis_type")
567 elif self.noise_type == "rocks_noise":
568 col.prop(self, "noise_depth")
569 col.prop(self, "distortion")
570 col.separator()
571 row = col.row(align=True)
572 row.prop(self, "hard_noise", expand=True)
573 elif self.noise_type == "slick_rock":
574 col.prop(self, "noise_depth")
575 col.prop(self, "dimension")
576 col.prop(self, "lacunarity")
577 col.prop(self, "gain")
578 col.prop(self, "offset")
579 col.prop(self, "distortion")
580 col.separator()
581 box.prop(self, "vl_basis_type")
582 elif self.noise_type == "planet_noise":
583 col.prop(self, "noise_depth")
584 col.separator()
585 row = col.row(align=True)
586 row.prop(self, "hard_noise", expand=True)
588 # Effects mix
589 col = box.column(align=False)
590 box.prop(self, "fx_type")
591 if self.fx_type != "0":
592 if int(self.fx_type) <= 12:
593 box.prop(self, "fx_bias")
595 box.prop(self, "fx_mix_mode")
596 col = box.column(align=True)
597 col.prop(self, "fx_mixfactor")
599 col = box.column(align=True)
600 col.prop(self, "fx_loc_x")
601 col.prop(self, "fx_loc_y")
602 col.prop(self, "fx_size")
604 col = box.column(align=True)
605 col.prop(self, "fx_depth")
606 if self.fx_depth != 0:
607 col.prop(self, "fx_frequency")
608 col.prop(self, "fx_amplitude")
609 col.prop(self, "fx_turb")
611 col = box.column(align=True)
612 row = col.row(align=True).split(factor=0.92, align=True)
613 row.prop(self, "fx_height")
614 row.prop(self, "fx_invert", toggle=True, text="", icon='ARROW_LEFTRIGHT')
615 col.prop(self, "fx_offset")
618 def draw_ant_displace(self, context, generate=True):
619 layout = self.layout
620 box = layout.box()
621 box.prop(self, "show_displace_settings", toggle=True)
622 if self.show_displace_settings:
623 if not generate:
624 col = box.column(align=False)
625 col.prop(self, "direction", toggle=True)
627 col = box.column(align=True)
628 row = col.row(align=True).split(factor=0.92, align=True)
629 row.prop(self, "height")
630 row.prop(self, "height_invert", toggle=True, text="", icon='ARROW_LEFTRIGHT')
631 col.prop(self, "height_offset")
632 col.prop(self, "maximum")
633 col.prop(self, "minimum")
634 if generate:
635 if not self.sphere_mesh:
636 col = box.column()
637 col.prop(self, "edge_falloff")
638 if self.edge_falloff != "0":
639 col = box.column(align=True)
640 col.prop(self, "edge_level")
641 if self.edge_falloff in ["2", "3"]:
642 col.prop(self, "falloff_x")
643 if self.edge_falloff in ["1", "3"]:
644 col.prop(self, "falloff_y")
646 col = box.column()
647 col.prop(self, "strata_type")
648 if self.strata_type != "0":
649 col = box.column()
650 col.prop(self, "strata")
652 if not generate:
653 col = box.column(align=False)
654 col.prop_search(self, "vert_group", bpy.context.object, "vertex_groups")
657 def draw_ant_water(self, context):
658 layout = self.layout
659 box = layout.box()
660 col = box.column()
661 col.prop(self, "water_plane", toggle=True)
662 if self.water_plane:
663 col = box.column(align=True)
664 col.prop_search(self, "water_material", bpy.data, "materials")
665 col = box.column()
666 col.prop(self, "water_level")
669 # Store properties
670 def store_properties(operator, ob):
671 ob.ant_landscape.ant_terrain_name = operator.ant_terrain_name
672 ob.ant_landscape.at_cursor = operator.at_cursor
673 ob.ant_landscape.smooth_mesh = operator.smooth_mesh
674 ob.ant_landscape.tri_face = operator.tri_face
675 ob.ant_landscape.sphere_mesh = operator.sphere_mesh
676 ob.ant_landscape.land_material = operator.land_material
677 ob.ant_landscape.water_material = operator.water_material
678 ob.ant_landscape.texture_block = operator.texture_block
679 ob.ant_landscape.subdivision_x = operator.subdivision_x
680 ob.ant_landscape.subdivision_y = operator.subdivision_y
681 ob.ant_landscape.mesh_size_x = operator.mesh_size_x
682 ob.ant_landscape.mesh_size_y = operator.mesh_size_y
683 ob.ant_landscape.mesh_size = operator.mesh_size
684 ob.ant_landscape.random_seed = operator.random_seed
685 ob.ant_landscape.noise_offset_x = operator.noise_offset_x
686 ob.ant_landscape.noise_offset_y = operator.noise_offset_y
687 ob.ant_landscape.noise_offset_z = operator.noise_offset_z
688 ob.ant_landscape.noise_size_x = operator.noise_size_x
689 ob.ant_landscape.noise_size_y = operator.noise_size_y
690 ob.ant_landscape.noise_size_z = operator.noise_size_z
691 ob.ant_landscape.noise_size = operator.noise_size
692 ob.ant_landscape.noise_type = operator.noise_type
693 ob.ant_landscape.basis_type = operator.basis_type
694 ob.ant_landscape.vl_basis_type = operator.vl_basis_type
695 ob.ant_landscape.distortion = operator.distortion
696 ob.ant_landscape.hard_noise = operator.hard_noise
697 ob.ant_landscape.noise_depth = operator.noise_depth
698 ob.ant_landscape.amplitude = operator.amplitude
699 ob.ant_landscape.frequency = operator.frequency
700 ob.ant_landscape.dimension = operator.dimension
701 ob.ant_landscape.lacunarity = operator.lacunarity
702 ob.ant_landscape.offset = operator.offset
703 ob.ant_landscape.gain = operator.gain
704 ob.ant_landscape.marble_bias = operator.marble_bias
705 ob.ant_landscape.marble_sharp = operator.marble_sharp
706 ob.ant_landscape.marble_shape = operator.marble_shape
707 ob.ant_landscape.height = operator.height
708 ob.ant_landscape.height_invert = operator.height_invert
709 ob.ant_landscape.height_offset = operator.height_offset
710 ob.ant_landscape.maximum = operator.maximum
711 ob.ant_landscape.minimum = operator.minimum
712 ob.ant_landscape.edge_falloff = operator.edge_falloff
713 ob.ant_landscape.edge_level = operator.edge_level
714 ob.ant_landscape.falloff_x = operator.falloff_x
715 ob.ant_landscape.falloff_y = operator.falloff_y
716 ob.ant_landscape.strata_type = operator.strata_type
717 ob.ant_landscape.strata = operator.strata
718 ob.ant_landscape.water_plane = operator.water_plane
719 ob.ant_landscape.water_level = operator.water_level
720 ob.ant_landscape.vert_group = operator.vert_group
721 ob.ant_landscape.remove_double = operator.remove_double
722 ob.ant_landscape.fx_mixfactor = operator.fx_mixfactor
723 ob.ant_landscape.fx_mix_mode = operator.fx_mix_mode
724 ob.ant_landscape.fx_type = operator.fx_type
725 ob.ant_landscape.fx_bias = operator.fx_bias
726 ob.ant_landscape.fx_turb = operator.fx_turb
727 ob.ant_landscape.fx_depth = operator.fx_depth
728 ob.ant_landscape.fx_frequency = operator.fx_frequency
729 ob.ant_landscape.fx_amplitude = operator.fx_amplitude
730 ob.ant_landscape.fx_size = operator.fx_size
731 ob.ant_landscape.fx_loc_x = operator.fx_loc_x
732 ob.ant_landscape.fx_loc_y = operator.fx_loc_y
733 ob.ant_landscape.fx_height = operator.fx_height
734 ob.ant_landscape.fx_offset = operator.fx_offset
735 ob.ant_landscape.fx_invert = operator.fx_invert
736 return ob
739 # ------------------------------------------------------------
740 # "name": "ErosionR"
741 # "author": "Michel Anders, Ian Huish"
743 from random import random as rand
744 from math import tan, radians
745 from .eroder import Grid
746 from .stats import Stats
747 from .utils import numexpr_available
750 def availableVertexGroupsOrNone(self, context):
751 groups = [('None', 'None', 'None', 1)]
752 return groups + [(name, name, name, n + 1) for n, name in enumerate(context.active_object.vertex_groups.keys())]
755 class Eroder(bpy.types.Operator):
756 bl_idname = "mesh.eroder"
757 bl_label = "ErosionR"
758 bl_description = "Apply various kinds of erosion to a square ANT-Landscape grid. Also available in Weight Paint mode > Weights menu"
759 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
761 Iterations: IntProperty(
762 name="Iterations",
763 description="Number of overall iterations",
764 default=1,
765 min=1,
766 soft_max=100
768 IterRiver: IntProperty(
769 name="River Iterations",
770 description="Number of river iterations",
771 default=30,
772 min=1,
773 soft_max=1000
775 IterAva: IntProperty(
776 name="Avalanche Iterations",
777 description="Number of avalanche iterations",
778 default=5,
779 min=1,
780 soft_max=10
782 IterDiffuse: IntProperty(
783 name="Diffuse Iterations",
784 description="Number of diffuse iterations",
785 default=5,
786 min=1,
787 soft_max=10
789 Ef: FloatProperty(
790 name="Rain on Plains",
791 description="1 gives equal rain across the terrain, 0 rains more at the mountain tops",
792 default=0.0,
793 min=0,
794 max=1
796 Kd: FloatProperty(
797 name="Kd",
798 description="Thermal diffusion rate (1.0 is a fairly high rate)",
799 default=0.1,
800 min=0,
801 soft_max=100
803 Kt: FloatProperty(
804 name="Kt",
805 description="Maximum stable talus angle",
806 default=radians(60),
807 min=0,
808 max=radians(90),
809 subtype='ANGLE'
811 Kr: FloatProperty(
812 name="Rain amount",
813 description="Total Rain amount",
814 default=.01,
815 min=0,
816 soft_max=1,
817 precision=3
819 Kv: FloatProperty(
820 name="Rain variance",
821 description="Rain variance (0 is constant, 1 is uniform)",
822 default=0,
823 min=0,
824 max=1
826 userainmap: BoolProperty(
827 name="Use rain map",
828 description="Use active vertex group as a rain map",
829 default=True
831 Ks: FloatProperty(
832 name="Soil solubility",
833 description="Soil solubility - how quickly water quickly reaches saturation point",
834 default=0.5,
835 min=0,
836 soft_max=1
838 Kdep: FloatProperty(
839 name="Deposition rate",
840 description="Sediment deposition rate - how quickly silt is laid down once water stops flowing quickly",
841 default=0.1,
842 min=0,
843 soft_max=1
845 Kz: FloatProperty(
846 name="Fluvial Erosion Rate",
847 description="Amount of sediment moved each main iteration - if 0, then rivers are formed but the mesh is not changed",
848 default=0.3,
849 min=0,
850 soft_max=20)
851 Kc: FloatProperty(
852 name="Carrying capacity",
853 description="Base sediment carrying capacity",
854 default=0.9,
855 min=0,
856 soft_max=1
858 Ka: FloatProperty(
859 name="Slope dependence",
860 description="Slope dependence of carrying capacity (not used)",
861 default=1.0,
862 min=0,
863 soft_max=2
865 Kev: FloatProperty(
866 name="Evaporation",
867 description="Evaporation Rate per grid square in % - causes sediment to be dropped closer to the hills",
868 default=.5,
869 min=0,
870 soft_max=2
872 numexpr: BoolProperty(
873 name="Numexpr",
874 description="Use numexpr module (if available)",
875 default=True
877 Pd: FloatProperty(
878 name="Diffusion Amount",
879 description="Diffusion probability",
880 default=0.2,
881 min=0,
882 max=1
884 Pa: FloatProperty(
885 name="Avalanche Amount",
886 description="Avalanche amount",
887 default=0.5,
888 min=0,
889 max=1
891 Pw: FloatProperty(
892 name="River Amount",
893 description="Water erosion probability",
894 default=1,
895 min=0,
896 max=1
898 smooth: BoolProperty(
899 name="Smooth",
900 description="Set smooth shading",
901 default=True
903 showiterstats: BoolProperty(
904 name="Iteration Stats",
905 description="Show iteraration statistics",
906 default=False
908 showmeshstats: BoolProperty(
909 name="Mesh Stats",
910 description="Show mesh statistics",
911 default=False
914 stats = Stats()
915 counts = {}
916 maps = {
917 'rainmap': lambda g, r, c: g.rainmap[r, c],
918 'scree': lambda g, r, c: g.avalanced[r, c],
919 'avalanced': lambda g, r, c: -g.avalanced[r, c],
920 'water': lambda g, r, c: g.water[r, c] / g.watermax,
921 'scour': lambda g, r, c: g.scour[r, c] / max(g.scourmax, -g.scourmin),
922 'deposit': lambda g, r, c: g.scour[r, c] / min(-g.scourmax, g.scourmin),
923 'flowrate': lambda g, r, c: g.flowrate[r, c],
924 'sediment': lambda g, r, c: g.sediment[r, c],
925 'sedimentpct': lambda g, r, c: g.sedimentpct[r, c],
926 'capacity': lambda g, r, c: g.capacity[r, c]
929 def execute(self, context):
931 ob = context.active_object
932 oldMesh = ob.data
933 self.stats.reset()
934 index_to_name = {}
936 for name in self.maps:
937 try:
938 ob.vertex_groups[name]
939 except:
940 ob.vertex_groups.new(name=name)
941 # Save a mapping from index to name, in case,
942 # the next iteration is different.
943 index_to_name[ob.vertex_groups[name].index] = name
945 g = Grid.fromBlenderMesh(oldMesh, ob.vertex_groups['rainmap'], self.Ef)
947 self.counts['diffuse'] = 0
948 self.counts['avalanche'] = 0
949 self.counts['water'] = 0
950 for i in range(self.Iterations):
951 if self.IterRiver > 0:
952 for i in range(self.IterRiver):
953 g.rivergeneration(
954 self.Kr,
955 self.Kv,
956 self.userainmap,
957 self.Kc,
958 self.Ks,
959 self.Kdep,
960 self.Ka,
961 self.Kev / 100,
966 self.numexpr,
969 if self.Kd > 0.0:
970 for k in range(self.IterDiffuse):
971 g.diffuse(self.Kd / 5, self.IterDiffuse, self.numexpr)
972 self.counts['diffuse'] += 1
974 if self.Kt < radians(90) and self.Pa > 0:
975 for k in range(self.IterAva):
976 # since dx and dy are scaled to 1, tan(Kt) is the height for a given angle
977 g.avalanche(tan(self.Kt), self.IterAva, self.Pa, self.numexpr)
978 self.counts['avalanche'] += 1
979 if self.Kz > 0:
980 g.fluvial_erosion(self.Kr, self.Kv, self.userainmap, self.Kc, self.Ks,
981 self.Kz * 50, self.Ka, 0, 0, 0, 0, self.numexpr)
982 self.counts['water'] += 1
984 newMesh = bpy.data.meshes.new(oldMesh.name)
985 g.toBlenderMesh(newMesh)
987 # This empties ob.vertex_groups.
988 ob.data = newMesh
990 # Copy vertex groups from the old mesh.
991 for name in self.maps:
992 ob.vertex_groups.new(name=name)
993 for vert in oldMesh.vertices:
994 for group in vert.groups:
995 name = index_to_name[group.group]
996 if name:
997 ob.vertex_groups[name].add([vert.index], group.weight, 'REPLACE')
999 # Add the new data.
1000 for row in range(g.rainmap.shape[0]):
1001 for col in range(g.rainmap.shape[1]):
1002 i = row * g.rainmap.shape[1] + col
1003 for name, fn in self.maps.items():
1004 ob.vertex_groups[name].add([i], fn(g, row, col), 'ADD')
1006 ob.vertex_groups.active = ob.vertex_groups['capacity']
1008 if self.smooth:
1009 bpy.ops.object.shade_smooth()
1010 else:
1011 bpy.ops.object.shade_flat()
1012 self.stats.time()
1013 self.stats.memory()
1014 if self.showmeshstats:
1015 self.stats.meshstats = g.analyze()
1017 return {'FINISHED'}
1019 def draw(self, context):
1020 layout = self.layout
1022 layout.operator('screen.repeat_last', text="Repeat", icon='FILE_REFRESH')
1024 layout.prop(self, 'Iterations')
1026 box = layout.box()
1027 col = box.column(align=True)
1028 col.label(text="Thermal (Diffusion)")
1029 col.prop(self, 'Kd')
1030 col.prop(self, 'IterDiffuse')
1032 box = layout.box()
1033 col = box.column(align=True)
1034 col.label(text="Avalanche (Talus)")
1035 col.prop(self, 'Pa')
1036 col.prop(self, 'IterAva')
1037 col.prop(self, 'Kt')
1039 box = layout.box()
1040 col = box.column(align=True)
1041 col.label(text="River erosion")
1042 col.prop(self, 'IterRiver')
1043 col.prop(self, 'Kz')
1044 col.prop(self, 'Ks')
1045 col.prop(self, 'Kc')
1046 col.prop(self, 'Kdep')
1047 col.prop(self, 'Kr')
1048 col.prop(self, 'Kv')
1049 col.prop(self, 'Kev')
1051 col.prop(self, 'Ef')
1053 layout.prop(self, 'smooth')