Find holes and gaps in .stl files

15 次查看(过去 30 天)
Hi,
I need to find gaps and holes in a stl file. I don´t know if there´s a function developed that can help me. I started to work with MATLAB recently and i´m finding some dificulties. I found 2 ways of doing this, each triangle should have 3 adjacent triangles or each edge must have 2 triangles in common. Hope someone could give a hand. Thank´s.
Eduardo G.
  3 个评论
Walter Roberson
Walter Roberson 2013-12-9
Please be more specific about the errors you are observing.
agdfg sdgfdg
agdfg sdgfdg 2013-12-10
[unqEdges, ~, edgeNos] = unique(edges,'rows'); | Error: Expression or statement is incorrect--possibly unbalanced (, {, or [. this the error i observed.
And i request you to suggest some other algorithms or codes for STL File Correction.

请先登录,再进行评论。

采纳的回答

Sven
Sven 2012-2-3
Hi Eduardo,
Firstly you need to read the .stl file in as faces and vertices. I'll presume you've already got that done (as Anton points out, stlread is available here http://www.mathworks.com/matlabcentral/fileexchange/22409-stl-file-reader).
Next, as you wrote: each edge must have 2 triangles in common... I think this is a good start. Here's some code that will make a test faces/vertices mesh, then determine such a thing:
% MAKE a face/vertices structure
tmpvol = zeros(20,20,20); % Empty voxel volume
tmpvol(8:12,8:12,5:15) = 1; % Turn some voxels on
fv = isosurface(tmpvol, 0.99);
% REMOVE a face
fv.faces(30,:) = []; % Remove a face!
% TEST for gaps or holes
edges = sort(cat(1, fv.faces(:,1:2), fv.faces(:,2:3), fv.faces(:,[3 1])),2);
[unqEdges, ~, edgeNos] = unique(edges,'rows');
if size(edges,1) == size(unqEdges,1)*2
% Every edge is used twice... consistent with a closed manifold mesh
disp('No problem!')
else
badEdgesMask = hist(edgeNos, 1:max(edgeNos))~=2;
badEdgeNos = edgeNos(badEdgesMask);
badNodeNos = edges(badEdgeNos,:);
badFaceNos = find(sum(ismember(fv.faces, badNodeNos),2)>=2);
end
Does this answer the question for you?
  1 个评论
Eduardo
Eduardo 2012-2-22
Thanks. This is exactly what i was looking for. Now i have to fill in the hole if one or more were found... Do you have any idea how to do it?

请先登录,再进行评论。

更多回答(4 个)

Eduardo
Eduardo 2012-2-4
Hi,
First of all thanks. I´m using this 'stlread':
and then i´m using 'patchslim'. Now i´ll try your code and i´ll give you feedback.

Eduardo
Eduardo 2012-2-26
Hi. I´ve made some tests and discovered that when a hole is found this code is not working correctly. When we axecute "badEdgeNos = edgeNos(badEdgesMask);" this should compare badEdgesMask with the number 1 and not with the first element of edgeNos. Can you understand what i´m trying to explain?
  2 个评论
Sven
Sven 2012-2-29
If you mean that the variable badEdgesMask only contains ones and zeros, then you need to realise that it's using logical indexing... if you're sure there's a problem, make a small example (such as the one I made) and show how it's not working.
Eduardo
Eduardo 2012-2-29
I´ve made a test with a simple cube. I removed 1 face and badEdgesMask is correct. The problem is in "badEdgeNos = edgeNos(badEdgesMask);" because badEdgesMask has 18 elements and edgeNos has 33. The comparison is made 1 by 1 element, so it compares the first logical value with the first element of edgeNos, i think it should be the first logical value with the element 1 of edgeNos (corresponding edge "1 2").

请先登录,再进行评论。


Eduardo
Eduardo 2012-3-25
I made it like this to solve the problem i´ve told you:
edges = sort(cat(1, fv.faces(:,1:2), fv.faces(:,2:3), fv.faces(:,[3 1])),2);
[unqEdges, ~, edgeNos] = unique(edges,'rows');
if size(edges,1) == size(unqEdges,1)*2
% Every edge is used twice... consistent with a closed manifold mesh
disp('No problem!')
else
badEdgesMask = hist(edgeNos, 1:max(edgeNos))~=2;
[~, ~, edgeNos1] = unique(unqEdges,'rows');
badEdgeNos = edgeNos1(badEdgesMask);
badNodeNos = unqEdges(badEdgesMask,:);
badFaceNos = find(sum(ismember(fv.faces, badNodeNos),2)>=2);
end
I don´t know if this the best way to do it, but it works...

DGM
DGM 2025-3-31
编辑:DGM 2025-6-5
Single missing triangles are easy enough to fix, but holes that need to be triangulated are a problem. This doesn't seem to be a simple problem that can generally be solved with built-in triangulation tools, since the boundary curve in 3D would need to be somehow unrolled in order to do the constrained triangulation in 2D. -- or something.
Here's an attempt. It's a terrible naive approach that will absolutely fail on anything but the simplest of holes. To be more specific, the only holes which this can be relied upon to fill are convex holes defined by a set of coplanar vertices. It may work in other cases, but only coincidentally.
The triangulation class wasn't introduced until R2013a, but the trirep class was available (R2009a). I'm not going to bother making this garbage answer period-correct anyway, as I do know that there are some subtle undocumented behavioral changes that have happened at unknown points over the years anyway. I'm not sure what would work when.
The hole in the top of this model only coincidentally doesn't get connected across its non-convex side. That's just luck.
unzip testfiles.zip % for the forum
% this model has two holes:
% a single missing triangle on the -y side
% three missing triangles on the +z side (including two missing edges)
T = stlread('bad_stepholecube.stl');
Tpatched = demo(T,true);
A saddle formed by four points does not have a unique triangulation. It's also conceivable that a hole can destroy unrecoverable information. It's only due to the simplicity and symmetry of this model that we might suspect there's an entire missing corner. At least we can still manage to fill this one too.
% this model has one hole:
% four missing triangles on the top
% (and four missing edges and a missing vertex)
T = stlread('bad_stepholecube2.stl');
Tpatched = demo(T,true);
The shape of the holes plays a large part in how successfully you can blindly fill them. Even admesh will fail on this one.
% this model has three holes:
% the hole on the front will fail by itself,
% but all three holes are connected by single vertices,
% so _everything_ fails.
T = stlread('bad_stepholecube3.stl');
Tpatched = demo(T,true);
Are there better ways? Probably. I haven't figured one out yet today.
I think the bigger question is why it's necessary to do this complicated task not only blindly, but in MATLAB. If you want to do it blind, why not just call admesh or something using a system call? Better yet, start by fixing the worst of the problems manually. How? Well, you could sacrifice your hair to learn Blender or some other capable tool, or if your problem is small, just figure out what triangles need to be added and add them.
% using the same bad 3-hole model, display the bad model,
% inspect the vertices with the mouse cursor and figure out where triangles should be.
%inspect(T) % obviously i can't run this on the forum
% apply the manually selected triangles
newF = [15 5 4; 16 5 15; 8 6 5; 18 8 5; 10 17 15; 7 17 10];
Tpatched = triangulation([T.ConnectivityList; newF],T.Points);
% visualize the problem
figure(2); clf
hp = patch('faces',Tpatched.ConnectivityList, ...
'vertices',Tpatched.Points, ...
'facecolor','w','edgecolor','k');
view(3)
camlight
axis equal
view(10,33)
grid on
xlabel('X')
ylabel('Y')
zlabel('Z')
It's still missing some extra vertices, but at least it's a closed 2-manifold now.
function Tpatched = demo(T,doplot)
% blindly try to fill holes in a triangulation object
if doplot
% visualize the problem
figure(1); clf
hp = patch('faces',T.ConnectivityList,'vertices',T.Points,'facecolor','w','edgecolor','k');
view(3)
camlight
axis equal
view(10,33)
grid on
xlabel('X')
ylabel('Y')
zlabel('Z')
end
% these are the open edges, sorted and ordered
Fb = freeBoundary(T);
% go through the list of open edges and sort them into closed curves
% there shouldn't be any open curves
vertpile = Fb; % a temporary copy to consume
k = 1;
while ~isempty(vertpile)
endidx = find(vertpile(:,2) == vertpile(1,1),1,'first');
if isempty(endidx)
% if this vertex does not lie on a closed curve, ignore it
% unless you can come up with a better idea
vertpile(1,:) = [];
else
holeCL{k} = vertpile(1:endidx,:); %#ok<*AGROW>
vertpile(1:endidx,:) = [];
k = k + 1;
end
end
% triangulate the holes with a naive fan pattern
% this is likely to fail for holes with nonconvex boundaries
% but it coincidentally works in this case.
newF = cell(numel(holeCL),1);
for k = 1:numel(holeCL)
H = holeCL{k}(:,1);
newF{k} = zeros(size(H,1)-2,3);
for kt = 1:size(newF{k},1)
newF{k}(kt,:) = H([1 kt+(1:2)]);
end
end
newF = cell2mat(newF); % new triangles to add to the model
% create a new triangulation object with the added faces
Tpatched = triangulation([T.ConnectivityList; newF],T.Points);
% visualize the result
if doplot
figure(2); clf
hp = patch('faces',Tpatched.ConnectivityList, ...
'vertices',Tpatched.Points, ...
'facecolor','w','edgecolor','k');
view(3)
camlight
axis equal
view(10,33)
grid on
xlabel('X')
ylabel('Y')
zlabel('Z')
end
end
function inspect(T)
% visualize an object and use a custom datacursor to get the indices of
% individual vertices.
% visualize the problem
figure(1); clf
hp = patch('faces',T.ConnectivityList,'vertices',T.Points,'facecolor','w','edgecolor','k');
view(3)
camlight
axis equal
view(10,33)
grid on
xlabel('X')
ylabel('Y')
zlabel('Z')
% create custom datacursor to view index
dcm_obj = datacursormode(gcf);
set(dcm_obj,'UpdateFcn',@myupdatefcn);
% this is kind of flaky, but i don't care that much
function txt = myupdatefcn(~,event_obj)
% get rid of old tips
alltips = findall(gcf,'Type','hggroup');
if numel(alltips)>2
delete(alltips(3:end))
end
pos = get(event_obj,'Position');
didx = find(all(T.Points == pos,2),1,'first');
txt = {sprintf('x: %.4f',pos(1)),...
sprintf('y: %.4f',pos(2)),...
sprintf('z: %.4f',pos(3)),...
['Index: ',num2str(didx)]};
end
end
If someone has a better answer, post it.

Community Treasure Hunt

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

Start Hunting!

Translated by