When exporting a triangulation as an stl using stlwrite() the resulting stl is missing half of its faces.

19 views (last 30 days)
I get an output file looking like the following picture when trying to export my triangulation object as an stl. the yellow represents valid faces and the red corresponds to missing faces. The section of code where I generate the face connectivity and convert to a triangulation object and subsequently an stl file.
Each "loadpath" (wiggly strand connecting one rectangle to the other) is defined in 2-d by a set of 2 1001 point curves, and in 3-d by a simple upwards translation of those curves. The matrix containing my points starts with 4 points defining the -z corners of the -x grip stock (rectangular section), followed by the +y and -y curves defining each of the loadpaths on the -z face of the object, then the 4 points defining the -z corners of the +x grip stock, the 4 points defining the +z corners of the -x grip stock, the upper loadpaths, and finally the 4 corners defining the +z corners of the +x grip stock.
% Generating tringular tiling of the loadpaths
f=zeros(3,2001*10*4);
for i=1:1000 % first loadpath
f(:,i)=[i,i+1,1001+i]; %y+ layer of triangles on bottom face
f(:,1000+i)=[i+1,1001+i,1002+i]; %y- layer of triangles on bottom face
f(:,20000+i)=[i,i+1,20028+i]; %z- layer of triangles on y+ face
f(:,21000+i)=[i+1,20028+i,20029+i]; %z+ layer of tringles on y+ face
f(:,40000+i)=[1001+i,1002+i,21029+i];
f(:,41000+i)=[1002+i,21029+i,21030+i];
f(:,60000+i)=[20028+i,20029+i,21029+i];
f(:,61000+i)=[20029+i,21029+i,21030+i];
end
for i=1:9 % loadpaths 2-10
f(:,((2000*i)+1):((2000*(i+1))))=f(:,1:2000)+2002*i;
f(:,((2000*i)+20000+1):(2000*(i+1)+20000))=f(:,20001:22000)+2002*i;
f(:,((2000*i)+40000+1):(2000*(i+1)+40000))=f(:,40001:42000)+2002*i;
f(:,((2000*i)+60000+1):(2000*(i+1)+60000))=f(:,60001:62000)+2002*i;
% faces between the loadpaths
f(:,i+80000)=[((i-.5)*2002)+1,((i)*2002)+1,(i-.5)*2002+20029];
f(:,i+80009)=[((i)*2002)+20029,((i)*2002)+1,(i-.5)*2002+20029];
f(:,i+80018)=f(:,i+80000)+1000;
f(:,i+80027)=f(:,i+80009)+1000;
end
f=f+4; % adjusting point indicies to reflect the first 4 points being used for grip stock
% generating grip stock and loadpath attachment points
f(:,80037)=[1,2,3]; % bottom face -x gs -x,-y triangle (third angle proj.)
f(:,80038)=[2,3,4]; % bottom face -x gs +x,+y triangle
f(:,80039)=[1,2,20029]; % left face -x gs -y,-z triangle
f(:,80040)=[1,3,20029]; % front face -x gs -x,-z triangle
f(:,80041)=[2,20030,20029]; % left face -x gs, +y,+z triangle
f(:,80042)=[2,4,20030]; % back face -x gs, -x,-z triangle
f(:,80043)=[4,20032,20030]; % back face -x gs, +x,+z triangle
f(:,80044)=[3,20031,20029]; % front face -x gs, +x,+z triangle
f(:,80045:80046)=[f(:,80037),f(:,80038)]+20028; % top face +x gs, both triangles
f(:,80047:80056)=f(:,80037:80046)+20024; % +x gs, all triangles
% attaching grip stock to first and last loadpaths.
f(:,80057)=[4,5,20032];
f(:,80058)=[5,20032,20033];
f(:,80059)=[20028,1005,21033];
f(:,80060)=[20028,21033,40056];
f(:,80061)=[3,19024,39052];
f(:,80062)=[3,20031,39052];
f(:,80063)=[20024,20027,40055];
f(:,80064)=[40055,40051,20024];
%% converting to triangulation object and stl
tr=triangulation(f',db');
%trisurf(tr)
axis 'equal'
stlwrite(tr, 'stl_test.stl')
%patch('Faces',f(:,:)','Vertices',db','facecol',[1,0,0],'edgecol','k');

Answers (1)

Umar
Umar on 27 Nov 2025 at 23:50

Hi @Jacob,

I can see from your attached image exactly what's happening. The yellow sections show valid triangulation on the grip stocks at both ends and on the wavy loadpath strands in the middle, but the red sections indicate missing face connectivity where these components should connect to each other. This is a classic indexing problem in triangulation construction.

Looking at your code, the main issue is in how you're calculating the vertex indices for the connection faces between the grip stocks and the first and last loadpaths. When you have sections like f(:,80057)=[4,5,20032], you're manually specifying vertex indices, but these need to precisely match the actual vertex locations in your db matrix. The problem is that your grip stock corners are at indices 1-4 and 20029-20032 for the bottom and top respectively, but your loadpath curves start at index 5, and there's likely a mismatch between where you think the connection vertices are versus where they actually exist in your vertex array.

The specific problematic sections are your grip stock attachment faces from f(:,80057) through f(:,80064). These eight triangles are supposed to bridge between the rectangular grip stocks and the wavy loadpaths, but the vertex indices you're using don't correctly reference the boundary points where these structures meet. For example, in f(:,80057)=[4,5,20032], vertex 4 is presumably a corner of the lower left grip stock, vertex 5 is the first point of the first loadpath's lower y-positive curve, and vertex 20032 should be the corresponding point on the upper surface, but this specific combination may not form a valid connection because those three points might not be adjacent in your actual geometry.

Additionally, your inter-loadpath connection faces from f(:,i+80000) through f(:,i+80027) use the formula ((i-.5)*2002) which produces non-integer values when you have expressions like (0.5)*2002 = 1001. MATLAB will truncate these to integers, but this might not give you the vertex indices you intend. These faces are trying to fill the gaps between adjacent loadpaths, but if the indices are off by even one position, you'll get holes in the mesh.

To fix this problem, first verify your vertex indexing scheme by checking the size and structure of your db matrix. Print out size(db,1) to confirm you have the expected number of vertices. Then systematically check that the vertex indices in your connection faces actually exist and are positioned where you expect. Add validation like this after building your face matrix: valid_indices = all(f(:) >= 1 and all(f(:) <= size(db,1)), and if that returns false, use find(f(:) < 1 or f(:) > size(db,1)) to identify which faces have invalid indices.

For the grip stock connections specifically, you need to ensure that vertices 1-4 are indeed the four corners of your lower left grip stock, that vertices 5 and 1005 are the start and end points of your first loadpath's lower curves, and that vertices 20029-20032 are the upper corners of your lower left grip stock positioned directly above vertices 1-4. If any of these assumptions are wrong, those connection triangles will reference incorrect vertices and create gaps. The same logic applies to the right grip stock connections which use vertices around 20024-20027 and 40051-40056.

I recommend adding debug visualization to isolate the problem. After creating your triangulation, try plotting just the problematic connection regions separately. For example, plot only the faces that connect the first grip stock to the first loadpath with subset_faces = f(:,80057:80060) and trisurf(triangulation(subset_faces',db')). This will let you see if those specific triangles are being formed correctly. If they appear in the wrong location or orientation, you'll know the vertex indices need adjustment.

Another debugging approach is to manually verify a few key connections. Pick one of the red missing face areas, identify which two structures should connect there, determine what the boundary vertex indices should be for each structure, and manually construct a test triangle using those indices. If your test triangle appears in the correct location when plotted, then you know those are the correct indices to use in your face matrix. If it appears elsewhere or causes an error, then your indexing scheme has an offset that needs correction.

The most likely fix is adjusting the vertex indices in your grip stock attachment section. The pattern suggests that your grip stocks are defined correctly and your loadpaths are defined correctly, but the bridge triangles between them are using indices that don't match up. Try recalculating those manual index assignments at f(:,80057:80064) by carefully tracing through where your vertex array places the grip stock corners versus where the loadpath endpoints are located. You may need to add or subtract offsets to align them properly.

Let me know how it goes.

  3 Comments
Umar
Umar 29 minutes ago

Thanks for the update, @Jacob — that makes sense. If the geometry rendered correctly in MATLAB but failed in the slicer, then yes, inconsistent facet normals would explain it. Many slicers are strict about normal orientation, while MATLAB recalculates them automatically, so the underlying mesh can be valid even if the STL export causes issues.

Good to hear that re-saving through CAD fixed the normals and that your colleague streamlined the code to avoid the issue entirely. Glad you got it working! Let me know if anything else comes up.

DGM
DGM 12 minutes ago
From the image, it looks like every other triangle is backwards, so yeah. I'd suspect something is wrong with the way you're defining the triangles. I can't really track that down without db. If it is truly every other triangle, it may suffice to simply flip every other row in f.
Note that patch() will render backwards faces by default. That can be misleading you into thinking they're not backwards.

Sign in to comment.

Categories

Find more on Graphics Performance in Help Center and File Exchange

Products


Release

R2025a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!