From 753e26861510670f564dbc6c685b8c63d366ce85 Mon Sep 17 00:00:00 2001 From: Patrick Scholz Date: Fri, 4 Oct 2024 16:00:28 +0200 Subject: [PATCH 1/3] add files to load fesom mesh into blender --- tools/blender/README.md | 42 +++ tools/blender/do_blenderplanet_fesom2.py | 377 +++++++++++++++++++++++ tools/blender/sub_blender.py | 315 +++++++++++++++++++ 3 files changed, 734 insertions(+) create mode 100644 tools/blender/README.md create mode 100644 tools/blender/do_blenderplanet_fesom2.py create mode 100644 tools/blender/sub_blender.py diff --git a/tools/blender/README.md b/tools/blender/README.md new file mode 100644 index 0000000..2d46dc4 --- /dev/null +++ b/tools/blender/README.md @@ -0,0 +1,42 @@ + +How to install tripyview in blender: + +1) download and unpack Blender src code archive e.g. blender-4.2.2-linux-x64.tar.xz + +2) unzip folder + +3) go to directory /blender-4.2.2-linux-x64/4.2/python/bin/ and execute + +/blender-4.2.2-linux-x64/4.2/python/bin/python3.11 -m pip -e install /path_to_tripyview/ + +--> This should install tripyview in the python version that blender is using + +4) test if tripyview installation works: + +/blender-4.2.2-linux-x64/4.2/python/bin/python3.11 -c "import tripyview" + +6) Open Blender + +7) go to the Scripting tab and load the script do_blenderplanet_fesom2.py +make sure all the path are correct + +8) run that script!!! + +9) There might occure the error message where the blender interface colides with pyvista: + +Python: Traceback (most recent call last): + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/home/pscholz/Python/tripyview/tripyview/__init__.py", line 25, in + from .sub_3dsphere import * + File "/home/pscholz/Python/tripyview/tripyview/sub_3dsphere.py", line 3, in + import vtk + File "/home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtk.py", line 47, in + from vtkmodules.vtkRenderingMatplotlib import * + ImportError: /home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtkmodules/libvtkPythonInterpreter-9.3.so: undefined symbol: Py_RunMain + +simply open the file /home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtk.py +and comment line 47! diff --git a/tools/blender/do_blenderplanet_fesom2.py b/tools/blender/do_blenderplanet_fesom2.py new file mode 100644 index 0000000..49608b5 --- /dev/null +++ b/tools/blender/do_blenderplanet_fesom2.py @@ -0,0 +1,377 @@ +import bpy +import bmesh +import os +import tripyview as tpv +import numpy as np +import xarray as xr +import time as clock +import warnings +from matplotlib.path import Path +from matplotlib.tri import Triangulation +import sys + +tpv_path = os.path.dirname(os.path.dirname(tpv.__file__)) +sys.path.append(tpv_path+'/tools/blender') +print(tpv_path+'/tools/blender') +from sub_blender import blender_create_mesh, blender_create_txtremat + +R_earth = 6371.0e3 # meter +potatoefac_ocean = 0.0 +potatoefac_land = 0.0 + +gen_path = '/home/pscholz/Python/blender_fesom2/' + +mesh_path, do_rot = '/home/pscholz/Python/blender_fesom2/data/core2_srt_dep@node/', 'None' + +do_oce_data = False +oce_txturepath = gen_path+'texture/Albedo.jpg' +oce_alphamap = gen_path+'lsmask/core2_lsmask_ocean.jpg' + +oce_datapath = '/home/pscholz/Python/blender_fesom2/data/' +oce_vname = 'ssh' +oce_year = [1958, 1958] +oce_mon = None +oce_day = None +oce_depth = None + +oce_bumpmap = gen_path+'texture/Bump.jpg' +oce_bumpstrength = 0.5 + +land_txturepath = gen_path+'texture/Albedo.jpg' +land_alphamap = gen_path+'lsmask/core2_lsmask_ocean.jpg' +land_bumpmap = gen_path+'texture/Bump.jpg' +land_bumpstrength = 0.5 + +topo_path, do_topo = gen_path+'topo/topo_1deg.nc', True +topo_varname, topo_dimname = 'topo', ['lon','lat'] +topo_resol = 1 + + + +# +# +# +#___LOAD FESOM2 MESH____________________________________________________________ +mesh=tpv.load_mesh_fesom2(mesh_path, do_rot=do_rot, focus=0, do_info=True, do_pickle=False, + do_earea=True, do_narea=True, do_eresol=[True,'mean'], do_nresol=[True,'eresol']) + + + +# +# +# +#=============================================================================== +#=== CREATE BLENDER OCEAN MESH ================================================= +#=============================================================================== +n_x = np.hstack((mesh.n_x,mesh.n_xa)) +n_y = np.hstack((mesh.n_y,mesh.n_ya)) +n_z = np.hstack((mesh.n_z,mesh.n_za)) +n_i = np.hstack((mesh.n_i,mesh.n_i[mesh.n_pbnd_a])) +n_resol = np.hstack((mesh.n_resol,mesh.n_resol[mesh.n_pbnd_a])) +e_i = np.vstack((mesh.e_i[mesh.e_pbnd_0,:],mesh.e_ia)) + +# convert sperical coordinates into cartesian coordinates +xs,ys,zs = tpv.grid_cart3d(n_x, n_y, 1.0, is_deg=True) + +# create blender vertice tuple list +# vertices = [ (V1_x, V1_y, V1_z), +# (V2_x, V2_y, V2_z), +# ... +# ] +vertices = list(zip(xs, ys, zs)) + +# create blender normal are with respect to face +# normals = [ (T1x, T1y, T1z), +# (T2x, T2y, T2z), +# ... +# ] +normals = list(zip(xs[e_i].sum(axis=1)/3, ys[e_i].sum(axis=1)/3, zs[e_i].sum(axis=1)/3 )) +del xs,ys,zs + +# create blender triangle tuple list +# triangles = [ (T1_V1, T1_V2, T1_V3), +# (T2_V1, T2_V2, T2_V3), +# ... +# ] +triangles = list(zip(e_i[:,0], e_i[:,1], e_i[:,2] )) + +# compute vertice uv mapping coordinates +#xs, ys, zs = tpv.grid_cart3d(mesh.n_x+90, mesh.n_y, 1.0, is_deg=True) +#u = 0.5 + np.arctan2(-xs,ys)/(2 * np.pi) +xs, ys, zs = tpv.grid_cart3d(n_x, n_y, 1.0, is_deg=True) +u = 0.5 + np.arctan2(ys,xs)/(2 * np.pi) +v = 0.5 + np.arcsin(zs)/np.pi +uv = list(zip(u, v)) +del(xs, ys, zs, u, v) + +# +# +#______________________________________________________________________________________ +# add additional attribute variables that can be used in blender via an attribute node +add_meshattr = dict() +add_meshattr['ntopo'] = -np.abs(n_z) +add_meshattr['nresol'] = n_resol + +# +# +#______________________________________________________________________________________ +# load specific fesom2 data with tripyview into additional attribute variables that can +# be used in blender via an attribute node +add_dataattr = dict() +if oce_datapath is not None and oce_vname is not None: + # load data with tripyview + data = tpv.load_data_fesom2(mesh, oce_datapath, vname=oce_vname, year=oce_year, mon=oce_mon, day=oce_day, depth=oce_depth, + do_load=True, do_persist=False) + data_plot = data[oce_vname].values + add_dataattr[oce_vname] = np.hstack((data_plot, data_plot[mesh.n_pbnd_a])) + +# +# +#______________________________________________________________________________________ +# create blender mesh object +obj_ocean = blender_create_mesh('ocean', + vertices, + triangles, + normals, + uv, + add_meshattr=add_meshattr, + add_dataattr=add_dataattr, + shade_flat=True ) + +# +# +#______________________________________________________________________________________ +# create blender texture material +obj_ocean = blender_create_txtremat('ocean', obj_ocean, + oce_txturepath, + alphamap=oce_alphamap, alpha_invert=False, + bumpmap=oce_bumpmap, bump_strength=0.1, bump_smth=1.0) +## make mesh active +#bpy.context.view_layer.objects.active = obj_ocean +#mesh_ocean = obj_ocean.data + +## Create a FloatAttribute for the vertices +#if not mesh_ocean.attributes.get(data_vname): +# # domain='POINT' decides that these data will be attributed to the vertices , can be also attributed to the Faces +# data_ocean = mesh_ocean.attributes.new(name=data_vname, type='FLOAT', domain='POINT') +#else: +# data_ocean = mesh_ocean.attributes[data_vname] +#data_ocean.data.foreach_set("value",data_plot) +#data_ocean.data.update() + +## put min/max value as custom properties +#obj_ocean[f'{data_vname}_min'] = float(np.min(data_plot)) +#obj_ocean[f'{data_vname}_max'] = float(np.max(data_plot)) + +# +# +# +#=============================================================================== +#=== CREATE BLENDER LAND MESH ================================================== +#=============================================================================== +# cycle over all land polygons +for niland, lsmask in enumerate(mesh.lsmask_a): + poly_x, poly_y = lsmask[:,0], lsmask[:,1] + xmin, xmax = np.floor(poly_x).min(), np.ceil(poly_x).max() + ymin, ymax = np.floor(poly_y).min(), np.ceil(poly_y).max() + + #x_m, y_m = np.meshgrid(np.arange(xmin, xmax, topo_resol),np.arange(ymin, ymax, topo_resol)) + x_m, y_m = np.meshgrid(np.arange(xmin, xmax, topo_resol),np.arange(-90, 90+topo_resol, topo_resol)) + x_m, y_m = x_m.reshape((x_m.size, 1)), y_m.reshape((y_m.size, 1)) + + # check if regular points are within polygon + IN = Path(lsmask).contains_points(np.concatenate((x_m, y_m),axis=1)) + x_m, y_m = x_m[IN==True], y_m[IN==True] + del IN + + # combine polygon points and regular points within polygon --> do triangulation + outeredge = np.vstack((poly_x, poly_y)).transpose() + points = np.hstack((x_m, y_m)) + pts_iscst = np.zeros(x_m.shape[0]) + points = np.vstack((outeredge, points)) + pts_iscst = np.hstack(( np.ones(poly_x.shape), pts_iscst)) # collect if points belongs to coast + if np.unique(points[:,0]).size<=3 or np.unique(points[:,1]).size<=3 : continue + tri = Triangulation(points[:,0], points[:,1]) + del outeredge, poly_x, poly_y + + # compute trinagle centroids and check if they are within polygon + tri_cx = np.sum(points[tri.triangles,0],axis=1)/3 + tri_cy = np.sum(points[tri.triangles,1],axis=1)/3 + tri_cx = np.reshape(tri_cx,(tri_cx.size,1)) + tri_cy = np.reshape(tri_cy,(tri_cy.size,1)) + IN = Path(lsmask).contains_points(np.concatenate((tri_cx,tri_cy),axis=1)) + tri.triangles=tri.triangles[IN==True,:] + del tri_cx, tri_cy, IN + + # concatenate all land trinagles + if niland==0: + land_points = points + land_elem2d = tri.triangles + land_ptsiscst = pts_iscst # does points belongs to coast + else: + land_elem2d = np.concatenate((land_elem2d, tri.triangles+land_points.shape[0]), axis=0) + land_points = np.concatenate((land_points, points), axis=0) + land_ptsiscst = np.concatenate((land_ptsiscst, pts_iscst)) + del points + +# do topographic scaling (potatoefication) for land mesh +R_grid = R_earth +from netCDF4 import Dataset +from scipy.interpolate import griddata +fid = Dataset(topo_path,'r') +topo = fid.variables[topo_varname][:] +topo[topo<0]=0.0 +lon = fid.variables[topo_dimname[0]][:] +lat = fid.variables[topo_dimname[1]][:] +fid.close() +mlon,mlat=np.meshgrid(lon,lat) +bottom_depth_2d = griddata( np.transpose( (mlon.flatten(),mlat.flatten() ) ), topo.flatten(), land_points, method='linear') +bottom_depth_2d[land_ptsiscst==1] = 0.0 +bottom_depth_2d[np.isnan(bottom_depth_2d)] = 0.0 +if do_topo: + R_grid = R_grid+( bottom_depth_2d*100*potatoefac_land) + del topo,lon,lat,mlon,mlat +R_grid = R_grid/R_earth + +# create blender vertice tuple list +# vertices = [ (V1_x, V1_y, V1_z), +# (V2_x, V2_y, V2_z), +# ... +# ] +xs,ys,zs = tpv.grid_cart3d(land_points[:,0], land_points[:,1], R_grid, is_deg=True) +vertices_land = list(zip(xs, ys, zs)) + +# create blender normal are with respect to face +# normals = [ (T1x, T1y, T1z), +# (T2x, T2y, T2z), +# ... +# ] +normals_land = list(zip(xs[land_elem2d].sum(axis=1)/3, ys[land_elem2d].sum(axis=1)/3, zs[land_elem2d].sum(axis=1)/3 )) +del xs,ys,zs + +# triangles = [ (T1_V1, T1_V2, T1_V3), +# (T2_V1, T2_V2, T2_V3), +# ... +# ] +triangles_land = list(zip(land_elem2d[:,0], land_elem2d[:,1],land_elem2d[:,2] )) + +# compute vertice uv mapping coordinates +#xs, ys, zs = tpv.grid_cart3d(mesh.n_x+90, mesh.n_y, 1.0, is_deg=True) +#u = 0.5 + np.arctan2(-xs,ys)/(2 * np.pi) +xs, ys, zs = tpv.grid_cart3d(land_points[:,0], land_points[:,1], 1.0, is_deg=True) +u = 0.5 + np.arctan2(ys,xs)/(2 * np.pi) +v = 0.5 + np.arcsin(zs)/np.pi +uv_land = list(zip(u, v)) +del(xs, ys, zs, u, v) + +# add additional attribute variables that can be used in blender via an attribute node +add_meshattr = dict() +add_meshattr['ntopo'] = np.abs(bottom_depth_2d) + +# +# +#______________________________________________________________________________________ +# create blender mesh object +obj_land = blender_create_mesh('land', + vertices_land, + triangles_land, + normals_land, + uv_land, + add_meshattr=add_meshattr, + shade_flat=False ) +# +# +#______________________________________________________________________________________ +# create blender texture material +obj_land = blender_create_txtremat('land', obj_land, + land_txturepath, + alphamap=land_alphamap, alpha_invert=True, + bumpmap=land_bumpmap, bump_strength=0.1, bump_smth=1.0) + +# +# +#=============================================================================== +#=== BLENDER ADD CLOUD LAYER SPHERE ============================================ +#=============================================================================== +#obj_cloud = bledner_create_cloud(R_grid, txture_clouds, facemultipl=2) + + + +# +# +# +#=============================================================================== +#=== COMBINE BLENDER OCEAN & LAND MESH ========================================= +#=============================================================================== +# Create parent object that is responsible for the rotation movement of all the +# childs +obj_parent = bpy.data.objects.new('Parent', None) +bpy.context.collection.objects.link(obj_parent) +## obj_list = ["obj_ocean", "obj_land", "obj_cloud"] +obj_list = ["obj_ocean", "obj_land"] +for obj_name in obj_list: + obj = bpy.data.objects.get(obj_name) + if obj: + obj.parent = obj_parent + + + +## +## +##=============================================================================== +##=== BLENDER ROTATION ANIMATION ================================================ +##=============================================================================== +#rotation_frames = 1000 # Number of frames for one complete rotation +#init_rot = 0 +#deg2rad = np.pi/180 +## Set the rotation mode to 'XYZ' to enable rotation around specific axes +#scene = bpy.data.scenes["Scene"] +#obj_parent.rotation_mode = 'XYZ' + +#scene.frame_start = 1 +#scene.frame_end = rotation_frames + +## Set the initial rotation (optional) +#obj_parent.rotation_euler = (0, 0, init_rot) +#obj_parent.keyframe_insert("rotation_euler", index=2 , frame=1) # Initial keyframe + +#obj_parent.rotation_euler = (0, 0, init_rot*deg2rad + 360*deg2rad) +#obj_parent.keyframe_insert('rotation_euler', index=2 ,frame=rotation_frames) + +## make planetary rotation cyclic +## Access the animation data +#action = obj_parent.animation_data.action +#fcurve = action.fcurves.find('rotation_euler', index=2) + +## Make the animation cyclic +#if fcurve: +# # Set the extrapolation mode to 'MAKE_CYCLIC' +# mod = fcurve.modifiers.new(type='CYCLES') +# mod.mode_before = 'REPEAT' +# mod.mode_after = 'REPEAT' + + +# +# +# +#=============================================================================== +#=== BLENDER RENDERER_ENGINE SETTINGS ========================================== +#=============================================================================== +# switch off Viewport Denoising otherwise bumpmap creates artifacts in the animation +# Set the context to Eevee +#bpy.context.scene.render.engine = 'BLENDER_EEVEE' +bpy.context.scene.render.engine = 'CYCLES' + +# Enable or disable viewport denoising +bpy.context.scene.eevee.use_gtao = False # Set to True to enable, False to disable + +# Optional: You can also adjust the samples and other related settings if needed +bpy.context.scene.eevee.taa_render_samples = 64 # Set the desired number of samples +bpy.context.scene.eevee.taa_samples = 16 # Set the desired number of viewport samples + + +#scene.render.use_stamp = 1 +#scene.render.stamp_background = (0,0,0,1) +#scene.render.filepath = "render/anim" +#scene.render.image_settings.file_format = "AVI_JPEG" +#bpy.ops.render.render(animation=True) diff --git a/tools/blender/sub_blender.py b/tools/blender/sub_blender.py new file mode 100644 index 0000000..b520a4a --- /dev/null +++ b/tools/blender/sub_blender.py @@ -0,0 +1,315 @@ +import bpy +import bmesh +import tripyview as tpv +import numpy as np +import xarray as xr +import time as clock +import warnings +from matplotlib.path import Path +from matplotlib.tri import Triangulation + + + +# +# +#_______________________________________________________________________________ +def blender_create_mesh(name, vertices, triangles, normals, uv, + add_meshattr=dict(), + add_dataattr=dict(), + shade_flat=True): + + # vertice and elements dimension + n2dn = len(vertices) + n2de = len(triangles) + + #___________________________________________________________________________ + # create empty blender mesh class + mesh = bpy.data.meshes.new("mesh_"+name) + + # add vertices and triangles + mesh.from_pydata(vertices, [], triangles, shade_flat=shade_flat) + del(vertices, triangles) + + # add uv layer mapping coordinates the blender mesh + # Create a new UV map if it doesn't already exist + uv_layer = mesh.uv_layers.new(name="UVMap") + + bm = bmesh.new() + bm.from_mesh(mesh) + bm.faces.ensure_lookup_table() + + # add uv coordinates and face normals + uv_layer = bm.loops.layers.uv.active + for ii, face in enumerate(bm.faces): + face.normal = normals[ii] + for loop in face.loops: + loop[uv_layer].uv = uv[loop.vert.index] # Ensure this matches your UV data structure + bm.to_mesh(mesh) + bm.free() + + + #___________________________________________________________________________ + # --> create empty blender object class connected with mesh + obj = bpy.data.objects.new("obj_"+name, mesh) + + #___________________________________________________________________________ + # add additional attributes like for resolution and vertical discplacement + if add_meshattr is not None: + for attr_var in add_meshattr: + # Create a FloatAttribute for the vertices + if not mesh.attributes.get(attr_var): + if len(add_meshattr[attr_var]) == n2dn : domain='POINT' + elif len(add_meshattr[attr_var]) == n2de : domain='FACE' + else: + print(' mesh attribute size is not supported') + + # domain='POINT' decides that these data will be attributed to the vertices , can be also attributed to the Faces + attribute = mesh.attributes.new(name=f'mesh_{attr_var}', type='FLOAT', domain='POINT') + else: + attribute = mesh.attributes[attr_var] + + attribute.data.foreach_set("value",add_meshattr[attr_var]) + attribute.data.update() + + # put min/max value as custom properties + obj[f'mesh_{attr_var}_min'] = float(np.nanmin(add_meshattr[attr_var])) + obj[f'mesh_{attr_var}_max'] = float(np.nanmax(add_meshattr[attr_var])) + + #_________________________________________________________________________________ + # add additional attributes like for resolution and vertical discplacement + if add_dataattr is not None: + for attr_var in add_dataattr: + # Create a FloatAttribute for the vertices + if not mesh.attributes.get(attr_var): + if len(add_dataattr[attr_var]) == n2dn : domain='POINT' + elif len(add_dataattr[attr_var]) == n2de : domain='FACE' + else: + print(' data attribute size is not supported') + + # domain='POINT' decides that these data will be attributed to the vertices , can be also attributed to the Faces + attribute = mesh.attributes.new(name=f'data_{attr_var}', type='FLOAT', domain='POINT') + else: + attribute = mesh.attributes[attr_var] + + attribute.data.foreach_set("value",add_dataattr[attr_var]) + attribute.data.update() + + # put min/max value as custom properties + obj[f'data_{attr_var}_min'] = float(np.nanmin(add_dataattr[attr_var])) + obj[f'data_{attr_var}_max'] = float(np.nanmax(add_dataattr[attr_var])) + + #___________________________________________________________________________ + bpy.context.collection.objects.link(obj) + return(obj) + + + +# +# +#_______________________________________________________________________________ +def blender_create_txtremat(name, obj, texture, + alphamap=None, alpha_invert=False, + bumpmap=None, bump_strength=0.1, bump_smth=1.0): + #___________________________________________________________________________ + # create texture material + material = bpy.data.materials.new(name="material_"+name) + material.use_nodes = True + + # Remove default nodes + nodes = material.node_tree.nodes + for node in nodes: + nodes.remove(node) + + #___________________________________________________________________________ + # Add different nodes in shader viewport that are than linked together by "wires" + # from import to output port + # Add an image texture node + + # Add a Texture Coordinate node + texture_coord = nodes.new(type='ShaderNodeTexCoord') + texture_coord.location = (0, 0) + + # Add a Texture File node + texture_map = nodes.new(type='ShaderNodeTexImage') + texture_map.image = bpy.data.images.load(texture) + texture_map.location = (300,0) + + #___________________________________________________________________________ + # Add a Alphamap File node + alpha_map = nodes.new(type='ShaderNodeTexImage') + alpha_map.image = bpy.data.images.load(alphamap) + alpha_map.image.colorspace_settings.name = 'Non-Color' + alpha_map.location = (300,-300) + if alpha_invert: + alpha_invert = nodes.new(type='ShaderNodeInvert') + alpha_invert.location = (450,-300) + alpha_map.location = (150,-300) + + #___________________________________________________________________________ + # Add a new principled BSDF node + bsdf_diff = nodes.new(type='ShaderNodeBsdfDiffuse') + bsdf_diff.location = (600,0) + bsdf_mix_DG = nodes.new(type='ShaderNodeMixShader') + bsdf_mix_DG.location = (800,0) + + bsdf_mix_G = nodes.new(type='ShaderNodeMixShader') + bsdf_mix_G.location = (800,-400) + bsdf_gloss = nodes.new(type='ShaderNodeBsdfGlossy') + bsdf_mix_G.location = (600,-400) + fresnel = nodes.new(type='ShaderNodeFresnel') + + #___________________________________________________________________________ + # Add a Bump node + if bumpmap is not None: + # Add a Bump Map File node + bump_map = nodes.new(type='ShaderNodeTexImage') + bump_map.image = bpy.data.images.load(bumpmap) + bump_map.image.colorspace_settings.name = 'Non-Color' + bump_map.location = (100, -600) + + bump_smooth = nodes.new(type='ShaderNodeMath') + bump_smooth.location = (250, -600) + bump_smooth.operation = 'SMOOTH_MAX' # Example: using the 'Multiply' operation + bump_smooth.inputs[1].default_value = 0.5 # Second input value + bump_smooth.inputs[2].default_value = bump_smth # Third input value (smoothness factor) + + bump_node = nodes.new(type='ShaderNodeBump') + bump_node.inputs['Strength'].default_value = bump_strength + bump_node.location = (450, -600) + + #___________________________________________________________________________ + # Create a new material output node + output = nodes.new(type='ShaderNodeOutputMaterial') + output.location = (900,0) + + # Link the nodes + material.node_tree.links.new(texture_map.inputs['Vector'], texture_coord.outputs[ 'UV']) + material.node_tree.links.new(bsdf_diff.inputs['Color'], texture_map.outputs[ 'Color']) + + # links for topographic bump mapping + if bumpmap is not None: + material.node_tree.links.new(bump_map.inputs['Vector'], texture_coord.outputs['UV']) + material.node_tree.links.new(bump_smooth.inputs[0], bump_map.outputs['Color']) + material.node_tree.links.new(bump_node.inputs['Height'], bump_smooth.outputs['Value']) + material.node_tree.links.new(bsdf_diff.inputs['Normal'], bump_node.outputs['Normal']) + + + material.node_tree.links.new(alpha_map.inputs[ 'Vector'], texture_coord.outputs[ 'UV']) + # links for alpha land seam mask mapping + if alpha_invert: + material.node_tree.links.new(alpha_invert.inputs['Color'], alpha_map.outputs['Color']) + material.node_tree.links.new(bsdf_mix_DG.inputs['Fac'], alpha_invert.outputs['Color']) + else: + material.node_tree.links.new(bsdf_mix_DG.inputs[0], alpha_map.outputs['Color']) + material.node_tree.links.new(bsdf_mix_DG.inputs[1], bsdf_diff.outputs[ 'BSDF']) + + #___________________________________________________________________________ + # link fresnel glossy + material.node_tree.links.new(bsdf_mix_G.inputs[0], fresnel.outputs[ 'Fac']) + material.node_tree.links.new(bsdf_mix_G.inputs[2], bsdf_gloss.outputs[ 'BSDF']) + material.node_tree.links.new(bsdf_mix_G.inputs[1], bsdf_diff.outputs[ 'BSDF']) + material.node_tree.links.new(bsdf_mix_DG.inputs[2], bsdf_mix_G.outputs[ 'Shader']) + material.node_tree.links.new(output.inputs[ 'Surface'], bsdf_mix_DG.outputs[ 'Shader']) + + material.blend_method = 'OPAQUE' + material.shadow_method = 'NONE' # Example: No shadow + material.use_backface_culling = False # Example: Render both side + + # Apply the material to the mesh + mesh = obj.data + mesh.materials.append(material) + + return(obj) + + + +# +# +#_______________________________________________________________________________ +def bledner_create_cloud(R_grid, txture_clouds, facemultipl=2): + mesh = bpy.data.meshes.new('mesh_'+'cloud') + # Create a UV sphere + bm = bmesh.new() + bmesh.ops.create_uvsphere(bm, u_segments=32*facemultipl, v_segments=16*facemultipl, radius=np.nanmax(R_grid)+0.01 ) + bm.to_mesh(mesh) + bm.free() + + # create object + obj = bpy.data.objects.new('obj_'+'cloud', mesh) + + #____________________________________________________________________________ + # create texture material + material = bpy.data.materials.new(name='material_'+'cloud') + material.use_nodes = True + + # Remove default nodes + nodes = material.node_tree.nodes + for node in nodes: + nodes.remove(node) + + #____________________________________________________________________________ + # Add a Texture Coordinate node + texture_coord = nodes.new(type='ShaderNodeTexCoord') + texture_coord.location = (0, 0) + + # Add a Texture File node + texture_map = nodes.new(type='ShaderNodeTexImage') + texture_map.image = bpy.data.images.load(txture_clouds) + texture_map.projection = 'SPHERE' + texture_map.image.colorspace_settings.name = 'Non-Color' + texture_map.location = (300,0) + + bsdf = nodes.new(type='ShaderNodeBsdfPrincipled') + bsdf.location = (600,0) + + bump = nodes.new(type='ShaderNodeBump') + bump.inputs['Strength'].default_value = 0.5 + bump.inputs['Distance'].default_value = 0.1 + bump.location = (450, -600) + + math = nodes.new(type='ShaderNodeMath') + math.location = (250, -600) + math.operation = 'SUBTRACT' # Example: using the 'Multiply' operation + math.inputs[1].default_value = 0.25 # Second input value + + output = nodes.new(type='ShaderNodeOutputMaterial') + output.location = (900,0) + + # Link the nodes + material.node_tree.links.new(texture_map.inputs['Vector'], texture_coord.outputs['Generated']) + material.node_tree.links.new(bsdf.inputs['Base Color'], texture_map.outputs['Color']) + material.node_tree.links.new(bump.inputs['Height'], texture_map.outputs['Color']) + material.node_tree.links.new(bsdf.inputs['Normal'], bump.outputs[ 'Normal']) + material.node_tree.links.new(math.inputs[0], texture_map.outputs[ 'Color']) + material.node_tree.links.new(bsdf.inputs['Alpha'], math.outputs[ 'Value']) + material.node_tree.links.new(output.inputs['Surface'], bsdf.outputs[ 'BSDF']) + + material.blend_method = 'BLEND' + material.shadow_method = 'NONE' # Example: No shadow + material.use_backface_culling = False # Example: Render both sides + + # Apply the material to the mesh + mesh.materials.append(material) + + # add the object into the scene + bpy.context.collection.objects.link(obj) + return(obj) + + + +# +# +#_______________________________________________________________________________ +def blender_combine_meshes(name, list_obj): + # Create a new empty mesh to combine both meshes into one object + combined_mesh = bpy.data.meshes.new('mesh_'+name) + combined_object = bpy.data.objects.new('obj_'+name, combined_mesh) + bpy.context.collection.objects.link(combined_object) + + # Join the objects + bpy.context.view_layer.objects.active = combined_object + combined_object.select_set(True) + for obj in list_obj: + obj.select_set(True) + bpy.ops.object.join() + return(combined_object) From e2f09f38d1ef0a22185856fbcd32f9d7e8a234e4 Mon Sep 17 00:00:00 2001 From: Patrick Scholz Date: Fri, 4 Oct 2024 16:25:55 +0200 Subject: [PATCH 2/3] improve readme, add some comment to do_blenderplanet_fesom2.py --- tools/blender/README.md | 86 +++++++++++++----------- tools/blender/do_blenderplanet_fesom2.py | 50 ++++++++------ 2 files changed, 76 insertions(+), 60 deletions(-) diff --git a/tools/blender/README.md b/tools/blender/README.md index 2d46dc4..e77c252 100644 --- a/tools/blender/README.md +++ b/tools/blender/README.md @@ -1,42 +1,46 @@ -How to install tripyview in blender: - -1) download and unpack Blender src code archive e.g. blender-4.2.2-linux-x64.tar.xz - -2) unzip folder - -3) go to directory /blender-4.2.2-linux-x64/4.2/python/bin/ and execute - -/blender-4.2.2-linux-x64/4.2/python/bin/python3.11 -m pip -e install /path_to_tripyview/ - ---> This should install tripyview in the python version that blender is using - -4) test if tripyview installation works: - -/blender-4.2.2-linux-x64/4.2/python/bin/python3.11 -c "import tripyview" - -6) Open Blender - -7) go to the Scripting tab and load the script do_blenderplanet_fesom2.py -make sure all the path are correct - -8) run that script!!! - -9) There might occure the error message where the blender interface colides with pyvista: - -Python: Traceback (most recent call last): - File "", line 1176, in _find_and_load - File "", line 1147, in _find_and_load_unlocked - File "", line 690, in _load_unlocked - File "", line 940, in exec_module - File "", line 241, in _call_with_frames_removed - File "/home/pscholz/Python/tripyview/tripyview/__init__.py", line 25, in - from .sub_3dsphere import * - File "/home/pscholz/Python/tripyview/tripyview/sub_3dsphere.py", line 3, in - import vtk - File "/home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtk.py", line 47, in - from vtkmodules.vtkRenderingMatplotlib import * - ImportError: /home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtkmodules/libvtkPythonInterpreter-9.3.so: undefined symbol: Py_RunMain - -simply open the file /home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtk.py -and comment line 47! +## How to install tripyview in blender: + +1. download and unpack Blender src code archive e.g. blender-4.2.2-linux-x64.tar.xz + +2. unzip folder + +3. go to directory /blender-4.2.2-linux-x64/4.2/python/bin/ and execute (make + sure that the entire path to blenders python3.11 is used) + + ```bash + ../blender-4.2.2-linux-x64/4.2/python/bin/python3.11 -m pip -e install /path_to_tripyview/ + ``` + This should install tripyview in the python version that blender is using + +4. test if tripyview installation works: + + ```bash + ../blender-4.2.2-linux-x64/4.2/python/bin/python3.11 -c "import tripyview" + ``` +5. open Blender + +6. go to the Scripting tab and load the script do_blenderplanet_fesom2.py + make sure all the path are correct + +7. run that script!!! + +8. There might occure the error message where the blender interface colides with pyvista: + + ```bash + Python: Traceback (most recent call last): + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/home/pscholz/Python/tripyview/tripyview/__init__.py", line 25, in + from .sub_3dsphere import * + File "/home/pscholz/Python/tripyview/tripyview/sub_3dsphere.py", line 3, in + import vtk + File "/home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtk.py", line 47, in + from vtkmodules.vtkRenderingMatplotlib import * + ImportError: /home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtkmodules/libvtkPythonInterpreter-9.3.so: undefined symbol: Py_RunMain + ``` + simply open the file /home/pscholz/Software/blender-4.2.2-linux-x64/4.2/python/lib/python3.11/site-packages/vtk.py + and comment line 47! diff --git a/tools/blender/do_blenderplanet_fesom2.py b/tools/blender/do_blenderplanet_fesom2.py index 49608b5..4454af0 100644 --- a/tools/blender/do_blenderplanet_fesom2.py +++ b/tools/blender/do_blenderplanet_fesom2.py @@ -16,32 +16,44 @@ from sub_blender import blender_create_mesh, blender_create_txtremat R_earth = 6371.0e3 # meter -potatoefac_ocean = 0.0 -potatoefac_land = 0.0 -gen_path = '/home/pscholz/Python/blender_fesom2/' - -mesh_path, do_rot = '/home/pscholz/Python/blender_fesom2/data/core2_srt_dep@node/', 'None' - -do_oce_data = False -oce_txturepath = gen_path+'texture/Albedo.jpg' -oce_alphamap = gen_path+'lsmask/core2_lsmask_ocean.jpg' - -oce_datapath = '/home/pscholz/Python/blender_fesom2/data/' -oce_vname = 'ssh' -oce_year = [1958, 1958] -oce_mon = None -oce_day = None -oce_depth = None - -oce_bumpmap = gen_path+'texture/Bump.jpg' -oce_bumpstrength = 0.5 +# real extrusion for ocean and land when !=0 +potatoefac_ocean = 0.0 +potatoefac_land = 0.0 + +# main path of data for blender project is used now the structure +# blender_fesom2/ +# |--data can contain fesom data and mesh folder +# | |--mesh +# |--texture contains texture and bump images +# |--lsmask contains land sea mask of original mesh to mask out regions +# | from texture files +# |--topo folder with etopo1 data +gen_path = '/home/pscholz/Python/blender_fesom2/' + +mesh_path, do_rot = gen_path+'data/core2_srt_dep@node/', 'None' + +oce_txturepath = gen_path+'texture/Albedo.jpg' +oce_alphamap = gen_path+'lsmask/core2_lsmask_ocean.jpg' +oce_bumpmap = gen_path+'texture/Bump.jpg' +oce_bumpstrength = 0.5 + +# load fesom2 data from path, if None no data are loaded +oce_datapath = None # gen_path+'/data/' +oce_vname = 'ssh' +oce_year = [1958, 1958] +oce_mon = None +oce_day = None +oce_depth = None land_txturepath = gen_path+'texture/Albedo.jpg' land_alphamap = gen_path+'lsmask/core2_lsmask_ocean.jpg' land_bumpmap = gen_path+'texture/Bump.jpg' land_bumpstrength = 0.5 +# load topographic information to interpolate on land mesh part, to give it a +# real extrusion. I used here very coarse etopo1 data. It is maybe more efficient +# to do a fake extrusion via bump maps topo_path, do_topo = gen_path+'topo/topo_1deg.nc', True topo_varname, topo_dimname = 'topo', ['lon','lat'] topo_resol = 1 From f12610c03666e8612b8a62eca1a9a895b55dc45c Mon Sep 17 00:00:00 2001 From: Patrick Scholz Date: Fri, 4 Oct 2024 16:31:35 +0200 Subject: [PATCH 3/3] improve readme --- tools/blender/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/blender/README.md b/tools/blender/README.md index e77c252..f2434a6 100644 --- a/tools/blender/README.md +++ b/tools/blender/README.md @@ -1,9 +1,14 @@ ## How to install tripyview in blender: -1. download and unpack Blender src code archive e.g. blender-4.2.2-linux-x64.tar.xz +1. go into the folder of your choice and download tripyview with + + ```bash + git clone https://github.com/FESOM/tripyview.git + ``` -2. unzip folder +2. download and unpack Blender src code archive from https://www.blender.org/download/ + and unzip the archive (e.g. blender-4.2.2-linux-x64.tar.xz) 3. go to directory /blender-4.2.2-linux-x64/4.2/python/bin/ and execute (make sure that the entire path to blenders python3.11 is used)