Editor's Note: This file was selected as MATLAB Central Pick of the Week
SOLID_FV = SURF2SOLID(FV,...) takes in a triangulated patch defined by
FV (a structure with fields 'vertices' and 'faces'), and returns a
solid patch SOLID_FV closed by options (described below).
SOLID_FV = SURF2SOLID(F, V,...) takes faces and vertices separately.
[F,V] = SURF2SOLID(...) returns solid faces and vertices separately.
SURF2SOLID(...) with no output argument plots the 3 components
(origsurface, sidewalls, undersurface) to a new figure.
SURF2SOLID(X, Y, Z, ...) reads in surface data in X, Y, and Z matrices,
and triangulates this gridded data into a surface using triangulation
options specified below. Z must be a 2D matrix. X and Y can be 2D
matrices of the same size as Z, or vectors of length equal to SIZE(Z,2)
and SIZE(Z,1), respectively. If X or Y are scalar values, they are used
to specify the X and Y spacing between grid points.
SURF2SOLID(...,'PropertyName',VALUE,...) makes a solid volume from thin
surface using any of the following property/value options:
ELEVATION  Extends the surface down to a flat base at the given
(Z) elevation value. Useful for turning a thin
elevation map into a solid block with a flat base. The
ELEVATION value should be below the lowest (or above
the highest) data point. If no other options are given,
ELEVATION defaults to MIN(Z)0.1*(MAX(Z)MIN(Z)).
Variable ELEVATION may also be given perpoint, via a
2D matrix (the same size as Z for X,Y,Z style input) or
a 1D array (with length equal to the number of vertices
given in face/vertex input).
THICKNESS  Value to offset the given thin surface to make a
thickened solid slab. Each node on the surface will be
projected along its normal direction by thickness. When
negative thickness is given, offset will be away from
face normal direction. Variable thickness can also be
specified via a 2D matrix (of same size as Z, for X,Y,Z
input) or an Nby1 array of thicknesses (where N is
the number of vertices in the thin surface)
TRIANGULATION  When used with gridded data, TRIANGULATION is either:
'delaunay'  (default) Delaunay triangulation of X, Y
'f'  Forward slash division of grid quads
'b'  Back slash division of quadrilaterals
'x'  Cross division of quadrilaterals
Note that 'f', 'b', or 'x' triangulations use an
inbuilt version of FEX entry 28327, "mesh2tri". 'x'
style triangulation cannot be used with variable
ELEVATION or THICKNESS parameters.
NORMALS  When THICKNESS options is used, the direction to
thicken the surface is (by default) determined by the
surface (unit vector) normal directions at each vertex.
To override these default directions, you may specify
NORMALS as an Nby3 array of normal directions (where
N is the number of vertices in the thin surface). This
is useful when underlying data gives more precise
normal directions than face orienatations (for an
example, see the isonormals function).
Note 1: Currently surf2solid will return a closed surface with face
normals pointing "out". With user feedback, I'd be happy to change this
behaviour to either "in" or "unchanged from input direction".
Note 2: If a single ELEVATION value is specified (i.e., flat base), the
resulting patch will have minimal triangles on the flat base to reduce
patch/file size.
Example (shows both THICKNESS and ELEVATION forms):
n = 30;
[X,Y] = meshgrid(linspace(0,1,2*n+1));
L = (40/51/0.9)*membrane(1,n);
figure, subplot(2,2,[1 3]), title 'Thin surface'
surf(X,Y,L,'EdgeColor','none'); colormap pink; axis image; camlight
subplot(2,2,2), title 'Block elevation'
surf2solid(X,Y,L,'elevation',min(L(:))0.05); axis image; camlight; camlight
subplot(2,2,4), title 'Thickness'
surf2solid(X,Y,L,'thickness',0.1); axis image; camlight;
Original idea adapted from Paul Kassebaum's blog post
http://blogs.mathworks.com/community/2013/06/20/paulprintsthelshapedmembrane/
Many thanks to Paul for his further input and improvements.
Sven (2020). surf2solid  make a solid volume from a surface for 3D printing (https://www.mathworks.com/matlabcentral/fileexchange/42876surf2solidmakeasolidvolumefromasurfacefor3dprinting), MATLAB Central File Exchange. Retrieved .
1.5.0.0  Added optional normal direction input 

1.3.0.0  Added ability to solidify selfenclosing surfaces without boundary edges 

1.2.0.0  Added pernode elevation and thickness, minimalflatbase file size, and default face orientation "out". 

1.1.0.0  Set otherfaces as opposite norm direction to input faces

Create scripts with code, output, and formatted text in a single executable document.
Hi there, I have a point cloud, basically a large matrix of x,y,z, and would like to get the stl surface of this. I tried to use this code but I don;t know hot to actually convert the "solid" that it forms to a stl. Please help.
I am trying to make a 3D model of topographical data. I have a file of longitude, latitude and altitude that was generated by recording data as a vehicle drove across a field. I have converted it into regularly spaced x, y, z data using mechgrid and griddata. If I plot my results using surf I see my expected contours. If I try to convert it to a solid using surf2solid (or also using stlwrite mentioned) I get a couple of very tall lines, not a surface. I can share the x,y,z data if anyone has interest in helping.
Thanks in advance.
Hi,
Great code! Thanks. Can we please get an adaptation to include face normals pointing 'in'. I think this would help! I'm trying to make a stl of a sphere by adding two hemispheres together and I think setting one to in and one to out would work?
How do you save these files?
Code is fine but it cannot handle correctly mesh with multiple connexed boundary (hole)
n = 4;
x = linspace(1,1,n+1);
y = linspace(1,1,n+1);
[X,Y] = ndgrid(x,y);
Z = zeros(size(X));
X = [X(:), Y(:), Z(:)];
F1 = zeros(n^2,3);
F2 = zeros(n^2,3);
for i0=1:n
i = i0+[0,1];
for j0=1:n
j = j0+[0,1];
[I,J] = ndgrid(i,j);
k = sub2ind([size(x,2),size(y,2)],I,J);
p = (i01)*n + j0;
F1(p,:) = k([1 2 4]);
F2(p,:) = k([1 4 3]);
end
end
F = [F1;F2];
Xc = X(F,:);
Xc = reshape(Xc,[],3,3);
Xc = mean(Xc,2);
Xc_x = Xc(:,:,1);
Xc_y = Xc(:,:,2);
b = abs(Xc_x)>0.5  abs(Xc_y)>0.5;
F = F(b,:);
V = surf2solid(F, X, 'Thickness', 0.5);
fig = figure();
set(fig,'color','k'); % black background
ax = subplot(1,2,1,'Parent', fig);
hold(ax,'on');
axis(ax,'equal');
view(ax,3)
ax.Clipping = 'off';
ax.Visible = 'off';
patch(ax, 'Faces', F, 'vertices', X, 'FaceColor', 0.9+[0 0 0], 'EdgeColor', 'b');
ax = subplot(1,2,2,'Parent', fig);
hold(ax,'on');
axis(ax,'equal');
view(ax,3)
ax.Clipping = 'off';
ax.Visible = 'off';
patch(ax, 'Faces', V.faces, 'vertices', V.vertices, 'FaceColor', 0.9+[0 0 0], 'EdgeColor', 'b');
Excellent and inspiring work! Many thanks to Sven, Paul, and Charles!
Great function, thanks!
Back to Nicolas Alvarez's question. If my model is not regular shape, how can I make some portion of the surface with 0 thickness?
Thanks!
Hello again
@charles tenney can you share your full code here that with the the changes u mentioned, as i am getting lots of errors on the code if changed as per your comment. The surf2solid function gives me the boundaries on one side are connected to those on the other side badly to me on generating the solid from surface.
Hi all,
i found this article great and useful for me.
Currently i am trying to add thickness to a gyroid surface, but i get a improper volume.
And is there way to add the thickness as a funtion or an array of values?. so that i can create variable thicken gyroid.
Hi All,
Can someone please point me out how to write the converted solid as .stl file.
Thanks in advance,
Yasith
This script is pretty great. However, I had to edit it to make it work for my project.
It seems that the way the "wall" faces are calculated works well for a single perimeter. However, if there are two disconnected perimeters  such as the two circular ends of a hollow tube  the calculation can produce bad results.
To address this, I rewrote lines 164172 in surf2solid to use the connectivity list when constructing the wall faces to prevent connecting vertices that belonged to different perimeters:
% The vertex reference numbers are specified to match with the [V;
% V_extrude] matrix.
% Number of boundary edges (nbe).
nbe = size(boundEdges, 1);
% Number of vertices on each surface (nsv).
nsv = length(V);
% Allocate memory for wallFaces.
F_wall = zeros(2*nbe,3);
% Define the faces.
for k = 1:nbe
a = boundEdges(k, 1);
b = boundEdges(k, 2);
F_wall(k ,:) = [a+nsv,b ,a];
F_wall(k+nbe,:) = [a+nsv,b+nsv,b];
end
The changes I made also required altering the output vertices/faces (lines 208212):
% Compile 3 sets of faces together
allVertices = [V; V_extrude];
allFaces = [F; % Use original faces
F_wall; % Add wall faces
F_extrude+size(V,1)]; % Add opposite faces (flipped)
As well as line 222 (if plotting is desired):
patch('Faces',F_wall , 'Vertices',[V; V_extrude] , 'FaceColor','g');
I don't know if this code correctly orients the wall faces. It correctly orients them in my project, but I can't prove that it would do it correctly in general. I also don't know if it messes up the "elevation" option; I don't use that functionality in my project. .
@Philippe Mercier, My mistake, it seems the problem came from somewhere else, it works perfectly now. Thank you very much !
@Pilippe Mercier, @Rose Haft,
I'm sorry, I'm unable to reproduce the trouble that you are both experiencing. I can currently run the surf2solid examples using matlab 2018a and 2018b without error. Please include the error message that you receive so that we can understand what the problem could be.
@mvulp it looks like you're trying to use this function as a script. The best thing I can suggest is to start here:
https://www.mathworks.com/help/matlab/programminganddatatypes.html
You should never need to directly run ANY of the code *inside* the surf2solid function (or any other function). Instead, just call surf2solid as a function and provide your inputs.
This no longer seems to be working even from the example. I did have this running previously.
Hello,
When I found this code I thought I could solve all my problems, but I cannot run it.
I am new to matlab and never used any function downloaded from "file exchange".
I have 3 vectors X, Y and Z,which are respectively the xaxis, yaxis and the surface roughness values.
Ones I feed those 3 vectors into the surf2solid function I get the following error:
Undefined function or variable 'varargin'.
Error in surf2solid (line 98)
[F, V, options] = parseInputs(varargin{:});
And when I switch the varargin{:} with my vectors X Y Z, I get this other error.
Not enough input arguments.
Error in surf2solid (line 98)
[F, V, options] = parseInputs(A1,A2,A3);
Could you please guide me through it, I do not really know how to make it work but I extremely need it.
Thanks.
It's so cool! Thanks for your talent!
been using this thing for a while. author deserves to get a cookie. if i ever meet you i will give you a cookie.
Great function. Thanks!
@Nicholas I found that just eliminating the NaNs worked well. E.g. for matrices X,Y,Z where Z has NaNs:
ind=isnan(Z);
X(ind)=[];
Y(ind)=[];
Z(ind)=[];
@Nicholas, you could try inpaint_nans or 3d_inpaint_nans written by John D'Errico. They provide a choice of methods for substituting better values for NaNs. These functions can be found in the Matlab File Exchange (like this one). I have not used these particular functions but implemented something similar many years ago.
Hi Sven,
Nice work! I do have one issue. My shape is irregular and therefore has many points in the matrix that are empty (i.e. zero). I typically set these to NaN so that surf omits them. However, your code does not allow for NaN points. If I leave the points as "0", then surf2solid uses them to create the solid object. How do you suppose I work around the issue? Thanks in advance, Nicolas
Hi Sven,
I have used surf2solid to create a solid block of my NURBs surface. The thing is that at the trough portions of the surface, faces form and the surface looks more like it is bounded by walls on all sides rather than just having a block with a base.
Hi Sven (+ all MATLAB guru),
I have a question regarding setting/fixing the dimension in MATLAB for 3D printing that I hope you could advice me. I have read through your code (it is soooooo good!!), my question is if I want the thickness to be exactly 1mm and the width of the object to be printed to be 5cm in length, where/how do I specify the dimension. Do I do it for the initial surface plot first?
Many thanks for your time and wisdom shared.
Regards,
Kim
Hi Sven,
I am having problems using the thickness feature of surf2solid.
I understand that the thickness feature will only create one offset plane from the surface normal and extrude the space in between (Singlesided extrusion).
However, in my application, a singlesided extrusion would mess up the proportionality of the geometry. I would need a doublesided extrusion (or a midplane extrusion). In other words, is there a way to create two offset planes so that I can extrude in both positive and negative direction from the normal, so that the original surface of the geometry would be at the exact middle of the plane.
Thank you very much!
Alex
@Thalles,
I was just picking the particular vertices to remove from the synthetic data that my code generated (I generated the MATLAB logo, the vertices to remove are in one quadrant of that logo).
You will have your own choice of vertices to remove from your data.
@Mico,
I don't use MuPAD, but can you sample your function at intervals so as to decimate the implicit surface and make it explicit (i.e., make a set of faces/vertices that lie on your surface)?
It would seem that this is the difficult step (and sorry, like I said, no experience with MuPAD). After that you can send your surface to surf2solid to thicken it.
Second question :)
If I make implicit function like im := plot::Implicit3d(cos(x) + cos(y) + cos(z) ....., is it possible to make thickness for solid generation and then to generate 3D volume mesh for FE model (for example to export Nastran file). Respectfully , Mico
Dear Sven, dear all,
if I have implicit function like im := plot::Implicit3d(cos(x) + cos(y) + cos(z),
x = 0..2*PI,
y = 0..2*PI,
z = 0..2*PI):
How to give desired thickness to this surface (getting "solid" for 3D printing) and export to stl (In MuPAD). BR, Mico
@Sven:
Thank you for your attention.
I have only one question, in this line:
badVertIds = find(XX.vertices(:,1)<0.5 & XX.vertices(:,2)>0.5);
Why are you searching on the first and second columns?
What do they represent? I'm sorry for the stupid question.
@Josafat:
There is a function called meshVolume in the geom3d package from David Legland.
@Thalles, maybe you can just delete them:
n = 10;
[X,Y] = meshgrid(linspace(0,1,2*n+1));
L = (40/51/0.9)*membrane(1,n);
XX = surf2solid(X,Y,L,'elevation',0);
% Find the vertices you want to remove
badVertIds = find(XX.vertices(:,1)<0.5 & XX.vertices(:,2)>0.5);
% Find the faces that use those vertices
badFaceIds = any(ismember(XX.faces,badVertIds),2);
% Remove them
XX.faces(badFaceIds,:) = [];
figure, patch(XX,'FaceColor','g')
Hi Sven,
Can I use this code without a elevation?
I'm trying to use
fv=surf2solid(x,y,z,'elevation',0)
to use it later with the stlwrite, but I'm getting a bad file stl.
When I use any other elevation, the stl works fine.
I have already tryied to use thickness 0 in the nodes I dont want, but it generated the same bad file, and when I print then, the printer still creates a basis.
Hi seven,
is there any way to get the area and volumen of the generated solid?
Thanks in advance.
Hi Sven,
Great job. I would like to generate a smooth surface;
FV_closed = surf2solid(x,y,z,'triangulation', 'delaunay', 'thickness',2);
I am using 'triangulation', 'delaunay', since only specific range of x and y is desirable. But the surface is not smooth. Is there any way to fix it?
Thanks in advance.
Is there any way to combine multiple surfaces (as two sets of X,Y,Z matrices) into one solid?
Thanks in advance!
@Sarah: You gave a property ('elevation') without a value. You need to specify the actual elevation that you desire. Try this code, which specifies an elevation of "3" such that faces are extruded down to a Zcoordinate of 3:
V = [0 0 10; 1 2 11; 2 4 12; 4 2 11; 5 0 10; 2.5 0 9];
F = [1 2 6; 6 4 5; 2 4 6; 2 3 4];
surf2solid(F,V,'Elevation',3)
Does this solve your problem?
Hi Sven,
I would like to use your code, giving it faces and vertices.
I tried using: surf2solid (F,V, 'elevation');
But Matlab is throwing an error. Can you imagine what is wrong with that?
Thank you very much.
Hi, a question when trying to figure out a solid with holes inside. For example, the "meshgrid" creates a 5*5 matrix, but I only need to use the central ring area to create solid ringcylinder. I tried to set the thickness of those pixels(unwanted) to 0, but it still carried out a flat plan covering the whole 5*5 area (I expect no solid in the center hole). How could I set it? Thanks so much.
Works really great and imports into openscad perfectly!
Hi Mona, the output from surf2solid (a faces/vertices structure) can be given directly to stlwrite:
FV = surf2solid(...)
stlwrite('yourfile.stl', FV)
Hi Sven,
Could you give me a clue on how to convert this generated solid topography into STL format? For instance using your older 'stlwrite  Write binary or ascii STL file' code.
Thanks for the awesome work in advance! :)
Hi Marios:
1) Use the triangulated faces/vertices input (SURF2SOLID(F, V,...)) instead of the gridded input (SURF2SOLID(X, Y, Z, ...)) and you can make arbitrary shaped surfaces instead of a grid. I would simply put 0 thickness at the nodes you don't want solid instead of NaN thickness.
2) This is possible, you just need to calculate the distance you want each node to be offset and use that 'thickness'. Basically, give the thickness as the difference between the original surface and your desired surface.
Here's a small example that uses most of those options:
v = [2 4 0; 2 6 0; 8 4 1; 8 0 0; 0 4 0]
f = [1 2 3; 1 3 4; 5 2 1]
figure, surf2solid(f,v,'thickness',[0 0 3 2 0])
Hi Sven,
Very good work. Two questions.
1) Can I change the rectangular mesh to an irregular (curved) one. I have NaN to areas I don't want to create a solid, but it says that it is not supported.
2) Is it possible that user provide a different bottom surface (assuming that up and down surface have the same x,y coordinates).
Great!
Awesome! Thanks!
Thanks for the update Sven! After removing degenerate faces from my surfaces, I feel like this works very well. I've even fed the result through stlwrite, and successfully printed one.
Daniel
Hi Daniel, I've had time to make this fix  it was simply a case of adding an ifisempty clause to line 100 to detect surfaces without any boundary. It's in submission now, and here's a working example of a torus that had the error you described but now works fine:
% Make a torus
a=5;
c=10;
[u,v]=meshgrid(0:10:360);
x=(c+a*cosd(v)).*cosd(u);
y=(c+a*cosd(v)).*sind(u);
z=a*sind(v);
% Get quad faces
figure, t = surf(x,y,z); p = surf2patch(t); close(gcf)
% Triangulate faces (removing coincident vertices)
tri = cat(1, p.faces(:, [1 2 3]), p.faces(:, [1 3 4]));
[unqV, ~, unqI] = unique(p.vertices,'rows');
pTri = struct('vertices',unqV,'faces',unqI(tri));
% Solidify thin surface
pSol = surf2solid(pTri,'Thickness',1)
% Show the result
figure, patch(pSol,'FaceColor','g','FaceAlpha',0.2)
Hi Daniel,
That sounds like the type of thing that surf2solid should handle but I never used it as a test case. I think it should be manageable to work out what to do when "edge" faces are empty. I'll try to put something in, but it will be a week or so... I'm travelling right now.
Thanks for the mfile! I am generally pleased with what I see.
I would like to use this file to thicken surfaces generated from implicit equations. Such an example is a torus. I have a coarse set of faces which describe the surface, and want to pass it through the thickener for printing, but I am getting an error at and beyond line 99 (getting boundVerts), because there are no bounding vertices (the surface is closed!)... Do you have a workaround? It is not immediately clear to me how to correct for this.
Thanks again!
Awesome work!
here is a link to an excellent blog post (written by Sven) with examples of how to use surf2solid function:
http://imageprocessingblog.com/surf2solidatoolfor3dprinting/
What I meant to say was, I use the hold on command to add new objects to my axis. But how do I create an stl file from that? My objective is to print a multilayered 3D file. But I'm not quite sure how to do that.
Hi Phalgun,
I don't quite know what you mean by "multilayered data". As far as I can tell, the "hold on" command will simply let you add new objects to your current plot axes... nothing about multilayers and nothing about printing there. What is the "multilayered 3d image file" type that your 3d printer accepts, and how do you create one of these files normally?
Dear Sven,
The file worked wonderfully well. But if I needed to use it for printing multilayered data, how would I do it? I usually use the "hold on" command to generate these multi layered 3d image files in matlab. But I was wondering how to create an stl file from it.
Love it!
The function is amazing. It works fast and efficiently, and is very easy to use. The resulting stl has high resolution and reasonable size.
Thank you Sven!
Really appreciate your work.