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