def test_no_duplicates(): def sphere(x, y, z): return np.sqrt((x - 4)**2 + (y - 4)**2 + (z - 4)**2) - 4 vertices, _ = mcubes.marching_cubes_func((2, 2, 2), (9, 9, 9), 20, 20, 20, sphere, 0) assert len(vertices) == len(np.unique(vertices, axis=0))
def sphere2(): # Create the volume f = lambda x, y, z: x**2 + y**2 + z**2 # Extract the 16-isosurface vertices, triangles = mcubes.marching_cubes_func((-10,-10,-10), (10,10,10),100, 100, 100, f, 16) # Export the result to sphere2.dae mcubes.export_mesh(vertices, triangles, "sphere2.dae", "MySphere")
def evaluate(dimX,dimY,dimZ): vertices, triangles = mcubes.marching_cubes_func( (boundingArea['minX'],boundingArea['minY'],boundingArea['minZ']), (boundingArea['maxX'], boundingArea['maxY'], boundingArea['maxZ']), # Bounds dimX, dimY, dimZ, # Number of samples in each dimension implicit, # Implicit function 0) # Isosurface value # Export the result to sphere2.dae mcubes.export_mesh(vertices, triangles, "/Users/Emscape/Documents/Blender Projects/result.dae", "MLS result") print("Done. Result saved in 'result.dae'.")
def test_invalid_input(): def func(x, y, z): return x**2 + y**2 + z**2 - 1 mcubes.marching_cubes_func((-1.5, -1.5, -1.5), (1.5, 1.5, 1.5), 10, 10, 10, func, 0) with pytest.raises(ValueError): mcubes.marching_cubes_func((0, 0, 0), (0, 0, 0), 10, 10, 10, func, 0) with pytest.raises(ValueError): mcubes.marching_cubes_func((-1.5, -1.5, -1.5), (1.5, 1.5, 1.5), 1, 10, 10, func, 0) with pytest.raises(Exception): mcubes.marching_cubes_func((-1.5, -1.5), (1.5, 1.5, 1.5), 10, 10, 10, func, 0) with pytest.raises(Exception): mcubes.marching_cubes_func((-1.5, -1.5, -1.5), (1.5, 1.5), 10, 10, 10, func, 0)
def test_sphere(): x, y, z = np.mgrid[:100, :100, :100] u = (x - 50)**2 + (y - 50)**2 + (z - 50)**2 - 25**2 def func(x, y, z): return (x - 50)**2 + (y - 50)**2 + (z - 50)**2 - 25**2 vertices1, triangles1 = mcubes.marching_cubes(u, 0.0) vertices2, triangles2 = mcubes.marching_cubes_func( (0, 0, 0), (99, 99, 99), 100, 100, 100, func, 0.0) assert_allclose(vertices1, vertices2) assert_array_equal(triangles1, triangles2)
def main(): logging.info("Example 2: Isosurface in Python function...") logging.info("(this might take a while...)") samples = 200 bounds = 8 t = time.time() # Extract the 16-isosurface vertices, triangles = mcubes.marching_cubes_func( (-0, -0, -0), (bounds, bounds, bounds), samples, samples, samples, f, # Implicit function 16) # Isosurface value logging.info("mesh completed in %f seconds" % (time.time() - t)) logging.debug("vertices: {} length: {}".format(type(vertices), len(vertices))) logging.debug(vertices) logging.debug("triangles: {} length: {}".format(type(triangles), len(triangles))) logging.debug(triangles) meshexport.export(vertices, triangles)
def get_plot(self, var, value, plottype, colorscale="Viridis", sym="F", log="F", vmin="", vmax=""): ''' Return a plotly figure object for the plottype requested. var, value, and plottype are required variables. -var = name of plot variable -value = position of slice or isosurface value -plottype = 2D-alt, 2D-lon, 2D-lat, 3D-alt, iso -colorscale = Viridis [default], Cividis, Rainbow, or BlueRed -sym = F [default] for symetric colorscale around 0 -log = F [default] for log10() of plot value -vmin,vmax = minimum and maximum value for contour values, empty is min/max ''' # Common code blocks for all plots txtbot = "Model: GITM v" + str(self.codeversion) + ", Run: " + self.runname units=self.variables[var]['units'] txtbar = var + " [" + units + "]" time=self.dtvalue.strftime("%Y/%m/%d %H:%M:%S UT") if plottype == "2D-alt": # Check if altitude entered is valid if value < self.altmin or value > self.altmax: print('Altitude is out of range: alt=',value,\ ' min/max=',self.altmin,'/',self.altmax) return # Set grid for plot altkm=value/1000. ilon = np.linspace(0, 360, 361) ilat = np.linspace(-90, 90, 181) ialt = np.array([value]) xx, yy = np.meshgrid(np.array(ilon), np.array(ilat)) grid = np.ndarray(shape=(np.size(np.reshape(xx,-1)),3), dtype=np.float32) grid[:,0] = np.reshape(xx,-1) grid[:,1] = np.reshape(yy,-1) grid[:,2] = value test = self.variables[var]['interpolator'](grid) result = np.reshape(test,(ilat.shape[0],ilon.shape[0])) if log == "T": txtbar = "log<br>"+txtbar result = np.log10(result) if sym == "T": cmax = np.max(np.absolute(result)) if vmax != "": cmax = abs(float(vmax)) if vmin != "": cmax = max(cmax,abs(float(vmin))) cmin = -cmax else: cmax = np.max(result) cmin = np.min(result) if vmax != "": cmax = float(vmax) if vmin != "": cmin = float(vmin) def plot_var(lon = ilon, lat = ilat): return result plotvar = Kamodo(plot_var = plot_var) fig = plotvar.plot(plot_var = dict()) #fig.update_xaxes(nticks=7,title_text="",scaleanchor='y') fig.update_xaxes(tick0=0.,dtick=45.,title_text="") fig.update_yaxes(tick0=0.,dtick=45,title_text="") if colorscale == "BlueRed": fig.update_traces(colorscale="RdBu", reversescale=True) elif colorscale == "Rainbow": fig.update_traces( colorscale=[[0.00, 'rgb(0,0,255)'], [0.25, 'rgb(0,255,255)'], [0.50, 'rgb(0,255,0)'], [0.75, 'rgb(255,255,0)'], [1.00, 'rgb(255,0,0)']] ) else: fig.update_traces(colorscale=colorscale) fig.update_traces( zmin=cmin, zmax=cmax, ncontours=201, colorbar=dict(title=txtbar, tickformat=".3g"), contours=dict(coloring="fill", showlines=False) ) if log == "T": fig.update_traces( hovertemplate="Lon: %{x:.0f}<br>Lat: %{y:.0f}<br><b>log("+var+"): %{z:.4g}</b><extra></extra>" ) else: fig.update_traces( hovertemplate="Lon: %{x:.0f}<br>Lat: %{y:.0f}<br><b>"+var+": %{z:.4g}</b><extra></extra>" ) fig.update_layout( title=dict(text="Altitude="+"{:.0f}".format(altkm)+" km, Time = " + time, yref="container", yanchor="top", y=0.95), title_font_size=16, annotations=[ dict(text="Lon [degrees]", x=0.5, y=-0.13, showarrow=False, xref="paper", yref="paper", font=dict(size=12)), dict(text="Lat [degrees]", x=-0.1, y=0.5, showarrow=False, xref="paper", yref="paper", font=dict(size=12), textangle=-90), dict(text=txtbot, x=0.0, y=0.0, ax=0, ay=0, xanchor="left", xshift=-65, yshift=-42, xref="paper", yref="paper", font=dict(size=16, family="sans serif", color="#000000")) ], height=340 ) return fig if plottype == "2D-lat": # Check if latitude entered is valid if value < -90. or value > 90.: print('Latitude is out of range: lat=',value,' min/max= -90./90.') return # Set grid for plot ilon = np.linspace(0, 360, 361) ilat = np.array([value]) ialt = np.linspace(self.altmin, self.altmax, 300) xx, yy = np.meshgrid(np.array(ilon), np.array(ialt)) grid = np.ndarray(shape=(np.size(np.reshape(xx,-1)),3), dtype=np.float32) grid[:,0] = np.reshape(xx,-1) grid[:,1] = value grid[:,2] = np.reshape(yy,-1) test = self.variables[var]['interpolator'](grid) result = np.reshape(test,(ialt.shape[0],ilon.shape[0])) if log == "T": txtbar = "log<br>"+txtbar result = np.log10(result) if sym == "T" and vmin == "" and vmax == "": cmax = np.max(np.absolute(result)) cmin = -cmax else: cmax = np.max(result) cmin = np.min(result) if vmax != "": cmax = float(vmax) if vmin != "": cmin = float(vmin) ialt = ialt/1000. def plot_var(lon = ilon, alt = ialt): return result plotvar = Kamodo(plot_var = plot_var) fig = plotvar.plot(plot_var = dict()) #fig.update_xaxes(nticks=7,title_text="",scaleanchor='y') fig.update_xaxes(tick0=0.,dtick=45.,title_text="") fig.update_yaxes(title_text="") if colorscale == "BlueRed": fig.update_traces(colorscale="RdBu", reversescale=True) elif colorscale == "Rainbow": fig.update_traces( colorscale=[[0.00, 'rgb(0,0,255)'], [0.25, 'rgb(0,255,255)'], [0.50, 'rgb(0,255,0)'], [0.75, 'rgb(255,255,0)'], [1.00, 'rgb(255,0,0)']] ) else: fig.update_traces(colorscale=colorscale) fig.update_traces( zmin=cmin, zmax=cmax, ncontours=201, colorbar=dict(title=txtbar, tickformat=".3g"), contours=dict(coloring="fill", showlines=False) ) if log == "T": fig.update_traces( hovertemplate="Lon: %{x:.0f}<br>Alt: %{y:.0f}<br><b>log("+var+"): %{z:.4g}</b><extra></extra>" ) else: fig.update_traces( hovertemplate="Lon: %{x:.0f}<br>Alt: %{y:.0f}<br><b>"+var+": %{z:.4g}</b><extra></extra>" ) fig.update_layout( title=dict(text="Latitude="+"{:.1f}".format(value)+" degrees, Time = " + time, yref="container", yanchor="top", y=0.95), title_font_size=16, annotations=[ dict(text="Lon [degrees]", x=0.5, y=-0.13, showarrow=False, xref="paper", yref="paper", font=dict(size=12)), dict(text="Altitude [km]", x=-0.1, y=0.5, showarrow=False, xref="paper", yref="paper", font=dict(size=12), textangle=-90), dict(text=txtbot, x=0.0, y=0.0, ax=0, ay=0, xanchor="left", xshift=-65, yshift=-42, xref="paper", yref="paper", font=dict(size=16, family="sans serif", color="#000000")) ], height=340 ) return fig if plottype == "2D-lon": # Check if longitude entered is valid if value < 0. or value > 360.: print('Latitude is out of range: lat=',value,' min/max= 0./360.') return # Set grid for plot ilon = np.array([value]) ilat = np.linspace(-90, 90, 181) ialt = np.linspace(self.altmin, self.altmax, 300) xx, yy = np.meshgrid(np.array(ilat), np.array(ialt)) grid = np.ndarray(shape=(np.size(np.reshape(xx,-1)),3), dtype=np.float32) grid[:,0] = value grid[:,1] = np.reshape(xx,-1) grid[:,2] = np.reshape(yy,-1) test = self.variables[var]['interpolator'](grid) result = np.reshape(test,(ialt.shape[0],ilat.shape[0])) if log == "T": txtbar = "log<br>"+txtbar result = np.log10(result) if sym == "T" and vmin == "" and vmax == "": cmax = np.max(np.absolute(result)) cmin = -cmax else: cmax = np.max(result) cmin = np.min(result) if vmax != "": cmax = float(vmax) if vmin != "": cmin = float(vmin) ialt = ialt/1000. def plot_var(lat = ilat, alt = ialt): return result plotvar = Kamodo(plot_var = plot_var) fig = plotvar.plot(plot_var = dict()) #fig.update_xaxes(nticks=7,title_text="",scaleanchor='y') fig.update_xaxes(tick0=0.,dtick=30.,title_text="") fig.update_yaxes(title_text="") if colorscale == "BlueRed": fig.update_traces(colorscale="RdBu", reversescale=True) elif colorscale == "Rainbow": fig.update_traces( colorscale=[[0.00, 'rgb(0,0,255)'], [0.25, 'rgb(0,255,255)'], [0.50, 'rgb(0,255,0)'], [0.75, 'rgb(255,255,0)'], [1.00, 'rgb(255,0,0)']] ) else: fig.update_traces(colorscale=colorscale) fig.update_traces( zmin=cmin, zmax=cmax, ncontours=201, colorbar=dict(title=txtbar, tickformat=".3g"), contours=dict(coloring="fill", showlines=False) ) if log == "T": fig.update_traces( hovertemplate="Lat: %{x:.0f}<br>Alt: %{y:.0f}<br><b>log("+var+"): %{z:.4g}</b><extra></extra>" ) else: fig.update_traces( hovertemplate="Lat: %{x:.0f}<br>Alt: %{y:.0f}<br><b>"+var+": %{z:.4g}</b><extra></extra>" ) fig.update_layout( title=dict(text="Longitude="+"{:.1f}".format(value)+" degrees, Time = " + time, yref="container", yanchor="top", y=0.95), title_font_size=16, annotations=[ dict(text="Lat [degrees]", x=0.5, y=-0.13, showarrow=False, xref="paper", yref="paper", font=dict(size=12)), dict(text="Altitude [km]", x=-0.1, y=0.5, showarrow=False, xref="paper", yref="paper", font=dict(size=12), textangle=-90), dict(text=txtbot, x=0.0, y=0.0, ax=0, ay=0, xanchor="left", xshift=-65, yshift=-42, xref="paper", yref="paper", font=dict(size=16, family="sans serif", color="#000000")) ], height=340 ) return fig if plottype == "3D-alt": # Check if altitude entered is valid if value < self.altmin or value > self.altmax: print('Altitude is out of range: alt=',value,\ ' min/max=',self.altmin,'/',self.altmax) return # Set grid for plot altkm=value/1000. ilon = np.linspace(0, 360, 361) ilat = np.linspace(-90, 90, 181) ialt = np.array([value]) xx, yy = np.meshgrid(np.array(ilon), np.array(ilat)) grid = np.ndarray(shape=(np.size(np.reshape(xx,-1)),3), dtype=np.float32) grid[:,0] = np.reshape(xx,-1) grid[:,1] = np.reshape(yy,-1) grid[:,2] = value test = self.variables[var]['interpolator'](grid) result = np.reshape(test,(ilat.shape[0],ilon.shape[0])) if log == "T": txtbar = "log<br>"+txtbar result = np.log10(result) r = value + 6.3781E6 x=-(r*np.cos(yy*np.pi/180.)*np.cos(xx*np.pi/180.))/6.3781E6 y=-(r*np.cos(yy*np.pi/180.)*np.sin(xx*np.pi/180.))/6.3781E6 z= (r*np.sin(yy*np.pi/180.))/6.3781E6 if sym == "T" and vmin == "" and vmax == "": cmax = np.max(np.absolute(result)) cmin = -cmax else: cmax = np.max(result) cmin = np.min(result) if vmax != "": cmax = float(vmax) if vmin != "": cmin = float(vmin) def plot_var(x = x, y = y, z = z): return result plotvar = Kamodo(plot_var = plot_var) fig = plotvar.plot(plot_var = dict()) fig.update_scenes(xaxis=dict(title=dict(text="X [Re]")), yaxis=dict(title=dict(text="Y [Re]")), zaxis=dict(title=dict(text="Z [Re]"))) if colorscale == "BlueRed": fig.update_traces(colorscale="RdBu", reversescale=True) elif colorscale == "Rainbow": fig.update_traces( colorscale=[[0.00, 'rgb(0,0,255)'], [0.25, 'rgb(0,255,255)'], [0.50, 'rgb(0,255,0)'], [0.75, 'rgb(255,255,0)'], [1.00, 'rgb(255,0,0)']] ) else: fig.update_traces(colorscale=colorscale) fig.update_traces( cmin=cmin, cmax=cmax, colorbar=dict(title=txtbar, tickformat=".3g") ) fig.update_traces( hovertemplate="X [Re]: %{x:.3f}<br>Y [Re]: %{y:.3f}<br>Z [Re]: %{z:.3f}<extra></extra>" ) fig.update_layout( title=dict(text="Altitude="+"{:.0f}".format(altkm)+" km, Time = " + time, yref="container", yanchor="top", x=0.01, y=0.95), title_font_size=16, annotations=[ dict(text=txtbot, x=0.0, y=0.0, ax=0, ay=0, xanchor="left", xshift=0, yshift=-20, xref="paper", yref="paper", font=dict(size=16, family="sans serif", color="#000000")) ], margin=dict(l=0), width=600 ) x1=[0., 0.] y1=[0., 0.] z1=[-1.2, 1.2] fig.add_scatter3d(mode='lines',x=x1,y=y1,z=z1,line=dict(width=4,color='black'), showlegend=False,hovertemplate='Polar Axis<extra></extra>') r = value + 10000. + 6.3781E6 x2=-(r*np.cos(ilon*np.pi/180.))/6.3781E6 y2=-(r*np.sin(ilon*np.pi/180.))/6.3781E6 z2=0.*ilon fig.add_scatter3d(mode='lines',x=x2,y=y2,z=z2,line=dict(width=2,color='black'), showlegend=False,hovertemplate='Equator<extra></extra>') x3=-(r*np.cos(ilat*np.pi/180.))/6.3781E6 y3=0.*ilat z3=(r*np.sin(ilat*np.pi/180.))/6.3781E6 fig.add_scatter3d(mode='lines',x=x3,y=y3,z=z3,line=dict(width=2,color='black'), showlegend=False,hovertemplate='prime meridian<extra></extra>') return fig if plottype == "iso": # Check if value entered is valid (checking before possible log scale) cmin=np.min(self.variables[var]['data']) cmax=np.max(self.variables[var]['data']) if value < cmin or value > cmax: print('Iso value is out of range: iso=',value,' min/max=',cmin,cmax) sys.exit("Exiting ...") return # Determine altitude start, stop, number for use in isosurface step=10000. # 10000. m altitude step size alt1=(step*round(self.alt[2]/step)) alt2=self.alt[(self.alt.shape[0]-3)] nalt=round((alt2-alt1)/step) alt2=alt1+step*nalt nalt=1+int(nalt) # Set function for interpolation gridp = np.ndarray(shape=(1,3), dtype=np.float32) def finterp(x,y,z): gridp[0,:] = [x,y,z] return self.variables[var]['interpolator'](gridp) # Extract the isosurface as vertices and triangulated connectivity import mcubes verts, tri = mcubes.marching_cubes_func( (0, -90, alt1), (360, 90, alt2), # Bounds (min:x,y,z), (max:x,y,z) 73, 37, nalt, # Number of samples in each dimension finterp, # Implicit function of x,y,z value) # Isosurface value # Process output for creating plots, including face or vertex colors X, Y, Z = verts[:,:3].T I, J, K = tri.T # Update cmin, cmax for log, sym, vmin, vmax values if set if sym == "T": if vmax != "": cmax = abs(float(vmax)) if vmin != "": cmax = max(cmax,abs(float(vmin))) cmin = -cmax else: if vmax != "": cmax = float(vmax) if vmin != "": cmin = float(vmin) dvalue=value if log == "T": dvalue=np.log10(value) cmin=np.log10(cmin) cmax=np.log10(cmax) txtbar = "log<br>"+txtbar # Create fig and update with modifications to customize plot fig=go.Figure(data=[go.Mesh3d( name='ISO', x=X, y=Y, z=Z, i=I, j=J, k=K, intensity=np.linspace(dvalue,dvalue,X.shape[0]), cmin=cmin, cmax=cmax, showscale=True, opacity=0.6, colorbar=dict(title=txtbar, tickformat=".3g"), hovertemplate="Isosurface<br>"+var+"="+"{:.3g}".format(value)+" "+units+"<br><extra></extra>", )]) if colorscale == "BlueRed": fig.update_traces(colorscale="RdBu", reversescale=True) elif colorscale == "Rainbow": fig.update_traces( colorscale=[[0.00, 'rgb(0,0,255)'], [0.25, 'rgb(0,255,255)'], [0.50, 'rgb(0,255,0)'], [0.75, 'rgb(255,255,0)'], [1.00, 'rgb(255,0,0)']] ) else: fig.update_traces(colorscale=colorscale) fig.update_scenes( xaxis=dict(title=dict(text="Lon [degrees]"),tick0=0.,dtick=45.), yaxis=dict(title=dict(text="Lat [degrees]"),tick0=0.,dtick=45.), zaxis=dict(title=dict(text="Alt [km]")) ) fig.update_layout( scene_camera_eye=dict(x=.1, y=-1.8, z=1.5), scene_aspectmode='manual', scene_aspectratio=dict(x=2, y=1, z=1), scene_xaxis=dict(range=[0,360]), scene_yaxis=dict(range=[-90,90]), scene_zaxis=dict(range=[alt1,alt2]), title=dict(text="Isosurface of "+var+"="+\ "{:.3g}".format(value)+" "+units+"<br>"+"Time = "+time, yref="container", yanchor="top", x=0.01, y=0.95), title_font_size=16, annotations=[ dict(text=txtbot, x=0.0, y=0.0, ax=0, ay=0, xanchor="left", xshift=0, yshift=-20, xref="paper", yref="paper", font=dict(size=16, family="sans serif", color="#000000")) ], margin=dict(l=10,t=80), ) return fig if plottype == "iso1": # This method keeps all the data local, resulting in huge storage # as well as corrupted html divs. ilon = np.linspace(0, 360, 73) #181 ilat = np.linspace(-90, 90, 37) #91 step=10000. # 5000. alt1=(step*round(self.alt[2]/step)) alt2=self.alt[(self.alt.shape[0]-3)] nalt=round((alt2-alt1)/step) alt2=alt1+step*nalt nalt=1+int(nalt) ialt = np.linspace(alt1, alt2, nalt) xx,yy,zz = np.meshgrid(np.array(ilon),np.array(ilat),np.array(ialt)) grid = np.ndarray(shape=(np.size(np.reshape(xx,-1)),3), dtype=np.float32) grid[:,0] = np.reshape(xx,-1) grid[:,1] = np.reshape(yy,-1) grid[:,2] = np.reshape(zz,-1) test = self.variables[var]['interpolator'](grid) result = np.reshape(test,(ilat.shape[0],ilon.shape[0],ialt.shape[0])) isovalue=value if log == "T": isovalue=np.log10(value) txtbar = "log<br>"+txtbar result = np.log10(result) if sym == "T": cmax = np.max(np.absolute(result)) if vmax != "": cmax = abs(float(vmax)) if vmin != "": cmax = max(cmax,abs(float(vmin))) cmin = -cmax else: cmax = np.max(result) cmin = np.min(result) if vmax != "": cmax = float(vmax) if vmin != "": cmin = float(vmin) # Check if value entered is valid (checking before possible log scale) if value < np.min(test) or value > np.max(test): print('Iso value is out of range: iso=',value,\ ' min/max=',np.min(test),'/',np.max(test)) sys.exit("Exiting ...") return slicevalue=0. fig1 = go.Figure(data=go.Isosurface( x=xx.flatten(), y=yy.flatten(), z=zz.flatten()/1000., value=result.flatten(), opacity=0.6, isomin=cmin, isomax=cmax, surface=dict(count=2, fill=1., pattern='all'), caps=dict(x_show=False, y_show=False, z_show=False), showscale=True, # show colorbar colorbar=dict(title=txtbar, tickformat=".3g"), slices_y=dict(show=True, locations=[slicevalue]), )) fig1.update_traces( hovertemplate="<b>Slice</b><br>Lon: %{x:.0f}<br>Lat: %{y:.0f}<br>Alt: %{z:.0f}km<br><extra></extra>" ) if colorscale == "BlueRed": fig1.update_traces(colorscale="RdBu", reversescale=True) elif colorscale == "Rainbow": fig1.update_traces( colorscale=[[0.00, 'rgb(0,0,255)'], [0.25, 'rgb(0,255,255)'], [0.50, 'rgb(0,255,0)'], [0.75, 'rgb(255,255,0)'], [1.00, 'rgb(255,0,0)']] ) else: fig1.update_traces(colorscale=colorscale) fig2 = go.Figure(data=go.Isosurface( x=xx.flatten(), y=yy.flatten(), z=zz.flatten()/1000., value=result.flatten(), opacity=1., colorscale=[[0.0, '#777777'],[1.0, '#777777']], isomin=isovalue, isomax=isovalue, surface=dict(count=1, fill=1., pattern='all'), caps=dict(x_show=False, y_show=False, z_show=False), showscale=False, # remove colorbar hovertemplate="<b>Isosurface</b><br>Lon: %{x:.0f}<br>Lat: %{y:.0f}<br>Alt: %{z:.0f}km<extra></extra>" )) fig2.update_scenes( xaxis=dict(title=dict(text="Lon [degrees]"),tick0=0.,dtick=45.), yaxis=dict(title=dict(text="Lat [degrees]"),tick0=0.,dtick=45.), zaxis=dict(title=dict(text="Alt [km]")) ) fig2.update_layout( scene_camera_eye=dict(x=.1, y=-1.8, z=1.5), scene_aspectmode='manual', scene_aspectratio=dict(x=2, y=1, z=1), title=dict(text="Latitude="+"{:.0f}".format(slicevalue)+ " slice through the data<br>"+ "Isosurface of "+var+"="+"{:.0f}".format(isovalue)+units+"<br>"+ "Time = " + time, yref="container", yanchor="top", x=0.01, y=0.95), title_font_size=16, annotations=[ dict(text=txtbot, x=0.0, y=0.0, ax=0, ay=0, xanchor="left", xshift=0, yshift=-20, xref="paper", yref="paper", font=dict(size=16, family="sans serif", color="#000000")) ], margin=dict(l=10,t=80), ) fig2.add_trace(fig1.data[0]) return fig2 print('Unknown plottype (',plottype,') returning.') return
print("Example 2: Isosurface in Python function...") print("(this might take a while...)") # Create the volume def f(x, y, z): return x**2 + y**2 + z**2 # Extract the 16-isosurface vertices2, triangles2 = mcubes.marching_cubes_func( (-10, -10, -10), (10, 10, 10), # Bounds 100, 100, 100, # Number of samples in each dimension f, # Implicit function 16) # Isosurface value # Export the result to sphere2.dae mcubes.export_mesh(vertices2, triangles2, "sphere2.dae", "MySphere") print("Done. Result saved in 'sphere2.dae'.") try: print("Plotting mesh...") from mayavi import mlab mlab.triangular_mesh(vertices1[:, 0], vertices1[:, 1], vertices1[:, 2], triangles1) print("Done.") mlab.show()
out triangles s """ import numpy as np import sys mcubes_path = r"/usr/local/lib/python3.5/dist-packages" #it depend on your OS but just paste the path where is scipy if not mcubes_path in sys.path: sys.path.append(mcubes_path) import mcubes import math # Create the volume def f(x, y, z): res = ((x - factor) * (x - factor) + y * y + z * z - 1) * ( (x + factor) * (x + factor) + y * y + z * z - 1) - 0.3 return res # Extract the 16-isosurface verts, tri = mcubes.marching_cubes_func( (-bounds, -bounds, -bounds), (bounds, bounds, bounds), # Bounds samples, samples, samples, # Number of samples in each dimension f, # Implicit function iso_val) # Isosurface value vertices, triangles = [verts.tolist()], [tri.tolist()]
def main(context): #1. Dimension of bounding box of object (USED TO SAMPLE 3D GRID) # ################################################## # #1. HERE THE BOUNDARIES OF THE OBJECT ARE COMPUTED --- name = context.active_object.data.name xDimensionsHalve = bpy.data.objects[name].dimensions.x/2 xMin = 0 - xDimensionsHalve xMax = 0 + xDimensionsHalve yDimensionsHalve = bpy.data.objects[name].dimensions.y/2 yMin = 0 - yDimensionsHalve yMax = 0 + yDimensionsHalve zDimensionsHalve = bpy.data.objects[name].dimensions.z/2 zMin = 0 - zDimensionsHalve zMax = 0 + zDimensionsHalve #2. COMPUTE MLS FUNCTION ########################## # ################################################## # #0. Define Help functions: # #Wendland weight function def Wendland ( r , h ): return (1 - r/h)**4*(4*r/h+1) #b^T for degree 0, 1 or 2. def ChooseB (degree, point): x = point[0] y = point[1] z = point[2] if degree == 0: A = np.matrix([[1]]) elif degree == 1: A = np.matrix([[1], [x], [y], [z], [x*y], [x*z], [y*z], [x*y*z]]) elif degree == 2: A = np.matrix([[1], [x], [y], [z], [x**2], [y**2], [z**2], [x*y], [x*z], [y*z], [x**2*y], [x**2*z], [x*y**2], [x*z**2], [y**2*z], [y*z**2], [x*y*z], [x**2*y*z], [x*y**2*z], [x*y*z**2], [x**2*y**2], [x**2*z**2], [y**2*z**2], [x**2*y**2*z], [x**2*y*z**2], [x*y**2*z**2], [x**2*y**2*z**2]]) return np.transpose(A) #Checks whether a point P is within a range of the given point C #which in 3D means is within the sphere with C as its center and the specified #, range as its radius def insideSphere(C, R, P): return ( P[0]- C[0] ) ** 2 + (P[1]- C[1]) ** 2 + (P[2]-C[2]) ** 2 < R**2 #Gets the appropriate length that matches to a degree k def matchingLengthofDegreeK(k): if k == 0: l = 1 elif k == 1: l = 8 elif k == 2: l = 27 return l #Can be used for visualization purposes def makeMaterial(name, diffuse, specular, alpha): mat = bpy.data.materials.new(name) mat.diffuse_color = diffuse mat.diffuse_shader = 'LAMBERT' mat.diffuse_intensity = 1.0 return mat def setMaterial(ob, mat): me = ob.data me.materials.append(mat) #1. SETUP EPSILON, POINTCLOUD POINTS (FIRST n POINTS), POINTCLOUD POINT NORMALS and N (NECESSARY VARIABLES FOR COMPUTING CONSTRAINT POINTS), POINTCLOUD CENTER --- # #Computes N (amount of points in pointCloud) PointCloud_points = [] PointCloud_points = context.active_object.data.vertices N = len(PointCloud_points ) #Computes Normals of point cloud points PointCloud_point_normals = [] for n in context.active_object['vertex_normal_list']: PointCloud_point_normals.append(np.array([n[0], n[1], n[2]])) #Fixes an ε value, for instance ε = 0.01 times the diagonal of the bounding box to the object. EXPERIMENTABLE PARAMETER v1 = np.array((xMin, yMin,zMax)) v2 = np.array((xMax, yMax,zMin)) diagonalOfBoundingBox = np.linalg.norm(v1-v2) epsilon = 0.01 * diagonalOfBoundingBox #2. IMPLEMENT SPATIAL INDEX: KD-TREE --- # #Computes a Spatial Index: KD-TREE from the PointCloud_points #(for faster nearest neighborhood calculations) kd = mathutils.kdtree.KDTree(N) for index, point in enumerate(PointCloud_points): kd.insert(point.co, index) #Must have been called before using any of the kd.find methods. kd.balance() #Compute the origin of the axis aligned bounding box origin = mathutils.Vector((0,0,0)) for index, point in enumerate(PointCloud_points): origin += point.co origin /= N polyDegree = 0 def MLS(x , y ,z): P = np.array([x , y, z]) #Get closest points Pi within wendland radius from P. EXPERIMENTABLE PARAMETER wendlandRadius = diagonalOfBoundingBox/20 pointsWithinRange = kd.find_range(P, wendlandRadius) numpyPointsWithinRange = [] redPoints = [] redPointsEpsilonValues = [] greenPoints = [] greenPointsEpsilonValues = [] for (co,index, dist) in pointsWithinRange: Ni = PointCloud_point_normals[index] pos = co PiNumpy = np.array((pos[0], pos[1], pos[2])) epsilonBackup = epsilon result = PiNumpy + epsilonBackup*Ni #Range to look for closest points from Pi+N. EXPERIMENTABLE PARAMETER Range = diagonalOfBoundingBox/20 Pi_Not_Closest = True #Change datatype of Pi, to make computations easier Pi = pos #Check whether Pi is the closest point to Pi+N while Pi_Not_Closest : #Calculates distances between Pi+N = result and the points that are within a range distance from Pi+N dist, closestPos = min([(np.linalg.norm(result - co), co) for (co,index, dist) in kd.find_range(result, Range)]) #If Pi is not the closest point to Pi+N, # divide ε by 2 and recompute pi+N until this is the case if closestPos != Pi: epsilonBackup /= 2 result = Pi + epsilonBackup * Ni else: Pi_Not_Closest = False break #check whether the green and red point are within wendlandRadius of P if insideSphere(C = P, R = wendlandRadius, P = result - 2*(epsilonBackup * Ni)): greenPoints.append(result - 2*(epsilonBackup * Ni)) greenPointsEpsilonValues.append(- epsilonBackup) if insideSphere(C = P, R = wendlandRadius ,P = result): redPoints.append((result)) redPointsEpsilonValues.append(epsilonBackup) numpyPointsWithinRange.append(PiNumpy) #Create lists of the Pi's and Di's belonging to P PointsPi = numpyPointsWithinRange + redPoints + greenPoints PointsDi = [0]*len(pointsWithinRange) + redPointsEpsilonValues + greenPointsEpsilonValues #sqrt(ti) = sqrt(theta(|P-Pi|)) (see MLS reference sheet: http://www.cs.uu.nl/docs/vakken/ddm/MLS%20Reference%20Sheet.pdf) sqrtTiValues = [] #smoothing value for wendland weight function. EXPERIMENTABLE PARAMETER h = 1 for Pi in PointsPi: value = np.sqrt(Wendland(np.linalg.norm(P-Pi), h)) sqrtTiValues.append(value) # A is a one-column matrix filled by sqrtTi multiplied with b^T A = np.empty((0,matchingLengthofDegreeK(polyDegree))) for index, sqrtTi in enumerate(sqrtTiValues): #Here we choose B for a degree 0, 1 or 2. EXPERIMENTABLE PARAMETER bT = ChooseB(polyDegree, PointsPi[index]) value = sqrtTi*bT A = np.insert(A, index, value, 0) # r = sqrtTi_x_Di r = np.empty((0, len(sqrtTiValues))) for index, sqrtTi in enumerate(sqrtTiValues): r = np.insert(r, index, sqrtTi * PointsDi[index]) if(len(A) != 0): A_T = np.transpose(A) A_T_x_A = np.dot(A_T, A) A_T_x_r = np.dot(A_T, r) #a = (A^T*A)^-1 * A^T*r a = np.dot(np.linalg.inv(A_T_x_A), A_T_x_r) #Finally add a to the samples return np.dot(ChooseB(polyDegree, [x,y,z]), a) else: return 10000 def f(x, y, z): return MLS(x, y, z) #4. INPUTS SAMPLED GRID TO MARCHING CUBES PLUGIN --- # lowerLeft = origin - mathutils.Vector((diagonalOfBoundingBox/2*0.9, diagonalOfBoundingBox/2*0.9, diagonalOfBoundingBox/2*0.9)) upperRight = origin + mathutils.Vector((diagonalOfBoundingBox/2*1.1, diagonalOfBoundingBox/2*1.1, diagonalOfBoundingBox/2*1.1)) vertices, triangles = mcubes.marching_cubes_func((lowerLeft[0], lowerLeft[1], lowerLeft[2]),(upperRight[0], upperRight[1], upperRight[2]), 100, 100, 100, f, 0) # Export the result mcubes.export_mesh(vertices, triangles, "C:\\Users\\jaswir\\Documents\\GameTechnology\\3DM\\3DM_Practical1\\DAE_Files\\Bunnyk1.dae", "Bunny_k1")
import numpy as np import sys import math import os try: mcubes_path = r"/usr/local/lib/python3.5/dist-packages" #it depend on your OS but just paste the path where is mcubes if not mcubes_path in sys.path: sys.path.append(mcubes_path) import mcubes except: os.system('pip3 install pymcubes') # Create the volume def f(x, y, z): return (z**3 / (math.sin(z*y+x)) + 3**x)**3 # Create a data volume (30 x 30 x 30) #X, Y, Z = np.mgrid[:100, :100, :100] # u = (X-50)**2 + (Y-50)**2 + (Z-50)**2 - 25**2 # Extract the 0-isosurface #verts, tri = mcubes.marching_cubes(u, 0) # Extract the 16-isosurface verts, tri = mcubes.marching_cubes_func( (-bounds, -bounds, -bounds), (bounds, bounds, bounds), # Bounds samples, samples, samples, # Number of samples in each dimension f, # Implicit function iso_val) # Isosurface value vertices, triangles = verts.tolist(), tri.tolist()
# Export the result to sphere.dae mcubes.export_mesh(vertices1, triangles1, "sphere1.dae", "MySphere") print("Done. Result saved in 'sphere1.dae'.") print("Example 2: Isosurface in Python function...") print("(this might take a while...)") # Create the volume def f(x, y, z): return x**2 + y**2 + z**2 # Extract the 16-isosurface vertices2, triangles2 = mcubes.marching_cubes_func( (-10,-10,-10), (10,10,10), # Bounds 100, 100, 100, # Number of samples in each dimension f, # Implicit function 16) # Isosurface value # Export the result to sphere2.dae mcubes.export_mesh(vertices2, triangles2, "sphere2.dae", "MySphere") print("Done. Result saved in 'sphere2.dae'.") try: print("Plotting mesh...") from mayavi import mlab mlab.triangular_mesh( vertices1[:, 0], vertices1[:, 1], vertices1[:, 2], triangles1) print("Done.") mlab.show()