File Exchange

## surf2solid - make a solid volume from a surface for 3D printing

version 1.5.0.0 (6.08 KB) by
Turns thin surfaces into closed solids by adding a flat base or offsetting by a given thickness.

Updated 10 Feb 2014

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
(orig-surface, side-walls, under-surface) 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 per-point, 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 N-by-1 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 N-by-3 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/paul-prints-the-l-shaped-membrane/
Many thanks to Paul for his further input and improvements.

### Cite As

Sven (2020). surf2solid - make a solid volume from a surface for 3D printing (https://www.mathworks.com/matlabcentral/fileexchange/42876-surf2solid-make-a-solid-volume-from-a-surface-for-3d-printing), MATLAB Central File Exchange. Retrieved .

Hossein Babaei

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.

Craig Puetz

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.

Julia

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?

Rose Haft

How do you save these files?

Bruno Luong

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 = (i0-1)*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');

Xuzhe Zhang

Excellent and inspiring work! Many thanks to Sven, Paul, and Charles!

Derek Feng

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!

Kabilan K

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.

Kabilan K

Hi all,
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.

Yasith Amarasinghe

Hi All,
Can someone please point me out how to write the converted solid as .stl file.

Yasith

Charles Tenney

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 164-172 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 208-212):
% Compile 3 sets of faces together
allVertices = [V; V_extrude];
allFaces = [F; % Use original 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

@Philippe Mercier, My mistake, it seems the problem came from somewhere else, it works perfectly now. Thank you very much !

Sven

@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.

Sven

@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/programming-and-data-types.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.

Rose Haft

This no longer seems to be working even from the example. I did have this running previously.

mvulp

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 x-axis, y-axis 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.

Ryan Scott

Steven

It's so cool! Thanks for your talent!

Hans Scharler

Leland Muller

Jonathan Haydak

been using this thing for a while. author deserves to get a cookie. if i ever meet you i will give you a cookie.

Andrew Bliss

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)=[];

Tom

@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.

Nicolas Alvarez

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

Akhilesh Gupta

Rahul Dutta

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.

John Logan

Amirali Nojoomi

danny

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

uncletat

uncletat

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 (Single-sided extrusion).

However, in my application, a single-sided extrusion would mess up the proportionality of the geometry. I would need a double-sided extrusion (or a mid-plane 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

Sven

@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.

Sven

@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.

Mico Stanojevic

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

Mico Stanojevic

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

Thalles Leite

@Sven:

I have only one question, in this line:

Why are you searching on the first and second columns?

What do they represent? I'm sorry for the stupid question.

Sven

@Josafat:
There is a function called meshVolume in the geom3d package from David Legland.

Sven

@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
% Find the faces that use those vertices
% Remove them
figure, patch(XX,'FaceColor','g')

Thalles Leite

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.

sumana

Josafat Quiroz

Hi seven,

is there any way to get the area and volumen of the generated solid?

Leila Ghanbari

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?

Meghan Kazanski

Is there any way to combine multiple surfaces (as two sets of X,Y,Z matrices) into one solid?

Sven

@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 Z-coordinate 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)

Sarah Weissenberger

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.

Evan

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 ring-cylinder. 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.

Nathan Tomlin

Works really great and imports into openscad perfectly!

Nathan Tomlin

Sven

Hi Mona, the output from surf2solid (a faces/vertices structure) can be given directly to stlwrite:

FV = surf2solid(...)
stlwrite('yourfile.stl', FV)

Mona Mahboob Kanafi

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! :)

Sven

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])

Marios

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!

Daniel

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

Sven

Hi Daniel, I've had time to make this fix - it was simply a case of adding an if-isempty 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);
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)

Sven

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.

Daniel

Thanks for the m-file! 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/surf2solid-a-tool-for-3d-printing/

Phalgun Lolur

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 multi-layered 3D file. But I'm not quite sure how to do that.

Sven

Hi Phalgun,
I don't quite know what you mean by "multi-layered 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 multi-layers and nothing about printing there. What is the "multi-layered 3d image file" type that your 3d printer accepts, and how do you create one of these files normally?

Phalgun Lolur

Dear Sven,

The file worked wonderfully well. But if I needed to use it for printing multi-layered 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.

Paul Kassebaum

Love it!

mathgirl

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!