Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJimmy Hazevoet <jimhazevoet@gmail.com>2017-06-17 17:52:25 +0300
committermeta-androcto <meta.androcto1@gmail.com>2017-06-17 17:52:25 +0300
commite813b903c340fdc1edb2d51c4c5c492a55f367bf (patch)
treefe4df5a6cda6fb63b0ce2f08e2ad73023ca29060 /ant_landscape
parent200a9bbbd480251d774424700a424f105022535a (diff)
Ant Landscape: fix requested ui changes
Diffstat (limited to 'ant_landscape')
-rw-r--r--ant_landscape/ErosionR.txt2
-rw-r--r--ant_landscape/__init__.py500
-rw-r--r--ant_landscape/add_mesh_ant_landscape.py18
-rw-r--r--ant_landscape/ant_functions.py452
-rw-r--r--ant_landscape/ant_landscape_refresh.py106
-rw-r--r--ant_landscape/eroder.py652
-rw-r--r--ant_landscape/mesh_ant_displace.py46
-rw-r--r--ant_landscape/stats.py55
-rw-r--r--ant_landscape/test.py19
-rw-r--r--ant_landscape/utils.py7
10 files changed, 1542 insertions, 315 deletions
diff --git a/ant_landscape/ErosionR.txt b/ant_landscape/ErosionR.txt
new file mode 100644
index 00000000..1dae7aa0
--- /dev/null
+++ b/ant_landscape/ErosionR.txt
@@ -0,0 +1,2 @@
+http://blog.michelanders.nl/search/label/erosion
+https://github.com/nerk987/ErosionR \ No newline at end of file
diff --git a/ant_landscape/__init__.py b/ant_landscape/__init__.py
index 02f66ec4..4b389184 100644
--- a/ant_landscape/__init__.py
+++ b/ant_landscape/__init__.py
@@ -16,14 +16,14 @@
#
# ##### END GPL LICENSE BLOCK #####
-# Another Noise Tool - Suite
+# Another Noise Tool - Suite (W.I.P.)
# Jim Hazevoet 5/2017
bl_info = {
"name": "A.N.T.Landscape",
"author": "Jim Hazevoet",
- "version": (0, 1, 6),
- "blender": (2, 77, 0),
+ "version": (0, 1, 7),
+ "blender": (2, 78, 0),
"location": "View3D > Tool Shelf",
"description": "Another Noise Tool: Landscape and Displace",
"warning": "",
@@ -54,18 +54,22 @@ from bpy.props import (
PointerProperty,
EnumProperty,
)
-'''
+
from .ant_functions import (
draw_ant_refresh,
draw_ant_main,
draw_ant_noise,
draw_ant_displace,
)
-'''
+
# ------------------------------------------------------------
# Menu and panels
+def menu_func_eroder(self, context):
+ #self.layout.operator(Eroder.bl_idname, text="Eroder", icon='RNDCURVE')
+ self.layout.operator('mesh.eroder', text="Eroder", icon='RNDCURVE')
+
# Define "Landscape" menu
def menu_func_landscape(self, context):
self.layout.operator('mesh.landscape_add', text="Landscape", icon="RNDCURVE")
@@ -76,7 +80,7 @@ class panel_func_add_landscape(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_context = "objectmode"
bl_region_type = "TOOLS"
- bl_label = "ANT Landscape"
+ bl_label = "Landscape"
bl_category = "Create"
bl_options = {'DEFAULT_CLOSED'}
@@ -90,37 +94,41 @@ class AntLandscapeToolsPanel(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_context = "objectmode"
bl_region_type = "TOOLS"
- bl_label = "ANT Displace/Slopemap"
+ bl_label = "Landscape Tools"
bl_category = "Tools"
bl_options = {'DEFAULT_CLOSED'}
+ @classmethod
+ def poll(cls, context):
+ ob = bpy.context.active_object
+ return (ob and ob.type == 'MESH')
+
def draw(self, context):
layout = self.layout
ob = context.active_object
if ob and ob.type == 'MESH':
- box = layout.box()
- col = box.column()
- col.label("Mesh:")
- col.operator('mesh.ant_displace', text="Displace", icon="RNDCURVE")
- col = box.column()
+ col = layout.column()
+ col.operator('mesh.ant_displace', text="Mesh Displace", icon="RNDCURVE")
+ col.operator('mesh.eroder', text="Landscape Eroder", icon='SMOOTHCURVE')
col.operator('mesh.ant_slope_map', icon='GROUP_VERTEX')
else:
box = layout.box()
- col = box.column()
- col.label("Select a Mesh Object")
+ box.label("Select a Mesh!", icon='ERROR')
-# Landscape Settings:
-class AntLandscapeSettingsPanel(bpy.types.Panel):
- bl_space_type = "VIEW_3D"
- bl_context = "objectmode"
- bl_region_type = "TOOLS"
- bl_category = "Create"
+# Landscape Settings / Properties:
+class AntMainSettingsPanel(bpy.types.Panel):
+ bl_idname = "ANTMAIN_PT_layout"
bl_options = {'DEFAULT_CLOSED'}
- bl_label = "ANT Landscape Settings"
- # bl_space_type = 'PROPERTIES'
- # bl_region_type = 'WINDOW'
- # bl_context = "world"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_context = "object"
+ bl_label = "Landscape Main"
+
+ @classmethod
+ def poll(cls, context):
+ ob = bpy.context.active_object.ant_landscape.keys()
+ return ob
def draw(self, context):
layout = self.layout
@@ -130,200 +138,256 @@ class AntLandscapeSettingsPanel(bpy.types.Panel):
if ob and ob.ant_landscape.keys():
ant = ob.ant_landscape
box = layout.box()
- split = box.column().row().split()
- split.scale_y = 1.5
- split.operator('mesh.ant_landscape_regenerate', text="", icon="LOOP_FORWARDS")
- split.operator('mesh.ant_landscape_refresh', text="", icon="FILE_REFRESH")
-
+ col = box.column(align=False)
+ col.scale_y = 1.5
+ col.operator('mesh.ant_landscape_regenerate', text="Regenerate", icon="LOOP_FORWARDS")
+ row = box.row(align=True)
+ split = row.split(align=True)
+ split.prop(ant, "smooth_mesh", toggle=True, text="Smooth", icon='SOLID')
+ split.prop(ant, "tri_face", toggle=True, text="Triangulate", icon='MESH_DATA')
+ if ant.sphere_mesh:
+ split.prop(ant, "remove_double", toggle=True, text="Remove Doubles", icon='MESH_DATA')
+ box.prop(ant, "ant_terrain_name")
+ box.prop_search(ant, "land_material", bpy.data, "materials")
+ col = box.column(align=True)
+ col.prop(ant, "subdivision_x")
+ col.prop(ant, "subdivision_y")
+ col = box.column(align=True)
+ if ant.sphere_mesh:
+ col.prop(ant, "mesh_size")
+ else:
+ col.prop(ant, "mesh_size_x")
+ col.prop(ant, "mesh_size_y")
+ else:
box = layout.box()
- box.prop(ant, "show_main_settings", toggle=True)
- if ant.show_main_settings:
- #row = box.row(align=True)
- #split = row.split(align=True)
- #split.prop(ant, "at_cursor", toggle=True, icon_only=True, icon='CURSOR')
- #split.prop(ant, "smooth_mesh", toggle=True, icon_only=True, icon='SOLID')
- #split.prop(ant, "sphere_mesh", toggle=True)
- #split.prop(ant, "tri_face", toggle=True, icon_only=True, icon='MESH_DATA')
- #box.prop(ant, "ant_terrain_name")
- #box.prop_search(ant, "land_material", bpy.data, "materials")
- col = box.column(align=True)
- col.prop(ant, "subdivision_x")
- col.prop(ant, "subdivision_y")
- col = box.column(align=True)
- if ant.sphere_mesh:
- col.prop(ant, "mesh_size")
- else:
- col.prop(ant, "mesh_size_x")
- col.prop(ant, "mesh_size_y")
+ box.label("Select a Landscape Object!", icon='ERROR')
- box = layout.box()
- box.prop(ant, "show_noise_settings", toggle=True)
- if ant.show_noise_settings:
- box.prop(ant, "noise_type")
- if ant.noise_type == "blender_texture":
- box.prop_search(ant, "texture_block", bpy.data, "textures")
- else:
- box.prop(ant, "basis_type")
- col = box.column(align=True)
- col.prop(ant, "random_seed")
- col = box.column(align=True)
- col.prop(ant, "noise_offset_x")
- col.prop(ant, "noise_offset_y")
- col.prop(ant, "noise_offset_z")
- col.prop(ant, "noise_size_x")
- col.prop(ant, "noise_size_y")
+# Landscape Settings / Properties:
+class AntNoiseSettingsPanel(bpy.types.Panel):
+ bl_idname = "ANTNOISE_PT_layout"
+ bl_options = {'DEFAULT_CLOSED'}
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_context = "object"
+ bl_label = "Landscape Noise"
+
+ @classmethod
+ def poll(cls, context):
+ ob = bpy.context.active_object.ant_landscape.keys()
+ return ob
+
+ def draw(self, context):
+ layout = self.layout
+ scene = context.scene
+ ob = bpy.context.active_object
+
+ if ob and ob.ant_landscape.keys():
+ ant = ob.ant_landscape
+
+ box = layout.box()
+ col = box.column(align=True)
+ col.scale_y = 1.5
+ if ant.sphere_mesh:
+ col.operator('mesh.ant_landscape_regenerate', text="Regenerate", icon="LOOP_FORWARDS")
+ else:
+ col.operator('mesh.ant_landscape_refresh', text="Refresh", icon="FILE_REFRESH")
+
+ box.prop(ant, "noise_type")
+ if ant.noise_type == "blender_texture":
+ box.prop_search(ant, "texture_block", bpy.data, "textures")
+ else:
+ box.prop(ant, "basis_type")
+
+ col = box.column(align=True)
+ col.prop(ant, "random_seed")
+ col = box.column(align=True)
+ col.prop(ant, "noise_offset_x")
+ col.prop(ant, "noise_offset_y")
+ col.prop(ant, "noise_offset_z")
+ col.prop(ant, "noise_size_x")
+ col.prop(ant, "noise_size_y")
+ if ant.sphere_mesh:
col.prop(ant, "noise_size_z")
+ col = box.column(align=True)
+ col.prop(ant, "noise_size")
+
+ col = box.column(align=True)
+ if ant.noise_type == "multi_fractal":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ elif ant.noise_type == "ridged_multi_fractal":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "offset")
+ col.prop(ant, "gain")
+ elif ant.noise_type == "hybrid_multi_fractal":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "offset")
+ col.prop(ant, "gain")
+ elif ant.noise_type == "hetero_terrain":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "offset")
+ elif ant.noise_type == "fractal":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ elif ant.noise_type == "turbulence_vector":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "amplitude")
+ col.prop(ant, "frequency")
+ col.separator()
+ row = col.row(align=True)
+ row.prop(ant, "hard_noise", expand=True)
+ elif ant.noise_type == "variable_lacunarity":
+ box.prop(ant, "vl_basis_type")
+ box.prop(ant, "distortion")
+ elif ant.noise_type == "marble_noise":
+ box.prop(ant, "marble_shape")
+ box.prop(ant, "marble_bias")
+ box.prop(ant, "marble_sharp")
col = box.column(align=True)
- col.prop(ant, "noise_size")
+ col.prop(ant, "distortion")
+ col.prop(ant, "noise_depth")
+ col.separator()
+ row = col.row(align=True)
+ row.prop(ant, "hard_noise", expand=True)
+ elif ant.noise_type == "shattered_hterrain":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "offset")
+ col.prop(ant, "distortion")
+ elif ant.noise_type == "strata_hterrain":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "offset")
+ col.prop(ant, "distortion", text="Strata")
+ elif ant.noise_type == "ant_turbulence":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "amplitude")
+ col.prop(ant, "frequency")
+ col.prop(ant, "distortion")
+ col.separator()
+ row = col.row(align=True)
+ row.prop(ant, "hard_noise", expand=True)
+ elif ant.noise_type == "vl_noise_turbulence":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "amplitude")
+ col.prop(ant, "frequency")
+ col.prop(ant, "distortion")
+ col.separator()
+ col.prop(ant, "vl_basis_type")
+ col.separator()
+ row = col.row(align=True)
+ row.prop(ant, "hard_noise", expand=True)
+ elif ant.noise_type == "vl_hTerrain":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "offset")
+ col.prop(ant, "distortion")
+ col.separator()
+ col.prop(ant, "vl_basis_type")
+ elif ant.noise_type == "distorted_heteroTerrain":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "offset")
+ col.prop(ant, "distortion")
+ col.separator()
+ col.prop(ant, "vl_basis_type")
+ elif ant.noise_type == "double_multiFractal":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "offset")
+ col.prop(ant, "gain")
+ col.separator()
+ col.prop(ant, "vl_basis_type")
+ elif ant.noise_type == "slick_rock":
+ col.prop(ant, "noise_depth")
+ col.prop(ant, "dimension")
+ col.prop(ant, "lacunarity")
+ col.prop(ant, "gain")
+ col.prop(ant, "offset")
+ col.prop(ant, "distortion")
+ col.separator()
+ col.prop(ant, "vl_basis_type")
+ elif ant.noise_type == "planet_noise":
+ col.prop(ant, "noise_depth")
+ col.separator()
+ row = col.row(align=True)
+ row.prop(ant, "hard_noise", expand=True)
+ else:
+ box = layout.box()
+ box.label("Select a Landscape Object!", icon='ERROR')
- col = box.column(align=True)
- if ant.noise_type == "multi_fractal":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- elif ant.noise_type == "ridged_multi_fractal":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "offset")
- col.prop(ant, "gain")
- elif ant.noise_type == "hybrid_multi_fractal":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "offset")
- col.prop(ant, "gain")
- elif ant.noise_type == "hetero_terrain":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "offset")
- elif ant.noise_type == "fractal":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- elif ant.noise_type == "turbulence_vector":
- col.prop(ant, "noise_depth")
- col.prop(ant, "amplitude")
- col.prop(ant, "frequency")
- col.separator()
- row = col.row(align=True)
- row.prop(ant, "hard_noise", expand=True)
- elif ant.noise_type == "variable_lacunarity":
- box.prop(ant, "vl_basis_type")
- box.prop(ant, "distortion")
- elif ant.noise_type == "marble_noise":
- box.prop(ant, "marble_shape")
- box.prop(ant, "marble_bias")
- box.prop(ant, "marble_sharp")
- col = box.column(align=True)
- col.prop(ant, "distortion")
- col.prop(ant, "noise_depth")
- col.separator()
- row = col.row(align=True)
- row.prop(ant, "hard_noise", expand=True)
- elif ant.noise_type == "shattered_hterrain":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "offset")
- col.prop(ant, "distortion")
- elif ant.noise_type == "strata_hterrain":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "offset")
- col.prop(ant, "distortion", text="Strata")
- elif ant.noise_type == "ant_turbulence":
- col.prop(ant, "noise_depth")
- col.prop(ant, "amplitude")
- col.prop(ant, "frequency")
- col.prop(ant, "distortion")
- col.separator()
- row = col.row(align=True)
- row.prop(ant, "hard_noise", expand=True)
- elif ant.noise_type == "vl_noise_turbulence":
- col.prop(ant, "noise_depth")
- col.prop(ant, "amplitude")
- col.prop(ant, "frequency")
- col.prop(ant, "distortion")
- col.separator()
- col.prop(ant, "vl_basis_type")
- col.separator()
- row = col.row(align=True)
- row.prop(ant, "hard_noise", expand=True)
- elif ant.noise_type == "vl_hTerrain":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "offset")
- col.prop(ant, "distortion")
- col.separator()
- col.prop(ant, "vl_basis_type")
- elif ant.noise_type == "distorted_heteroTerrain":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "offset")
- col.prop(ant, "distortion")
- col.separator()
- col.prop(ant, "vl_basis_type")
- elif ant.noise_type == "double_multiFractal":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "offset")
- col.prop(ant, "gain")
- col.separator()
- col.prop(ant, "vl_basis_type")
- elif ant.noise_type == "slick_rock":
- col.prop(ant, "noise_depth")
- col.prop(ant, "dimension")
- col.prop(ant, "lacunarity")
- col.prop(ant, "gain")
- col.prop(ant, "offset")
- col.prop(ant, "distortion")
- col.separator()
- col.prop(ant, "vl_basis_type")
- elif ant.noise_type == "planet_noise":
- col.prop(ant, "noise_depth")
- col.separator()
- row = col.row(align=True)
- row.prop(ant, "hard_noise", expand=True)
+# Landscape Settings / Properties:
+class AntDisplaceSettingsPanel(bpy.types.Panel):
+ bl_idname = "ANTDISP_PT_layout"
+ bl_options = {'DEFAULT_CLOSED'}
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_context = "object"
+ bl_label = "Landscape Displace"
+
+ @classmethod
+ def poll(cls, context):
+ ob = bpy.context.active_object.ant_landscape.keys()
+ return ob
+
+ def draw(self, context):
+ layout = self.layout
+ scene = context.scene
+ ob = bpy.context.active_object
+
+ if ob and ob.ant_landscape.keys():
+ ant = ob.ant_landscape
box = layout.box()
- box.prop(ant, "show_displace_settings", toggle=True)
- if ant.show_displace_settings:
- col = box.column(align=True)
- row = col.row(align=True).split(0.92, align=True)
- row.prop(ant, "height")
- row.prop(ant, "height_invert", toggle=True, text="", icon='ARROW_LEFTRIGHT')
- col.prop(ant, "height_offset")
- col.prop(ant, "maximum")
- col.prop(ant, "minimum")
-
- if not ant.sphere_mesh:
- col = box.column()
- col.prop(ant, "edge_falloff")
- if ant.edge_falloff is not "0":
- col = box.column(align=True)
- col.prop(ant, "edge_level")
- if ant.edge_falloff in ["2", "3"]:
- col.prop(ant, "falloff_x")
- if ant.edge_falloff in ["1", "3"]:
- col.prop(ant, "falloff_y")
+ col = box.column(align=True)
+ col.scale_y = 1.5
+ if ant.sphere_mesh:
+ col.operator('mesh.ant_landscape_regenerate', text="Regenerate", icon="LOOP_FORWARDS")
+ else:
+ col.operator('mesh.ant_landscape_refresh', text="Refresh", icon="FILE_REFRESH")
+
+ col = box.column(align=True)
+ row = col.row(align=True).split(0.92, align=True)
+ row.prop(ant, "height")
+ row.prop(ant, "height_invert", toggle=True, text="", icon='ARROW_LEFTRIGHT')
+ col.prop(ant, "height_offset")
+ col.prop(ant, "maximum")
+ col.prop(ant, "minimum")
+ if not ant.sphere_mesh:
+ col = box.column()
+ col.prop(ant, "edge_falloff")
+ if ant.edge_falloff is not "0":
+ col = box.column(align=True)
+ col.prop(ant, "edge_level")
+ if ant.edge_falloff in ["2", "3"]:
+ col.prop(ant, "falloff_x")
+ if ant.edge_falloff in ["1", "3"]:
+ col.prop(ant, "falloff_y")
+ col = box.column()
+ col.prop(ant, "strata_type")
+ if ant.strata_type is not "0":
col = box.column()
- col.prop(ant, "strata_type")
- if ant.strata_type is not "0":
- col = box.column()
- col.prop(ant, "strata")
-
+ col.prop(ant, "strata")
+ col = box.column()
+ col.prop(ant, "use_vgroup", toggle=True)
else:
box = layout.box()
- col = box.column()
- col.label("Select a Landscape Object")
+ box.label("Select a Landscape Object!", icon='ERROR')
# ------------------------------------------------------------
@@ -712,21 +776,6 @@ class AntLandscapePropertiesGroup(bpy.types.PropertyGroup):
default=False,
description="Remove doubles"
)
- show_main_settings = BoolProperty(
- name="Main Settings",
- default=True,
- description="Show settings"
- )
- show_noise_settings = BoolProperty(
- name="Noise Settings",
- default=True,
- description="Show noise settings"
- )
- show_displace_settings = BoolProperty(
- name="Displace Settings",
- default=True,
- description="Show displace settings"
- )
refresh = BoolProperty(
name="Refresh",
default=False,
@@ -738,20 +787,23 @@ class AntLandscapePropertiesGroup(bpy.types.PropertyGroup):
description="Automatic refresh"
)
-
# ------------------------------------------------------------
# Register:
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_mesh_add.append(menu_func_landscape)
- bpy.types.Object.ant_landscape = PointerProperty(type=AntLandscapePropertiesGroup, name="ANT_Landscape", description="Landscape properties", options={'ANIMATABLE'})
+ bpy.types.Object.ant_landscape = PointerProperty(type=AntLandscapePropertiesGroup, name="ANT_Landscape", description="Landscape properties")
+ bpy.types.VIEW3D_MT_paint_weight.append(menu_func_eroder)
+ bpy.types.VIEW3D_MT_object.append(menu_func_eroder)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_mesh_add.remove(menu_func_landscape)
- #del bpy.types.Object.AntLandscapePropertiesGroup
+ bpy.types.VIEW3D_MT_paint_weight.remove(menu_func_eroder)
+ bpy.types.VIEW3D_MT_object.remove(menu_func_eroder)
+
if __name__ == "__main__":
register()
diff --git a/ant_landscape/add_mesh_ant_landscape.py b/ant_landscape/add_mesh_ant_landscape.py
index cad52c44..fccb54c8 100644
--- a/ant_landscape/add_mesh_ant_landscape.py
+++ b/ant_landscape/add_mesh_ant_landscape.py
@@ -47,7 +47,7 @@ from .ant_functions import (
class AntAddLandscape(bpy.types.Operator):
bl_idname = "mesh.landscape_add"
bl_label = "Another Noise Tool - Landscape"
- bl_description = "Add landscape mesh"
+ bl_description = "A.N.T. Add landscape mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
ant_terrain_name = StringProperty(
@@ -650,19 +650,3 @@ class AntAddLandscape(bpy.types.Operator):
context.user_preferences.edit.use_global_undo = undo
return {'FINISHED'}
-
-'''
-# ------------------------------------------------------------
-# Register:
-
-def register():
- bpy.utils.register_module(__name__)
-
-
-def unregister():
- bpy.utils.unregister_module(__name__)
-
-
-if __name__ == "__main__":
- register()
-''' \ No newline at end of file
diff --git a/ant_landscape/ant_functions.py b/ant_landscape/ant_functions.py
index 74412d17..3c3091ac 100644
--- a/ant_landscape/ant_functions.py
+++ b/ant_landscape/ant_functions.py
@@ -19,6 +19,9 @@
# Another Noise Tool - Functions
# Jim Hazevoet
+# ErosionR:
+# Michel Anders (varkenvarken), Ian Huish (nerk)
+
# import modules
import bpy
from bpy.props import (
@@ -151,8 +154,8 @@ class AntVgSlopeMap(bpy.types.Operator):
name="Method:",
default='SLOPE_Z',
items=[
- ('SLOPE_Z', "Slope Z", "Slope for planar mesh"),
- ('SLOPE_XYZ', "Slope XYZ", "Slope for spherical mesh")
+ ('SLOPE_Z', "Z Slope", "Slope for planar mesh"),
+ ('SLOPE_XYZ', "Sphere Slope", "Slope for spherical mesh")
])
group_name = StringProperty(
name="Vertex Group Name:",
@@ -171,6 +174,11 @@ class AntVgSlopeMap(bpy.types.Operator):
max=1.0,
description="Increase to select more vertices"
)
+ weight_mode = BoolProperty(
+ name="Enter WeightPaint Mode:",
+ default=True,
+ description="Enter weightpaint mode when done"
+ )
@classmethod
def poll(cls, context):
@@ -184,10 +192,11 @@ class AntVgSlopeMap(bpy.types.Operator):
def execute(self, context):
- message = "Popup Values: %d, %f, %s, %s" % \
- (self.select_flat, self.select_range, self.group_name, self.z_method)
+ message = "Popup Values: %d, %f, %s, %s, %s" % \
+ (self.select_flat, self.select_range, self.group_name, self.z_method, self.weight_mode)
self.report({'INFO'}, message)
+ bpy.ops.object.mode_set(mode='OBJECT')
ob = bpy.context.active_object
dim = ob.dimensions
@@ -214,6 +223,8 @@ class AntVgSlopeMap(bpy.types.Operator):
vg_normal.name = self.group_name
+ if self.weight_mode:
+ bpy.ops.paint.weight_paint_toggle()
return {'FINISHED'}
@@ -575,7 +586,8 @@ def noise_gen(coords, props):
# Adjust height
if height_invert:
- value = (1.0 - value) * height + height_offset
+ value = 1.0 - value
+ value = value * height + height_offset
else:
value = value * height + height_offset
@@ -631,7 +643,6 @@ def noise_gen(coords, props):
return value
-
# ------------------------------------------------------------
# draw properties
@@ -827,6 +838,9 @@ def draw_ant_displace(self, context, generate=True):
box = layout.box()
box.prop(self, "show_displace_settings", toggle=True)
if self.show_displace_settings:
+ col = box.column(align=False)
+ if not generate:
+ col.prop(self, "direction", toggle=True)
col = box.column(align=True)
row = col.row(align=True).split(0.92, align=True)
row.prop(self, "height")
@@ -845,9 +859,6 @@ def draw_ant_displace(self, context, generate=True):
col.prop(self, "falloff_x")
if self.edge_falloff in ["1", "3"]:
col.prop(self, "falloff_y")
- else:
- col = box.column(align=False)
- col.prop(self, "use_vgroup", toggle=True)
col = box.column()
col.prop(self, "strata_type")
@@ -855,6 +866,10 @@ def draw_ant_displace(self, context, generate=True):
col = box.column()
col.prop(self, "strata")
+ if not generate:
+ col = box.column(align=False)
+ col.prop(self, "use_vgroup", toggle=True)
+
def draw_ant_water(self, context):
layout = self.layout
@@ -920,11 +935,418 @@ def store_properties(operator, ob):
ob.ant_landscape.water_plane = operator.water_plane
ob.ant_landscape.water_level = operator.water_level
ob.ant_landscape.use_vgroup = operator.use_vgroup
- ob.ant_landscape.show_main_settings = operator.show_main_settings
- ob.ant_landscape.show_noise_settings = operator.show_noise_settings
- ob.ant_landscape.show_displace_settings = operator.show_displace_settings
- #print("A.N.T. Landscape Object Properties:")
- #for k in ob.ant_landscape.keys():
- # print(k, "-", ob.ant_landscape[k])
+ ob.ant_landscape.remove_double = operator.remove_double
return ob
+
+# ------------------------------------------------------------
+# "name": "ErosionR"
+# "author": "Michel Anders (varkenvarken), Ian Huish (nerk)"
+
+from random import random as rand
+from math import tan, radians
+from .eroder import Grid
+#print("Imported multifiles", file=sys.stderr)
+from .stats import Stats
+from .utils import numexpr_available
+
+
+def availableVertexGroupsOrNone(self, context):
+ groups = [ ('None', 'None', 'None', 1) ]
+ return groups + [(name, name, name, n+1) for n,name in enumerate(context.active_object.vertex_groups.keys())]
+
+
+class Eroder(bpy.types.Operator):
+ bl_idname = "mesh.eroder"
+ bl_label = "ErosionR"
+ bl_description = "Apply various kinds of erosion to a landscape mesh"
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
+
+ Iterations = IntProperty(
+ name="Iterations",
+ description="Number of overall iterations",
+ default=1,
+ min=0,
+ soft_max=100
+ )
+ IterRiver = IntProperty(
+ name="River Iterations",
+ description="Number of river iterations",
+ default=30,
+ min=0,
+ soft_max=1000
+ )
+ IterAva = IntProperty(
+ name="Avalanche Iterations",
+ description="Number of avalanche iterations",
+ default=5,
+ min=0,
+ soft_max=10
+ )
+ IterDiffuse = IntProperty(
+ name="Diffuse Iterations",
+ description="Number of diffuse iterations",
+ default=5,
+ min=0,
+ soft_max=10
+ )
+
+ Ef = FloatProperty(
+ name="Rain on Plains",
+ description="1 gives equal rain across the terrain, 0 rains more at the mountain tops",
+ default=0.0,
+ min=0,
+ max=1
+ )
+ Kd = FloatProperty(
+ name="Kd",
+ description="Thermal diffusion rate (1.0 is a fairly high rate)",
+ default=0.1,
+ min=0,
+ soft_max=100
+ )
+
+ Kt = FloatProperty(
+ name="Kt",
+ description="Maximum stable talus angle",
+ default=radians(60),
+ min=0,
+ max=radians(90),
+ subtype='ANGLE'
+ )
+
+ Kr = FloatProperty(
+ name="Rain amount",
+ description="Total Rain amount",
+ default=.01,
+ min=0,
+ soft_max=1
+ )
+ Kv = FloatProperty(
+ name="Rain variance",
+ description="Rain variance (0 is constant, 1 is uniform)",
+ default=0,
+ min=0,
+ max=1
+ )
+ userainmap = BoolProperty(
+ name="Use rain map",
+ description="Use active vertex group as a rain map",
+ default=True
+ )
+
+ Ks = FloatProperty(
+ name="Soil solubility",
+ description="Soil solubility - how quickly water quickly reaches saturation point",
+ default=0.5,
+ min=0,
+ soft_max=1
+ )
+ Kdep = FloatProperty(
+ name="Deposition rate",
+ description="Sediment deposition rate - how quickly silt is laid down once water stops flowing quickly",
+ default=0.1,
+ min=0,
+ soft_max=1
+ )
+ Kz = FloatProperty(name="Fluvial Erosion Rate",
+ description="Amount of sediment moved each main iteration - if 0, then rivers are formed but the mesh is not changed",
+ default=0.3,
+ min=0,
+ soft_max=20
+ )
+ Kc = FloatProperty(
+ name="Carrying capacity",
+ description="Base sediment carrying capacity",
+ default=0.9,
+ min=0,
+ soft_max=1
+ )
+ Ka = FloatProperty(
+ name="Slope dependence",
+ description="Slope dependence of carrying capacity (not used)",
+ default=1.0,
+ min=0,
+ soft_max=2
+ )
+ Kev = FloatProperty(
+ name="Evaporation",
+ description="Evaporation Rate per grid square in % - causes sediment to be dropped closer to the hills",
+ default=.5,
+ min=0,
+ soft_max=2
+ )
+
+ numexpr = BoolProperty(
+ name="Numexpr",
+ description="Use numexpr module (if available)",
+ default=True
+ )
+
+ Pd = FloatProperty(
+ name="Diffusion Amount",
+ description="Diffusion probability",
+ default=0.2,
+ min=0,
+ max=1
+ )
+ Pa = FloatProperty(
+ name="Avalanche Amount",
+ description="Avalanche amount",
+ default=0.5,
+ min=0,
+ max=1
+ )
+ Pw = FloatProperty(
+ name="River Amount",
+ description="Water erosion probability",
+ default=1,
+ min=0,
+ max=1
+ )
+
+ smooth = BoolProperty(
+ name="Smooth",
+ description="Set smooth shading",
+ default=True
+ )
+
+ showiterstats = BoolProperty(
+ name="Iteration Stats",
+ description="Show iteraration statistics",
+ default=False
+ )
+ showmeshstats = BoolProperty(name="Mesh Stats",
+ description="Show mesh statistics",
+ default=False
+ )
+
+ stats = Stats()
+ counts= {}
+
+ # add poll function to restrict action to mesh object in object mode
+
+ def execute(self, context):
+ ob = context.active_object
+ #obwater = bpy.data.objects["water"]
+ me = ob.data
+ #mewater = obwater.data
+ self.stats.reset()
+ try:
+ vgActive = ob.vertex_groups.active.name
+ except:
+ vgActive = "capacity"
+ print("ActiveGroup", vgActive)
+ try:
+ vg=ob.vertex_groups["rainmap"]
+ except:
+ vg=ob.vertex_groups.new("rainmap")
+ try:
+ vgscree=ob.vertex_groups["scree"]
+ except:
+ vgscree=ob.vertex_groups.new("scree")
+ try:
+ vgavalanced=ob.vertex_groups["avalanced"]
+ except:
+ vgavalanced=ob.vertex_groups.new("avalanced")
+ try:
+ vgw=ob.vertex_groups["water"]
+ except:
+ vgw=ob.vertex_groups.new("water")
+ try:
+ vgscour=ob.vertex_groups["scour"]
+ except:
+ vgscour=ob.vertex_groups.new("scour")
+ try:
+ vgdeposit=ob.vertex_groups["deposit"]
+ except:
+ vgdeposit=ob.vertex_groups.new("deposit")
+ try:
+ vgflowrate=ob.vertex_groups["flowrate"]
+ except:
+ vgflowrate=ob.vertex_groups.new("flowrate")
+ try:
+ vgsediment=ob.vertex_groups["sediment"]
+ except:
+ vgsediment=ob.vertex_groups.new("sediment")
+ try:
+ vgsedimentpct=ob.vertex_groups["sedimentpct"]
+ except:
+ vgsedimentpct=ob.vertex_groups.new("sedimentpct")
+ try:
+ vgcapacity=ob.vertex_groups["capacity"]
+ except:
+ vgcapacity=ob.vertex_groups.new("capacity")
+ g = Grid.fromBlenderMesh(me, vg, self.Ef)
+
+ me = bpy.data.meshes.new(me.name)
+ #mewater = bpy.data.meshes.new(mewater.name)
+
+ self.counts['diffuse']=0
+ self.counts['avalanche']=0
+ self.counts['water']=0
+ for i in range(self.Iterations):
+ if self.IterRiver > 0:
+ for i in range(self.IterRiver):
+ g.rivergeneration(self.Kr, self.Kv, self.userainmap, self.Kc, self.Ks, self.Kdep, self.Ka, self.Kev/100, 0,0,0,0, self.numexpr)
+
+ if self.Kd > 0.0:
+ for k in range(self.IterDiffuse):
+ g.diffuse(self.Kd / 5, self.IterDiffuse, self.numexpr)
+ self.counts['diffuse']+=1
+ #if self.Kt < radians(90) and rand() < self.Pa:
+ if self.Kt < radians(90) and self.Pa > 0:
+ for k in range(self.IterAva):
+ # since dx and dy are scaled to 1, tan(Kt) is the height for a given angle
+ g.avalanche(tan(self.Kt), self.IterAva, self.Pa, self.numexpr)
+ self.counts['avalanche']+=1
+ if self.Kz > 0:
+ g.fluvial_erosion(self.Kr, self.Kv, self.userainmap, self.Kc, self.Ks, self.Kz*50, self.Ka, 0,0,0,0, self.numexpr)
+ self.counts['water']+=1
+
+ g.toBlenderMesh(me)
+ ob.data = me
+ #g.toWaterMesh(mewater)
+ #obwater.data = mewater
+ if vg:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ vg.add([i],g.rainmap[row,col],'ADD')
+ if vgscree:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ vgscree.add([i],g.avalanced[row,col],'ADD')
+ if vgavalanced:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ vgavalanced.add([i],-g.avalanced[row,col],'ADD')
+ if vgw:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ vgw.add([i],g.water[row,col]/g.watermax,'ADD')
+ # vgw.add([i],g.water[row,col],'ADD')
+ if vgscour:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ # vgscour.add([i],(g.scour[row,col]-g.scourmin)/(g.scourmax-g.scourmin),'ADD')
+ vgscour.add([i],g.scour[row,col]/max(g.scourmax, -g.scourmin),'ADD')
+ if vgdeposit:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ vgdeposit.add([i],g.scour[row,col]/min(-g.scourmax, g.scourmin),'ADD')
+ if vgflowrate:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ # vgflowrate.add([i],g.flowrate[row,col]/g.flowratemax,'ADD')
+ vgflowrate.add([i],g.flowrate[row,col],'ADD')
+ if vgsediment:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ # vgsediment.add([i],g.sediment[row,col]/g.sedmax,'ADD')
+ vgsediment.add([i],g.sediment[row,col],'ADD')
+ if vgsedimentpct:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ vgsedimentpct.add([i],g.sedimentpct[row,col],'ADD')
+ if vgcapacity:
+ for row in range(g.rainmap.shape[0]):
+ for col in range(g.rainmap.shape[1]):
+ i = row * g.rainmap.shape[1] + col
+ vgcapacity.add([i],g.capacity[row,col],'ADD')
+ try:
+ vg = ob.vertex_groups["vgActive"]
+ except:
+ vg = vgcapacity
+ ob.vertex_groups.active = vg
+
+ if self.smooth:
+ bpy.ops.object.shade_smooth()
+ self.stats.time()
+ self.stats.memory()
+ if self.showmeshstats:
+ self.stats.meshstats = g.analyze()
+
+ return {'FINISHED'}
+
+ def draw(self,context):
+ layout = self.layout
+
+ layout.operator('screen.repeat_last', text="Repeat", icon='FILE_REFRESH' )
+
+ layout.prop(self, 'Iterations')
+
+ box = layout.box()
+ col = box.column(align=True)
+ col.label("Thermal (Diffusion)")
+ col.prop(self, 'Kd')
+ col.prop(self, 'IterDiffuse')
+
+ box = layout.box()
+ col = box.column(align=True)
+ col.label("Avalanche (Talus)")
+ col.prop(self, 'Pa')
+ col.prop(self, 'IterAva')
+ col.prop(self, 'Kt')
+
+ box = layout.box()
+ col = box.column(align=True)
+ col.label("River erosion")
+ col.prop(self, 'IterRiver')
+ col.prop(self, 'Kz')
+ col.prop(self, 'Ks')
+ col.prop(self, 'Kc')
+ col.prop(self, 'Kdep')
+ col.prop(self, 'Kr')
+ col.prop(self, 'Kv')
+ col.prop(self, 'Kev')
+ #box2 = box.box()
+ #box2.prop(self, 'userainmap')
+ #box2.enabled = context.active_object.vertex_groups.active is not None
+ #box.prop(self, 'Ka')
+ col.prop(self, 'Ef')
+
+ #box = layout.box()
+ #box.label("Probabilities")
+ #box.prop(self, 'Pa')
+ #box.prop(self, 'Pw')
+
+ layout.prop(self,'smooth')
+
+ #if numexpr_available:
+ # layout.prop(self, 'numexpr')
+ #else:
+ # box = layout.box()
+ # box.alert=True
+ # box.label("Numexpr not available. Will slow down large meshes")
+
+ #box = layout.box()
+ #box.prop(self,'showiterstats')
+ #if self.showiterstats:
+ # row = box.row()
+ # col1 = row.column()
+ # col2 = row.column()
+ # col1.label("Time"); col2.label("%.1f s"%self.stats.elapsedtime)
+ # if self.stats.memstats_available:
+ # col1.label("Memory"); col2.label("%.1f Mb"%(self.stats.maxmem/(1024.0*1024.0)))
+ # col1.label("Diffusions"); col2.label("%d"% self.counts['diffuse'])
+ # col1.label("Avalanches"); col2.label("%d"% self.counts['avalanche'])
+ # col1.label("Water movements"); col2.label("%d"% self.counts['water'])
+ #box = layout.box()
+ #box.prop(self,'showmeshstats')
+ #if self.showmeshstats:
+ # row = box.row()
+ # col1 = row.column()
+ # col2 = row.column()
+ # for line in self.stats.meshstats.split('\n'):
+ # label, value = line.split(':')
+ # col1.label(label)
+ # col2.label(value)
diff --git a/ant_landscape/ant_landscape_refresh.py b/ant_landscape/ant_landscape_refresh.py
index ae3710de..5518f2db 100644
--- a/ant_landscape/ant_landscape_refresh.py
+++ b/ant_landscape/ant_landscape_refresh.py
@@ -58,9 +58,9 @@ class AntLandscapeRefresh(bpy.types.Operator):
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.object.mode_set(mode = 'OBJECT')
-
if obj and obj.ant_landscape.keys():
- obi = obj.ant_landscape.items()
+ ob = obj.ant_landscape
+ obi = ob.items()
#print("Refresh A.N.T. Landscape Grid")
#for k in obi.keys():
# print(k, "-", obi[k])
@@ -70,9 +70,17 @@ class AntLandscapeRefresh(bpy.types.Operator):
# redraw verts
mesh = obj.data
- for v in mesh.vertices:
- v.co[2] = 0
- v.co[2] = noise_gen(v.co, prop)
+
+ if ob['use_vgroup']:
+ vertex_group = obj.vertex_groups.active
+ if vertex_group:
+ for v in mesh.vertices:
+ v.co[2] = 0
+ v.co[2] = vertex_group.weight(v.index) * noise_gen(v.co, prop)
+ else:
+ for v in mesh.vertices:
+ v.co[2] = 0
+ v.co[2] = noise_gen(v.co, prop)
mesh.update()
else:
pass
@@ -120,26 +128,31 @@ class AntLandscapeRegenerate(bpy.types.Operator):
new_name = ob.ant_terrain_name
# Main function, create landscape mesh object
- if ob["sphere_mesh"]:
+ if ob['sphere_mesh']:
# sphere
verts, faces = sphere_gen(
- ob["subdivision_y"],
- ob["subdivision_x"],
- ob["tri_face"],
- ob["mesh_size"],
+ ob['subdivision_y'],
+ ob['subdivision_x'],
+ ob['tri_face'],
+ ob['mesh_size'],
ant_props,
False,
0.0
)
new_ob = create_mesh_object(context, verts, [], faces, new_name).object
+ if ob['remove_double']:
+ new_ob.select = True
+ bpy.ops.object.mode_set(mode = 'EDIT')
+ bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
+ bpy.ops.object.mode_set(mode = 'OBJECT')
else:
# grid
verts, faces = grid_gen(
- ob["subdivision_x"],
- ob["subdivision_y"],
- ob["tri_face"],
- ob["mesh_size_x"],
- ob["mesh_size_y"],
+ ob['subdivision_x'],
+ ob['subdivision_y'],
+ ob['tri_face'],
+ ob['mesh_size_x'],
+ ob['mesh_size_y'],
ant_props,
False,
0.0
@@ -148,54 +161,59 @@ class AntLandscapeRegenerate(bpy.types.Operator):
new_ob.select = True
- if ob["smooth_mesh"]:
+ if ob['smooth_mesh']:
bpy.ops.object.shade_smooth()
# Landscape Material
- if ob["land_material"] != "" and ob["land_material"] in bpy.data.materials:
- mat = bpy.data.materials[ob["land_material"]]
+ if ob['land_material'] != "" and ob['land_material'] in bpy.data.materials:
+ mat = bpy.data.materials[ob['land_material']]
bpy.context.object.data.materials.append(mat)
# Water plane
- if ob["water_plane"]:
- if ob["sphere_mesh"]:
+ if ob['water_plane']:
+ if ob['sphere_mesh']:
# sphere
verts, faces = sphere_gen(
- ob["subdivision_y"],
- ob["subdivision_x"],
- ob["tri_face"],
- ob["mesh_size"],
+ ob['subdivision_y'],
+ ob['subdivision_x'],
+ ob['tri_face'],
+ ob['mesh_size'],
ant_props,
- ob["water_plane"],
- ob["water_level"]
+ ob['water_plane'],
+ ob['water_level']
)
wobj = create_mesh_object(context, verts, [], faces, new_name+"_plane").object
+ if ob['remove_double']:
+ wobj.select = True
+ bpy.ops.object.mode_set(mode = 'EDIT')
+ bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
+ bpy.ops.object.mode_set(mode = 'OBJECT')
else:
# grid
verts, faces = grid_gen(
2,
2,
- ob["tri_face"],
- ob["mesh_size_x"],
- ob["mesh_size_y"],
+ ob['tri_face'],
+ ob['mesh_size_x'],
+ ob['mesh_size_y'],
ant_props,
- ob["water_plane"],
- ob["water_level"]
+ ob['water_plane'],
+ ob['water_level']
)
wobj = create_mesh_object(context, verts, [], faces, new_name+"_plane").object
wobj.select = True
- if ob["smooth_mesh"]:
+ if ob['smooth_mesh']:
bpy.ops.object.shade_smooth()
# Water Material
- if ob["water_material"] != "" and ob["water_material"] in bpy.data.materials:
- mat = bpy.data.materials[ob["water_material"]]
+ if ob['water_material'] != "" and ob['water_material'] in bpy.data.materials:
+ mat = bpy.data.materials[ob['water_material']]
bpy.context.object.data.materials.append(mat)
# Loc Rot Scale
- if ob["water_plane"]:
+ if ob['water_plane']:
wobj.location = obj.location
wobj.rotation_euler = obj.rotation_euler
wobj.scale = obj.scale
@@ -205,7 +223,7 @@ class AntLandscapeRegenerate(bpy.types.Operator):
new_ob.location = obj.location
new_ob.rotation_euler = obj.rotation_euler
new_ob.scale = obj.scale
-
+
# Store props
new_ob = store_properties(ob, new_ob)
@@ -225,19 +243,3 @@ class AntLandscapeRegenerate(bpy.types.Operator):
context.user_preferences.edit.use_global_undo = undo
return {'FINISHED'}
-
-'''
-# ------------------------------------------------------------
-# Register:
-
-def register():
- bpy.utils.register_module(__name__)
-
-
-def unregister():
- bpy.utils.unregister_module(__name__)
-
-
-if __name__ == "__main__":
- register()
-''' \ No newline at end of file
diff --git a/ant_landscape/eroder.py b/ant_landscape/eroder.py
new file mode 100644
index 00000000..0c7e2edb
--- /dev/null
+++ b/ant_landscape/eroder.py
@@ -0,0 +1,652 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# erode.py -- a script to simulate erosion of height fields
+# (c) 2014 Michel J. Anders (varkenvarken)
+# now with some modifications by Ian Huish (nerk)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+from time import time
+import unittest
+import sys
+import os
+# import resource # so much for platform independence, this only works on unix :-(
+from random import random as rand, shuffle
+import numpy as np
+#from .perlin import pnoise
+
+numexpr_available = False
+# Sorry, nerk can't handle numexpr at this time!
+#try:
+# import numexpr as ne
+# numexpr_available = True
+#except ImportError:
+# pass
+
+def getmemsize():
+ return 0.0
+ #return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss*resource.getpagesize()/(1024.0*1024.0)
+
+def getptime():
+ #r = resource.getrusage(resource.RUSAGE_SELF)
+ #return r.ru_utime + r.ru_stime
+ return time()
+
+class Grid:
+
+ def __init__(self, size=10, dtype=np.single):
+ self.center = np.zeros([size,size], dtype)
+ #print("Centre\n", np.array_str(self.center,precision=3), file=sys.stderr)
+ self.water = None
+ self.sediment = None
+ self.scour = None
+ self.flowrate = None
+ self.sedimentpct = None
+ self.sedimentpct = None
+ self.capacity = None
+ self.avalanced = None
+ self.minx=None
+ self.miny=None
+ self.maxx=None
+ self.maxy=None
+ self.zscale=1
+ self.maxrss=0.0
+ self.sequence=[0,1,2,3]
+ self.watermax = 1.0
+ self.flowratemax = 1.0
+ self.scourmax = 1.0
+ self.sedmax = 1.0
+ self.scourmin = 1.0
+
+ def init_water_and_sediment(self):
+ if self.water is None:
+ self.water = np.zeros(self.center.shape, dtype=np.single)
+ if self.sediment is None:
+ self.sediment = np.zeros(self.center.shape, dtype=np.single)
+ if self.scour is None:
+ self.scour = np.zeros(self.center.shape, dtype=np.single)
+ if self.flowrate is None:
+ self.flowrate = np.zeros(self.center.shape, dtype=np.single)
+ if self.sedimentpct is None:
+ self.sedimentpct = np.zeros(self.center.shape, dtype=np.single)
+ if self.capacity is None:
+ self.capacity = np.zeros(self.center.shape, dtype=np.single)
+ if self.avalanced is None:
+ self.avalanced = np.zeros(self.center.shape, dtype=np.single)
+
+ def __str__(self):
+ return ''.join(self.__str_iter__(fmt="%.3f"))
+
+ def __str_iter__(self, fmt):
+ for row in self.center[::]:
+ values=[]
+ for v in row:
+ values.append(fmt%v)
+ yield ' '.join(values) + '\n'
+
+ @staticmethod
+ def fromFile(filename):
+ if filename == '-' : filename = sys.stdin
+ g=Grid()
+ g.center=np.loadtxt(filename,np.single)
+ return g
+
+ def toFile(self, filename, fmt="%.3f"):
+ if filename == '-' :
+ filename = sys.stdout.fileno()
+ with open(filename,"w") as f:
+ for line in self.__str_iter__(fmt):
+ f.write(line)
+
+ def raw(self,format="%.3f"):
+ fstr=format+" "+ format+" "+ format+" "
+ a=self.center / self.zscale
+ minx=0.0 if self.minx is None else self.minx
+ miny=0.0 if self.miny is None else self.miny
+ maxx=1.0 if self.maxx is None else self.maxx
+ maxy=1.0 if self.maxy is None else self.maxy
+ dx=(maxx-minx)/(a.shape[0]-1)
+ dy=(maxy-miny)/(a.shape[1]-1)
+ for row in range(a.shape[0]-1):
+ row0=miny+row*dy
+ row1=row0+dy
+ for col in range(a.shape[1]-1):
+ col0=minx+col*dx
+ col1=col0+dx
+ yield (fstr%(row0 ,col0 ,a[row ][col ])+
+ fstr%(row0 ,col1 ,a[row ][col+1])+
+ fstr%(row1 ,col0 ,a[row+1][col ])+"\n")
+ yield (fstr%(row0 ,col1 ,a[row ][col+1])+
+ fstr%(row1 ,col0 ,a[row+1][col ])+
+ fstr%(row1 ,col1 ,a[row+1][col+1])+"\n")
+
+ def toRaw(self, filename, infomap=None):
+ with open(filename if type(filename) == str else sys.stdout.fileno() , "w") as f:
+ f.writelines(self.raw())
+ if infomap:
+ with open(os.path.splitext(filename)[0]+".inf" if type(filename) == str else sys.stdout.fileno() , "w") as f:
+ f.writelines("\n".join("%-15s: %s"%t for t in sorted(infomap.items())))
+
+ @staticmethod
+ def fromRaw(filename):
+ """initialize a grid from a Blender .raw file.
+ currenly suports just rectangular grids of all triangles
+ """
+ g=Grid.fromFile(filename)
+ # we assume tris and an axis aligned grid
+ g.center=np.reshape(g.center,(-1,3))
+ g._sort()
+ return g
+
+ def _sort(self, expfact):
+ # keep unique vertices only by creating a set and sort first on x then on y coordinate
+ # using rather slow python sort but couldn;t wrap my head around np.lexsort
+ verts = sorted(list({ tuple(t) for t in self.center[::] }))
+ x=set(c[0] for c in verts)
+ y=set(c[1] for c in verts)
+ nx=len(x)
+ ny=len(y)
+ self.minx=min(x)
+ self.maxx=max(x)
+ self.miny=min(y)
+ self.maxy=max(y)
+ xscale=(self.maxx-self.minx)/(nx-1)
+ yscale=(self.maxy-self.miny)/(ny-1)
+ # note: a purely flat plane cannot be scaled
+ if (yscale != 0.0) and (abs(xscale/yscale) - 1.0 > 1e-3) : raise ValueError("Mesh spacing not square %d x %d %.4f x %4.f"%(nx,ny,xscale,yscale))
+ self.zscale=1.0
+ if abs(yscale) > 1e-6 :
+ self.zscale=1.0/yscale
+
+ # keep just the z-values and null any ofsset
+ # we might catch a reshape error that will occur if nx*ny != # of vertices (if we are not dealing with a heightfield but with a mesh with duplicate x,y coords, like an axis aligned cube
+ self.center=np.array([c[2] for c in verts],dtype=np.single).reshape(nx,ny)
+ self.center=(self.center-np.amin(self.center))*self.zscale
+ if self.rainmap is not None:
+ #rainmap = sorted(list({ tuple(t) for t in self.rainmap[::] }))
+ #self.rainmap=np.array([c[2] for c in rainmap],dtype=np.single).reshape(nx,ny)
+ rmscale = np.max(self.center)
+ #self.rainmap = (self.center/rmscale) * np.exp(expfact*((self.center/rmscale)-1))
+ self.rainmap = expfact + (1-expfact)*(self.center/rmscale)
+
+ @staticmethod
+ def fromBlenderMesh(me, vg, expfact):
+ g=Grid()
+ g.center=np.asarray(list(tuple(v.co) for v in me.vertices), dtype=np.single )
+ g.rainmap=None
+ print("VertexGroup\n",vg, file=sys.stderr)
+ if vg is not None:
+ for v in me.vertices:
+ vg.add([v.index],0.0,'ADD')
+ g.rainmap=np.asarray(list( (v.co[0], v.co[1], vg.weight(v.index)) for v in me.vertices), dtype=np.single )
+ g._sort(expfact)
+ #print("CentreMesh\n", np.array_str(g.center,precision=3), file=sys.stderr)
+ #print('rainmap',np.max(g.rainmap),np.min(g.rainmap))
+ return g
+
+# def rainmapcolor(me, vg):
+# if vg is not None:
+# for v in me.vertices:
+
+
+
+ def setrainmap(self, rainmap):
+ self.rainmap = rainmap
+
+ def _verts(self, surface):
+ a=surface / self.zscale
+ minx=0.0 if self.minx is None else self.minx
+ miny=0.0 if self.miny is None else self.miny
+ maxx=1.0 if self.maxx is None else self.maxx
+ maxy=1.0 if self.maxy is None else self.maxy
+ dx=(maxx-minx)/(a.shape[0]-1)
+ dy=(maxy-miny)/(a.shape[1]-1)
+ for row in range(a.shape[0]):
+ row0=miny+row*dy
+ for col in range(a.shape[1]):
+ col0=minx+col*dx
+ yield (row0 ,col0 ,a[row ][col ])
+
+ def _faces(self):
+ nrow, ncol = self.center.shape
+ for row in range(nrow-1):
+ for col in range(ncol-1):
+ vi = row * ncol + col
+ yield (vi, vi+ncol, vi+1)
+ yield (vi+1, vi+ncol, vi+ncol+1)
+
+ def toBlenderMesh(self, me): # pass me as argument so that we don't need to import bpy and create a dependency
+ # the docs state that from_pydata takes iterators as arguments but it will fail with generators because it does len(arg)
+ me.from_pydata(list(self._verts(self.center)),[],list(self._faces()))
+
+ def toWaterMesh(self, me): # pass me as argument so that we don't need to import bpy and create a dependency
+ # the docs state that from_pydata takes iterators as arguments but it will fail with generators because it does len(arg)
+ me.from_pydata(list(self._verts(self.water)),[],list(self._faces()))
+
+ def peak(self, value=1):
+ nx,ny = self.center.shape
+ self.center[int(nx/2),int(ny/2)] += value
+
+ def shelf(self, value=1):
+ nx,ny = self.center.shape
+ self.center[:nx/2] += value
+
+ def mesa(self, value=1):
+ nx,ny = self.center.shape
+ self.center[nx/4:3*nx/4,ny/4:3*ny/4] += value
+
+ def random(self, value=1):
+ self.center += np.random.random_sample(self.center.shape)*value
+
+ def neighborgrid(self):
+ self.up=np.roll(self.center,-1,0)
+ self.down=np.roll(self.center,1,0)
+ self.left=np.roll(self.center,-1,1)
+ self.right=np.roll(self.center,1,1)
+
+ def zeroedge(self, quantity=None):
+ c = self.center if quantity is None else quantity
+ c[0,:]=0
+ c[-1,:]=0
+ c[:,0]=0
+ c[:,-1]=0
+
+ def diffuse(self, Kd, IterDiffuse, numexpr):
+ self.zeroedge()
+ c = self.center[1:-1,1:-1]
+ up = self.center[ :-2,1:-1]
+ down = self.center[2: ,1:-1]
+ left = self.center[1:-1, :-2]
+ right = self.center[1:-1,2: ]
+ if(numexpr and numexpr_available):
+ self.center[1:-1,1:-1] = ne.evaluate('c + Kd * (up + down + left + right - 4.0 * c)')
+ else:
+ self.center[1:-1,1:-1] = c + (Kd/IterDiffuse) * (up + down + left + right - 4.0 * c)
+ print("diffuse: ", Kd)
+ self.maxrss = max(getmemsize(), self.maxrss)
+ return self.center
+
+ def avalanche(self, delta, iterava, prob, numexpr):
+ self.zeroedge()
+ #print(self.center)
+
+ c = self.center[1:-1,1:-1]
+ up = self.center[ :-2,1:-1]
+ down = self.center[2: ,1:-1]
+ left = self.center[1:-1, :-2]
+ right = self.center[1:-1,2: ]
+ where = np.where
+
+ if(numexpr and numexpr_available):
+ self.center[1:-1,1:-1] = ne.evaluate('c + where((up -c) > delta ,(up -c -delta)/2, 0) \
+ + where((down -c) > delta ,(down -c -delta)/2, 0) \
+ + where((left -c) > delta ,(left -c -delta)/2, 0) \
+ + where((right-c) > delta ,(right-c -delta)/2, 0) \
+ + where((up -c) < -delta,(up -c +delta)/2, 0) \
+ + where((down -c) < -delta,(down -c +delta)/2, 0) \
+ + where((left -c) < -delta,(left -c +delta)/2, 0) \
+ + where((right-c) < -delta,(right-c +delta)/2, 0)')
+ else:
+ sa = (
+ # incoming
+ where((up -c) > delta ,(up -c -delta)/2, 0)
+ + where((down -c) > delta ,(down -c -delta)/2, 0)
+ + where((left -c) > delta ,(left -c -delta)/2, 0)
+ + where((right-c) > delta ,(right-c -delta)/2, 0)
+ # outgoing
+ + where((up -c) < -delta,(up -c +delta)/2, 0)
+ + where((down -c) < -delta,(down -c +delta)/2, 0)
+ + where((left -c) < -delta,(left -c +delta)/2, 0)
+ + where((right-c) < -delta,(right-c +delta)/2, 0)
+ )
+ randarray = np.random.randint(0,100,sa.shape) *0.01
+ sa = where(randarray < prob, sa, 0)
+ self.avalanced[1:-1,1:-1] = self.avalanced[1:-1,1:-1] + sa/iterava
+ self.center[1:-1,1:-1] = c + sa/iterava
+
+ #print(self.center)
+ self.maxrss = max(getmemsize(), self.maxrss)
+ return self.center
+
+ def rain(self, amount=1, variance=0, userainmap=False):
+ self.water += (1.0 - np.random.random(self.water.shape) * variance) * (amount if ((self.rainmap is None) or (not userainmap)) else self.rainmap * amount)
+
+ def spring(self, amount, px, py, radius): # px, py and radius are all fractions
+ nx, ny = self.center.shape
+ rx = max(int(nx*radius),1)
+ ry = max(int(ny*radius),1)
+ px = int(nx*px)
+ py = int(ny*py)
+ self.water[px-rx:px+rx+1,py-ry:py+ry+1] += amount
+
+ def river(self, Kc, Ks, Kdep, Ka, Kev, numexpr):
+
+ zeros = np.zeros
+ where = np.where
+ min = np.minimum
+ max = np.maximum
+ abs = np.absolute
+ arctan = np.arctan
+ sin = np.sin
+
+ center = (slice( 1, -1,None),slice( 1, -1,None))
+ #print("CentreSlice\n", np.array_str(center,precision=3), file=sys.stderr)
+ up = (slice(None, -2,None),slice( 1, -1,None))
+ down = (slice( 2, None,None),slice( 1, -1,None))
+ left = (slice( 1, -1,None),slice(None, -2,None))
+ right = (slice( 1, -1,None),slice( 2,None,None))
+
+ water = self.water
+ rock = self.center
+ sediment = self.sediment
+ height = rock + water
+ sc = where(water>0, sediment/water, 0) ##!! this gives a runtime warning for division by zero
+ sdw = zeros(water[center].shape)
+ svdw = zeros(water[center].shape)
+ sds = zeros(water[center].shape)
+ angle = zeros(water[center].shape)
+ #print(water[center])
+ for d in (up,down,left,right):
+ if(numexpr and numexpr_available):
+ hdd = height[d]
+ hcc = height[center]
+ dw = ne.evaluate('hdd-hcc')
+ inflow = ne.evaluate('dw > 0')
+ wdd = water[d]
+ wcc = water[center]
+ dw = ne.evaluate('where(inflow, where(wdd<dw, wdd, dw), where(-wcc>dw, -wcc, dw))/4.0') # nested where() represent min() and max()
+ sdw = ne.evaluate('sdw + dw')
+ scd = sc[d]
+ scc = sc[center]
+ rockd= rock[d]
+ rockc= rock[center]
+ sds = ne.evaluate('sds + dw * where(inflow, scd, scc)')
+ svdw = ne.evaluate('svdw + abs(dw)')
+ angle= ne.evaluate('angle + arctan(abs(rockd-rockc))')
+ else:
+ dw = (height[d]-height[center])
+ inflow = dw > 0
+ dw = where(inflow, min(water[d], dw), max(-water[center], dw))/4.0
+ sdw = sdw + dw
+ sds = sds + dw * where(inflow, sc[d], sc[center])
+ svdw = svdw + abs(dw)
+ angle= angle + np.arctan(abs(rock[d]-rock[center]))
+
+ if(numexpr and numexpr_available):
+ wcc = water[center]
+ scc = sediment[center]
+ rcc = rock[center]
+ water[center] = ne.evaluate('wcc + sdw')
+ sediment[center] = ne.evaluate('scc + sds')
+ sc = ne.evaluate('where(wcc>0, scc/wcc, 2000*Kc)')
+ fKc = ne.evaluate('Kc*sin(Ka*angle)*svdw')
+ ds = ne.evaluate('where(sc > fKc, -Kd * scc, Ks * svdw)')
+ rock[center] = ne.evaluate('rcc - ds')
+ rock[center] = ne.evaluate('where(rcc<0,0,rcc)') # there isn't really a bottom to the rock but negative values look ugly
+ sediment[center] = ne.evaluate('scc + ds')
+ else:
+ wcc = water[center]
+ scc = sediment[center]
+ rcc = rock[center]
+ water[center] = wcc * (1-Kev) + sdw
+ sediment[center] = scc + sds
+ sc = where(wcc>0, scc/wcc, 2*Kc)
+ fKc = Kc*svdw
+ #fKc = Kc*np.sin(Ka*angle)*svdw*wcc
+ #ds = where(sc > fKc, -Kd * scc, Ks * svdw)
+ ds = where(fKc>sc,(fKc-sc)*Ks,(fKc-sc)*Kdep)*wcc
+ self.flowrate[center] = svdw
+ self.scour[center] = ds
+ self.sedimentpct[center] = sc
+ self.capacity[center] = fKc
+ #rock[center] = rcc - ds
+ #rock[center] = where(rcc<0,0,rcc) # there isn't really a bottom to the rock but negative values look ugly
+ sediment[center] = scc + ds + sds
+ #print("sdw", sdw[10,15])
+
+ def flow(self, Kc, Ks, Kz, Ka, numexpr):
+
+ zeros = np.zeros
+ where = np.where
+ min = np.minimum
+ max = np.maximum
+ abs = np.absolute
+ arctan = np.arctan
+ sin = np.sin
+
+ center = (slice( 1, -1,None),slice( 1, -1,None))
+ #print("CentreSlice\n", np.array_str(center,precision=3), file=sys.stderr)
+ #up = (slice(None, -2,None),slice( 1, -1,None))
+ #down = (slice( 2, None,None),slice( 1, -1,None))
+ #left = (slice( 1, -1,None),slice(None, -2,None))
+ #right = (slice( 1, -1,None),slice( 2,None,None))
+
+ #water = self.water
+ rock = self.center
+ #sediment = self.sediment
+ #height = rock + water
+ #sc = where(water>0, sediment/water, 0) ##!! this gives a runtime warning for division by zero
+ #sdw = zeros(water[center].shape)
+ #svdw = zeros(water[center].shape)
+ #sds = zeros(water[center].shape)
+ #angle = zeros(water[center].shape)
+ #print(height[center])
+ #print(water[center])
+ #for d in (up,down,left,right):
+ #if(numexpr and numexpr_available):
+ #hdd = height[d]
+ #hcc = height[center]
+ #dw = ne.evaluate('hdd-hcc')
+ #inflow = ne.evaluate('dw > 0')
+ #wdd = water[d]
+ #wcc = water[center]
+ #dw = ne.evaluate('where(inflow, where(wdd<dw, wdd, dw), where(-wcc>dw, -wcc, dw))/4.0') # nested where() represent min() and max()
+ #sdw = ne.evaluate('sdw + dw')
+ #scd = sc[d]
+ #scc = sc[center]
+ #rockd= rock[d]
+ #rockc= rock[center]
+ #sds = ne.evaluate('sds + dw * where(inflow, scd, scc)')
+ #svdw = ne.evaluate('svdw + abs(dw)')
+ #angle= ne.evaluate('angle + arctan(abs(rockd-rockc))')
+ #else:
+ #dw = (height[d]-height[center])
+ #inflow = dw > 0
+ #dw = where(inflow, min(water[d], dw), max(-water[center], dw))/4.0
+ #sdw = sdw + dw
+ #sds = sds + dw * where(inflow, sc[d], sc[center])
+ #svdw = svdw + abs(dw)
+ #angle= angle + np.arctan(abs(rock[d]-rock[center]))
+
+ #if(numexpr and numexpr_available):
+ #wcc = water[center]
+ #scc = sediment[center]
+ #rcc = rock[center]
+ #water[center] = ne.evaluate('wcc + sdw')
+ #sediment[center] = ne.evaluate('scc + sds')
+ #sc = ne.evaluate('where(wcc>0, scc/wcc, 2000*Kc)')
+ #fKc = ne.evaluate('Kc*sin(Ka*angle)*svdw')
+ #ds = ne.evaluate('where(sc > fKc, -Kd * scc, Ks * svdw)')
+ #rock[center] = ne.evaluate('rcc - ds')
+ #rock[center] = ne.evaluate('where(rcc<0,0,rcc)') # there isn't really a bottom to the rock but negative values look ugly
+ #sediment[center] = ne.evaluate('scc + ds')
+ #else:
+ #wcc = water[center]
+ #scc = sediment[center]
+ ds = self.scour[center]
+ rcc = rock[center]
+ #water[center] = wcc + sdw
+ #sediment[center] = scc + sds
+ #sc = where(wcc>0, scc/wcc, 2*Kc)
+ #fKc = Kc*np.sin(Ka*angle)*svdw
+ #ds = where(sc > fKc, -Kd * scc, Ks * svdw)
+ rock[center] = rcc - ds * Kz
+ rock[center] = where(rcc<0,0,rcc) # there isn't really a bottom to the rock but negative values look ugly
+ #sediment[center] = scc + ds
+
+ def rivergeneration(self, rainamount, rainvariance, userainmap, Kc, Ks, Kdep, Ka, Kev, Kspring, Kspringx, Kspringy, Kspringr, numexpr):
+ self.init_water_and_sediment()
+ self.rain(rainamount, rainvariance, userainmap)
+ self.zeroedge(self.water)
+ self.zeroedge(self.sediment)
+ #self.spring(Kspring, Kspringx, Kspringy, Kspringr)
+ self.river(Kc, Ks, Kdep, Ka, Kev, numexpr)
+ self.watermax = np.max(self.water)
+
+ def fluvial_erosion(self, rainamount, rainvariance, userainmap, Kc, Ks, Kdep, Ka, Kspring, Kspringx, Kspringy, Kspringr, numexpr):
+ #self.init_water_and_sediment()
+ #self.rain(rainamount, rainvariance, userainmap)
+ #self.zeroedge(self.water)
+ #self.zeroedge(self.sediment)
+ #self.spring(Kspring, Kspringx, Kspringy, Kspringr)
+ self.flow(Kc, Ks, Kdep, Ka, numexpr)
+ self.flowratemax = np.max(self.flowrate)
+ self.scourmax = np.max(self.scour)
+ self.scourmin = np.min(self.scour)
+ self.sedmax = np.max(self.sediment)
+ print("DSMinMax", np.min(self.scour), np.max(self.scour))
+
+ def analyze(self):
+ self.neighborgrid()
+ # just looking at up and left to avoid needless doubel calculations
+ slopes=np.concatenate((np.abs(self.left - self.center),np.abs(self.up - self.center)))
+ return '\n'.join(["%-15s: %.3f"%t for t in [
+ ('height average', np.average(self.center)),
+ ('height median', np.median(self.center)),
+ ('height max', np.max(self.center)),
+ ('height min', np.min(self.center)),
+ ('height std', np.std(self.center)),
+ ('slope average', np.average(slopes)),
+ ('slope median', np.median(slopes)),
+ ('slope max', np.max(slopes)),
+ ('slope min', np.min(slopes)),
+ ('slope std', np.std(slopes))
+ ]]
+ )
+
+class TestGrid(unittest.TestCase):
+
+ def test_diffuse(self):
+ g=Grid(5)
+ g.peak(1)
+ self.assertEqual(g.center[2,2],1.0)
+ g.diffuse(0.1, numexpr=False)
+ for n in [(2,1),(2,3),(1,2),(3,2)]:
+ self.assertAlmostEqual(g.center[n],0.1)
+ self.assertAlmostEqual(g.center[2,2],0.6)
+
+ def test_diffuse_numexpr(self):
+ g=Grid(5)
+ g.peak(1)
+ g.diffuse(0.1, numexpr=False)
+ h=Grid(5)
+ h.peak(1)
+ h.diffuse(0.1, numexpr=True)
+ self.assertEqual(list(g.center.flat),list(h.center.flat))
+
+ def test_avalanche_numexpr(self):
+ g=Grid(5)
+ g.peak(1)
+ g.avalanche(0.1, numexpr=False)
+ h=Grid(5)
+ h.peak(1)
+ h.avalanche(0.1, numexpr=True)
+ print(g)
+ print(h)
+ np.testing.assert_almost_equal(g.center,h.center)
+
+if __name__ == "__main__":
+
+ import argparse
+
+ parser = argparse.ArgumentParser(description='Erode a terrain while assuming zero boundary conditions.')
+ parser.add_argument('-I', dest='iterations', type=int, default=1, help='the number of iterations')
+ parser.add_argument('-Kd', dest='Kd', type=float, default=0.01, help='Diffusion constant')
+ parser.add_argument('-Kh', dest='Kh', type=float, default=6, help='Maximum stable cliff height')
+ parser.add_argument('-Kp', dest='Kp', type=float, default=0.1, help='Avalanche probability for unstable cliffs')
+ parser.add_argument('-Kr', dest='Kr', type=float, default=0.1, help='Average amount of rain per iteration')
+ parser.add_argument('-Kspring', dest='Kspring', type=float, default=0.0, help='Average amount of wellwater per iteration')
+ parser.add_argument('-Kspringx', dest='Kspringx', type=float, default=0.5, help='relative x position of spring')
+ parser.add_argument('-Kspringy', dest='Kspringy', type=float, default=0.5, help='relative y position of spring')
+ parser.add_argument('-Kspringr', dest='Kspringr', type=float, default=0.02, help='radius of spring')
+ parser.add_argument('-Kdep', dest='Kdep', type=float, default=0.1, help='Sediment deposition constant')
+ parser.add_argument('-Ks', dest='Ks', type=float, default=0.1, help='Soil softness constant')
+ parser.add_argument('-Kc', dest='Kc', type=float, default=1.0, help='Sediment capacity')
+ parser.add_argument('-Ka', dest='Ka', type=float, default=2.0, help='Slope dependency of erosion')
+ parser.add_argument('-ri', action='store_true', dest='rawin', default=False, help='use Blender raw format for input')
+ parser.add_argument('-ro', action='store_true', dest='rawout', default=False, help='use Blender raw format for output')
+ parser.add_argument('-i', action='store_true', dest='useinputfile', default=False, help='use an inputfile (instead of just a synthesized grid)')
+ parser.add_argument('-t', action='store_true', dest='timingonly', default=False, help='do not write anything to an output file')
+ parser.add_argument('-infile', type=str, default="-", help='input filename')
+ parser.add_argument('-outfile', type=str, default="-", help='output filename')
+ parser.add_argument('-Gn', dest='gridsize', type=int, default=20, help='Gridsize (always square)')
+ parser.add_argument('-Gp', dest='gridpeak', type=float, default=0, help='Add peak with given height')
+ parser.add_argument('-Gs', dest='gridshelf', type=float, default=0, help='Add shelve with given height')
+ parser.add_argument('-Gm', dest='gridmesa', type=float, default=0, help='Add mesa with given height')
+ parser.add_argument('-Gr', dest='gridrandom', type=float, default=0, help='Add random values between 0 and given value')
+ parser.add_argument('-m', dest='threads', type=int, default=1, help='number of threads to use')
+ parser.add_argument('-u', action='store_true', dest='unittest', default=False, help='perfom unittests')
+ parser.add_argument('-a', action='store_true', dest='analyze', default=False, help='show some statistics of input and output meshes')
+ parser.add_argument('-d', action='store_true', dest='dump', default=False, help='show sediment and water meshes at end of run')
+ parser.add_argument('-n', action='store_true', dest='usenumexpr', default=False, help='use numexpr optimizations')
+
+ args = parser.parse_args()
+ print("\nInput arguments:")
+ print("\n".join("%-15s: %s"%t for t in sorted(vars(args).items())), file=sys.stderr)
+
+ if args.unittest:
+ unittest.main(argv=[sys.argv[0]])
+ sys.exit(0)
+
+ if args.useinputfile:
+ if args.rawin:
+ grid = Grid.fromRaw(args.infile)
+ else:
+ grid = Grid.fromFile(args.infile)
+ else:
+ grid = Grid(args.gridsize)
+
+ if args.gridpeak > 0 : grid.peak(args.gridpeak)
+ if args.gridmesa > 0 : grid.mesa(args.gridmesa)
+ if args.gridshelf > 0 : grid.shelf(args.gridshelf)
+ if args.gridrandom > 0 : grid.random(args.gridrandom)
+
+ if args.analyze:
+ print('\nstatistics of the input grid:\n\n', grid.analyze(), file=sys.stderr, sep='' )
+ t = getptime()
+ for g in range(args.iterations):
+ if args.Kd > 0:
+ grid.diffuse(args.Kd, args.usenumexpr)
+ if args.Kh > 0 and args.Kp > rand():
+ grid.avalanche(args.Kh, args.usenumexpr)
+ if args.Kr > 0 or args.Kspring > 0:
+ grid.fluvial_erosion(args.Kr, args.Kc, args.Ks, args.Kdep, args.Ka, args.Kspring, args.Kspringx, args.Kspringy, args.Kspringr, args.usenumexpr)
+ t = getptime() - t
+ print("\nElapsed time: %.1f seconds, max memory %.1f Mb.\n"%(t,grid.maxrss), file=sys.stderr)
+ if args.analyze:
+ print('\nstatistics of the output grid:\n\n', grid.analyze(), file=sys.stderr, sep='')
+
+ if not args.timingonly:
+ if args.rawout:
+ grid.toRaw(args.outfile, vars(args))
+ else:
+ grid.toFile(args.outfile)
+
+ if args.dump:
+ print("sediment\n", np.array_str(grid.sediment,precision=3), file=sys.stderr)
+ print("water\n", np.array_str(grid.water,precision=3), file=sys.stderr)
+ print("sediment concentration\n", np.array_str(grid.sediment/grid.water,precision=3), file=sys.stderr)
diff --git a/ant_landscape/mesh_ant_displace.py b/ant_landscape/mesh_ant_displace.py
index 7a1c17ea..f1204455 100644
--- a/ant_landscape/mesh_ant_displace.py
+++ b/ant_landscape/mesh_ant_displace.py
@@ -43,7 +43,7 @@ from .ant_functions import (
class AntMeshDisplace(bpy.types.Operator):
bl_idname = "mesh.ant_displace"
bl_label = "Another Noise Tool - Displace"
- bl_description = "Displace mesh vertices"
+ bl_description = "A.N.T. Displace mesh vertices"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
ant_terrain_name = StringProperty(
@@ -428,6 +428,16 @@ class AntMeshDisplace(bpy.types.Operator):
default=False,
description="Remove doubles"
)
+ direction = EnumProperty(
+ name="Direction",
+ default="NORMAL",
+ description="Displacement direction",
+ items = [
+ ("NORMAL", "Normal", "Displace along vertex normal direction", 0),
+ ("Z", "Z", "Displace in the Z direction", 1),
+ ("Y", "Y", "Displace in the Y direction", 2),
+ ("X", "X", "Displace in the X direction", 3)]
+ )
show_main_settings = BoolProperty(
name="Main Settings",
default=True,
@@ -533,7 +543,8 @@ class AntMeshDisplace(bpy.types.Operator):
self.strata,
self.water_plane,
self.water_level,
- self.use_vgroup
+ self.use_vgroup,
+ self.remove_double
]
# do displace
@@ -542,12 +553,33 @@ class AntMeshDisplace(bpy.types.Operator):
if self.use_vgroup is True:
vertex_group = ob.vertex_groups.active
if vertex_group:
- for v in mesh.vertices:
- v.co += vertex_group.weight(v.index) * v.normal * noise_gen(v.co, props)
-
+ if self.direction == "X":
+ for v in mesh.vertices:
+ v.co[0] += vertex_group.weight(v.index) * noise_gen(v.co, props)
+ if self.direction == "Y":
+ for v in mesh.vertices:
+ v.co[1] += vertex_group.weight(v.index) * noise_gen(v.co, props)
+ if self.direction == "Z":
+ for v in mesh.vertices:
+ v.co[2] += vertex_group.weight(v.index) * noise_gen(v.co, props)
+ else:
+ for v in mesh.vertices:
+ v.co += vertex_group.weight(v.index) * v.normal * noise_gen(v.co, props)
+
else:
- for v in mesh.vertices:
- v.co += v.normal * noise_gen(v.co, props)
+ if self.direction == "X":
+ for v in mesh.vertices:
+ v.co[0] += noise_gen(v.co, props)
+ elif self.direction == "Y":
+ for v in mesh.vertices:
+ v.co[1] += noise_gen(v.co, props)
+ elif self.direction == "Z":
+ for v in mesh.vertices:
+ v.co[2] += noise_gen(v.co, props)
+ else:
+ for v in mesh.vertices:
+ v.co += v.normal * noise_gen(v.co, props)
+
mesh.update()
if bpy.ops.object.shade_smooth == True:
diff --git a/ant_landscape/stats.py b/ant_landscape/stats.py
new file mode 100644
index 00000000..a5053465
--- /dev/null
+++ b/ant_landscape/stats.py
@@ -0,0 +1,55 @@
+from time import time
+
+try:
+ import psutil
+ print('psutil available')
+ psutil_available=True
+except ImportError:
+ psutil_available=False
+
+class Stats:
+ def __init__(self):
+ self.memstats_available = False
+ if psutil_available:
+ self.process=psutil.Process()
+ self.memstats_available = True
+ self.reset()
+
+ def reset(self):
+ self.lasttime=self._gettime()
+ self.lastmem=self._getmem()
+ self.basemem = self.lastmem
+ self.maxmem=0
+ self.elapsedtime=0
+
+ def _gettime(self):
+ """return the time in seconds used by the current process."""
+ if psutil_available:
+ m=self.process.get_cpu_times()
+ return m.user+m.system
+ return time()
+
+ def _getmem(self):
+ """return the resident set size in bytes used by the current process."""
+ if psutil_available:
+ m=self.process.get_memory_info()
+ return m.rss
+ return 0
+
+ def time(self):
+ """return the time since the last call in seconds used by the current process."""
+ old = self.lasttime
+ self.lasttime = self._gettime()
+ self.elapsedtime = self.lasttime-old
+ return self.elapsedtime
+
+ def memory(self):
+ """return the maximum resident set size since the first call in bytes used by the current process."""
+ self.lastmem = self._getmem()
+ d = self.lastmem - self.basemem
+ if d>self.maxmem:
+ self.maxmem = d
+ return self.maxmem
+
+
+ \ No newline at end of file
diff --git a/ant_landscape/test.py b/ant_landscape/test.py
new file mode 100644
index 00000000..32894a18
--- /dev/null
+++ b/ant_landscape/test.py
@@ -0,0 +1,19 @@
+from stats import Stats
+from numpy import *
+
+stats = Stats()
+
+a = zeros(10000000)
+print(stats.time())
+print(stats.memory())
+a = sin(a)
+print(stats.time())
+print(stats.memory())
+a = cos(a)
+print(stats.time())
+print(stats.memory())
+a = cos(a)**2+sin(a)**2
+print(stats.time())
+print(stats.memory())
+
+
diff --git a/ant_landscape/utils.py b/ant_landscape/utils.py
new file mode 100644
index 00000000..595d5250
--- /dev/null
+++ b/ant_landscape/utils.py
@@ -0,0 +1,7 @@
+numexpr_available=False
+try:
+ import numexpr
+ numexpr_available=True
+except ImportError:
+ pass
+