diff options
-rw-r--r-- | ant_landscape/ErosionR.txt | 2 | ||||
-rw-r--r-- | ant_landscape/__init__.py | 500 | ||||
-rw-r--r-- | ant_landscape/add_mesh_ant_landscape.py | 18 | ||||
-rw-r--r-- | ant_landscape/ant_functions.py | 452 | ||||
-rw-r--r-- | ant_landscape/ant_landscape_refresh.py | 106 | ||||
-rw-r--r-- | ant_landscape/eroder.py | 652 | ||||
-rw-r--r-- | ant_landscape/mesh_ant_displace.py | 46 | ||||
-rw-r--r-- | ant_landscape/stats.py | 55 | ||||
-rw-r--r-- | ant_landscape/test.py | 19 | ||||
-rw-r--r-- | ant_landscape/utils.py | 7 | ||||
-rw-r--r-- | presets/operator/mesh.landscape_add/billow.py | 59 | ||||
-rw-r--r-- | presets/operator/mesh.landscape_add/canions.py | 59 | ||||
-rw-r--r-- | presets/operator/mesh.landscape_add/mounds.py (renamed from presets/operator/mesh.landscape_add/smooth_terrain.py) | 22 | ||||
-rw-r--r-- | presets/operator/mesh.landscape_add/ridged.py | 59 |
14 files changed, 1730 insertions, 326 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 + diff --git a/presets/operator/mesh.landscape_add/billow.py b/presets/operator/mesh.landscape_add/billow.py new file mode 100644 index 00000000..66b13977 --- /dev/null +++ b/presets/operator/mesh.landscape_add/billow.py @@ -0,0 +1,59 @@ +import bpy +op = bpy.context.active_operator + +op.ant_terrain_name = 'Landscape' +op.land_material = '' +op.water_material = '' +op.texture_block = '' +op.at_cursor = True +op.smooth_mesh = True +op.tri_face = False +op.sphere_mesh = False +op.subdivision_x = 128 +op.subdivision_y = 128 +op.mesh_size = 2.0 +op.mesh_size_x = 2.0 +op.mesh_size_y = 2.0 +op.random_seed = 13 +op.noise_offset_x = 0.0 +op.noise_offset_y = 0.0 +op.noise_offset_z = 0.0 +op.noise_size_x = 1.0 +op.noise_size_y = 1.0 +op.noise_size_z = 1.0 +op.noise_size = 1.0 +op.noise_type = 'turbulence_vector' +op.basis_type = '2' +op.vl_basis_type = '0' +op.distortion = 1.0 +op.hard_noise = '1' +op.noise_depth = 6 +op.amplitude = 0.5 +op.frequency = 1.5 +op.dimension = 1.0 +op.lacunarity = 2.0 +op.offset = 0.8999999761581421 +op.gain = 2.0 +op.marble_bias = '0' +op.marble_sharp = '0' +op.marble_shape = '0' +op.height = 0.25 +op.height_invert = False +op.height_offset = 0.0 +op.edge_falloff = '0' +op.falloff_x = 4.0 +op.falloff_y = 4.0 +op.edge_level = 0.0 +op.maximum = 1.0 +op.minimum = -1.0 +op.use_vgroup = False +op.strata = 5.0 +op.strata_type = '0' +op.water_plane = False +op.water_level = 0.009999999776482582 +op.remove_double = False +op.show_main_settings = True +op.show_noise_settings = True +op.show_displace_settings = True +op.refresh = True +op.auto_refresh = True diff --git a/presets/operator/mesh.landscape_add/canions.py b/presets/operator/mesh.landscape_add/canions.py new file mode 100644 index 00000000..d0635be2 --- /dev/null +++ b/presets/operator/mesh.landscape_add/canions.py @@ -0,0 +1,59 @@ +import bpy +op = bpy.context.active_operator + +op.ant_terrain_name = 'Landscape' +op.land_material = '' +op.water_material = '' +op.texture_block = '' +op.at_cursor = True +op.smooth_mesh = True +op.tri_face = False +op.sphere_mesh = False +op.subdivision_x = 128 +op.subdivision_y = 128 +op.mesh_size = 2.0 +op.mesh_size_x = 2.0 +op.mesh_size_y = 2.0 +op.random_seed = 533 +op.noise_offset_x = 0.0 +op.noise_offset_y = 0.0 +op.noise_offset_z = 0.0 +op.noise_size_x = 1.0 +op.noise_size_y = 1.0 +op.noise_size_z = 1.0 +op.noise_size = 0.5 +op.noise_type = 'hetero_terrain' +op.basis_type = '2' +op.vl_basis_type = '0' +op.distortion = 1.0 +op.hard_noise = '0' +op.noise_depth = 8 +op.amplitude = 0.5 +op.frequency = 2.0 +op.dimension = 1.100000023841858 +op.lacunarity = 1.7999999523162842 +op.offset = 0.800000011920929 +op.gain = 2.0 +op.marble_bias = '0' +op.marble_sharp = '0' +op.marble_shape = '0' +op.height = 0.25 +op.height_invert = False +op.height_offset = -0.0 +op.edge_falloff = '0' +op.falloff_x = 4.0 +op.falloff_y = 4.0 +op.edge_level = 0.0 +op.maximum = 1.0 +op.minimum = -1.0 +op.use_vgroup = False +op.strata = 2.0 +op.strata_type = '2' +op.water_plane = False +op.water_level = 0.009999999776482582 +op.remove_double = False +op.show_main_settings = True +op.show_noise_settings = True +op.show_displace_settings = True +op.refresh = True +op.auto_refresh = True diff --git a/presets/operator/mesh.landscape_add/smooth_terrain.py b/presets/operator/mesh.landscape_add/mounds.py index 1e66fba1..e46bbee4 100644 --- a/presets/operator/mesh.landscape_add/smooth_terrain.py +++ b/presets/operator/mesh.landscape_add/mounds.py @@ -14,40 +14,40 @@ op.subdivision_y = 128 op.mesh_size = 2.0 op.mesh_size_x = 2.0 op.mesh_size_y = 2.0 -op.random_seed = 11 +op.random_seed = 0 op.noise_offset_x = 0.0 op.noise_offset_y = 0.0 op.noise_offset_z = 0.0 op.noise_size_x = 1.0 op.noise_size_y = 1.0 op.noise_size_z = 1.0 -op.noise_size = 0.8899999856948853 -op.noise_type = 'hybrid_multi_fractal' -op.basis_type = '1' +op.noise_size = 0.33329999446868896 +op.noise_type = 'hetero_terrain' +op.basis_type = '0' op.vl_basis_type = '0' op.distortion = 1.0 op.hard_noise = '0' op.noise_depth = 8 op.amplitude = 0.5 op.frequency = 2.0 -op.dimension = 0.800000011920929 -op.lacunarity = 2.2100000381469727 -op.offset = 0.559999942779541 -op.gain = 3.0 +op.dimension = 1.100000023841858 +op.lacunarity = 2.200000047683716 +op.offset = 0.4399999976158142 +op.gain = 1.0 op.marble_bias = '0' op.marble_sharp = '0' op.marble_shape = '0' -op.height = 0.2199999988079071 +op.height = 0.20000000298023224 op.height_invert = False op.height_offset = 0.0 -op.edge_falloff = '3' +op.edge_falloff = '0' op.falloff_x = 4.0 op.falloff_y = 4.0 op.edge_level = 0.0 op.maximum = 1.0 op.minimum = -1.0 op.use_vgroup = False -op.strata = 2.0 +op.strata = 5.0 op.strata_type = '0' op.water_plane = False op.water_level = 0.009999999776482582 diff --git a/presets/operator/mesh.landscape_add/ridged.py b/presets/operator/mesh.landscape_add/ridged.py new file mode 100644 index 00000000..a0fff501 --- /dev/null +++ b/presets/operator/mesh.landscape_add/ridged.py @@ -0,0 +1,59 @@ +import bpy +op = bpy.context.active_operator + +op.ant_terrain_name = 'Landscape' +op.land_material = '' +op.water_material = '' +op.texture_block = '' +op.at_cursor = True +op.smooth_mesh = True +op.tri_face = False +op.sphere_mesh = False +op.subdivision_x = 128 +op.subdivision_y = 128 +op.mesh_size = 2.0 +op.mesh_size_x = 2.0 +op.mesh_size_y = 2.0 +op.random_seed = 23 +op.noise_offset_x = 0.0 +op.noise_offset_y = 0.0 +op.noise_offset_z = 0.0 +op.noise_size_x = 1.0 +op.noise_size_y = 1.0 +op.noise_size_z = 1.0 +op.noise_size = 1.0 +op.noise_type = 'ridged_multi_fractal' +op.basis_type = '0' +op.vl_basis_type = '0' +op.distortion = 1.0 +op.hard_noise = '0' +op.noise_depth = 8 +op.amplitude = 0.5 +op.frequency = 2.0 +op.dimension = 1.0 +op.lacunarity = 2.0 +op.offset = 0.8999999761581421 +op.gain = 2.0 +op.marble_bias = '0' +op.marble_sharp = '0' +op.marble_shape = '0' +op.height = 0.25 +op.height_invert = False +op.height_offset = 0.0 +op.edge_falloff = '0' +op.falloff_x = 4.0 +op.falloff_y = 4.0 +op.edge_level = 0.0 +op.maximum = 1.0 +op.minimum = -1.0 +op.use_vgroup = False +op.strata = 5.0 +op.strata_type = '0' +op.water_plane = False +op.water_level = 0.009999999776482582 +op.remove_double = False +op.show_main_settings = True +op.show_noise_settings = True +op.show_displace_settings = True +op.refresh = True +op.auto_refresh = True |