我在之前的一个专栏发过一个将地理坐标的数据转成3D的STL模型。我下面介绍一下如何将长方体转成STL文件。我提供两个方法。
第一个是matlab程序。与地理坐标不一样,长方体存在特殊的情况,比如需要把其分成上下、左右、前后6部分进行拼接。关于这部分的知识,可以参考这个up的专栏。我在此基础上,得到了一个方案。具体的代码如下:
bi = load('b01.txt');
scatter3(bi(1,:),bi(2,:),bi(3,:))
x = bi(1,:);
y = bi(2,:);
z = bi(3,:);
[Anum,index] = sort(x);
%% labeling
x1 = [Anum(1),Anum(3);Anum(5),Anum(7)];
y1 = [y(index(1)),y(index(3));y(index(5)),y(index(7))];
z1 = [z(index(1)),z(index(3));z(index(5)),z(index(7))];
xyz2stl('rectangular.stl',x1,y1,z1)
其中的 b01.txt为x,y,z三列数据。
18.6613959728922 3.14143675683556 3.02457703598341
20.3888047311257 -7.71573813292703 3.02457703598341
30.4000436850529 -6.12292029353781 3.02457703598341
28.6726349268193 4.73425459622477 3.02457703598341
28.6726349268193 4.73425459622477 -3.02457703598341
30.4000436850529 -6.12292029353781 -3.02457703598341
20.3888047311257 -7.71573813292703 -3.02457703598341
18.6613959728922 3.14143675683556 -3.02457703598341
而xyz2stl函数为
function f=xyz2stl(FileName,X,Y,Z)
Nx=size(Z,2);
Ny=size(Z,1);
fid=fopen(FileName,'w');
fprintf(fid,'solid\n');
%top surface
Surf_write(fid,X,Y,Z,0);
%other surface assumed to be plane
% %X=X(1,1) plane
Xs=repmat(X(1,:),2,1);
Ys=repmat(Y(1,:),2,1);
Zs=[zeros(1,Ny);Z(:,1)'];
Surf_write(fid,Xs,Ys,Zs,0);
%X=X(1,Nx) plane
Xs=repmat(X(2,:),2,1);
Ys=repmat(Y(2,:),2,1);
Zs=[zeros(1,Ny);Z(:,1)'];
Surf_write(fid,Xs,Ys,Zs,0);
% %Y=Y(1,1) plane
Xs=repmat(X(:,1)',2,1);
Ys=repmat(Y(:,1)',2,1);
Zs=[zeros(1,Ny);Z(:,1)'];
Surf_write(fid,Xs,Ys,Zs,0);
% %Y=Y(Ny,1) plane
Xs=repmat(X(:,2)',2,1);
Ys=repmat(Y(:,2)',2,1);
Zs=[zeros(1,Ny);Z(:,1)'];
Surf_write(fid,Xs,Ys,Zs,0);
%
% %the bottom plane
Surf_write(fid,X,Y,0*Z,0);
fprintf(fid,'endsolid');
fclose(fid);
disp('xyz2stl finish!')
end
其中包含的函数为:
function f=Surf_write(fid,X,Y,Z,opt)
for i=1:size(Z,1)-1
for j=1:size(Z,2)-1
%splitting each small rectangular to 2 small triangular
p1=[X(i,j),Y(i,j),Z(i,j)];
p2=[X(i+1,j),Y(i+1,j),Z(i+1,j)];
p3=[X(i+1,j+1),Y(i+1,j+1),Z(i+1,j+1)];
local_write_facet(fid,p1,p2,p3,opt);
p1=[X(i,j),Y(i,j),Z(i,j)];
p3=[X(i,j+1),Y(i,j+1),Z(i,j+1)];
p2=[X(i+1,j+1),Y(i+1,j+1),Z(i+1,j+1)];
local_write_facet(fid,p1,p2,p3,opt);
end
end
f=1;
end
%to write a single small triangular to the file
function num = local_write_facet(fid,p1,p2,p3,opt)
num = 1;
n = local_find_normal(p1,p2,p3,opt);
fprintf(fid,'facet normal %.7E %.7E %.7E\r\n', n(1),n(2),n(3) );
fprintf(fid,'outer loop\r\n');
fprintf(fid,'vertex %.7E %.7E %.7E\r\n', p1);
fprintf(fid,'vertex %.7E %.7E %.7E\r\n', p2);
fprintf(fid,'vertex %.7E %.7E %.7E\r\n', p3);
fprintf(fid,'endloop\r\n');
fprintf(fid,'endfacet\r\n');
end
%to generate the normal direction vector n, from the vertex of triangular
function n = local_find_normal(p1,p2,p3,opt)
if ~opt
v2 = p2-p1;
v1 = p3-p1;
v3 = cross(v1,v2);
n = v3 ./ sqrt(sum(v3.*v3));
else
n=opt;
end
end
第二个是一个matlab小程序包。具体的下载链接为:https://github.com/NWRichmond/xyz2stl.
经过本文的测试,运行该程序包,需要在这个文件目录下增加一个函数:stlWrite函数,如下:
function stlWrite(filename, varargin)
%STLWRITE Write STL file from patch or surface data.
%
% STLWRITE(FILE,fv) writes a stereolithography (STL) file to FILE for a triangulated
% patch defined by FV (a structure with fields 'vertices' and 'faces').
%
% STLWRITE(FILE,FACES,VERTICES) takes faces and vertices separately, rather than in an FV struct
%
% STLWRITE(FILE,X,Y,Z) creates an STL file from surface data in X, Y, and Z. STLWRITE triangulates
% this gridded data into a triangulated surface using triangulations options specified below. X, Y
% and Z can be two-dimensional arrays with the same size. If X and Y are vectors with length equal
% to SIZE(Z,2) and SIZE(Z,1), respectively, they are passed through MESHGRID to create gridded
% data. If X or Y are scalar values, they are used to specify the X and Y spacing between grid
% points.
%
% STLWRITE(...,'PropertyName',VALUE,'PropertyName',VALUE,...) writes an STL file using the
% following property values:
%
% MODE - File is written using 'binary' (default) or 'ascii'.
%
% TITLE - Header text (max 80 characters) written to the STL file.
%
% TRIANGULATION - When used with gridded data, TRIANGULATION is either:
% 'delaunay' - (default) Delaunay triangulation of X, Y
% 'f' - Forward slash division of grid quadrilaterals
% 'b' - Back slash division of quadrilaterals
% 'x' - Cross division of quadrilaterals
% Note that 'f', 'b', or 't' triangulations require FEX entry 28327, "mesh2tri".
%
% FACECOLOR - (not currently implemented) When used with face/vertex input, specifies the
% colour of each triangle face. If users request this feature, I will attempt to
% implement it.
%
% Example 1:
% % Write binary STL from face/vertex data
% 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); % Create the patch object
% stlwrite('test.stl',fv) % Save to binary .stl
%
% Example 2:
% % Write ascii STL from gridded data
% [X,Y] = deal(1:40); % Create grid reference
% Z = peaks(40); % Create grid height
% stlwrite('test.stl',X,Y,Z,'mode','ascii')
% Original idea adapted from surf2stl by Bill McDonald. Huge speed
% improvements implemented by Oliver Woodford. Non-Delaunay triangulation
% of quadrilateral surface input requires mesh2tri by Kevin Moerman.
%
% Author: Sven Holcombe, 11-24-11
% Check valid filename path
narginchk(2, inf)
path = fileparts(filename);
if ~isempty(path) && ~exist(path,'dir')
error('Directory "%s" does not exist.',path);
end
% Get faces, vertices, and user-defined options for writing
[faces, vertices, options] = parseInputs(varargin{:});
asciiMode = strcmp( options.mode ,'ascii');
% Create the facets
facets = single(vertices');
facets = reshape(facets(:,faces'), 3, 3, []);
% Compute their normals
V1 = squeeze(facets(:,2,:) - facets(:,1,:));
V2 = squeeze(facets(:,3,:) - facets(:,1,:));
normals = V1([2 3 1],:) .* V2([3 1 2],:) - V2([2 3 1],:) .* V1([3 1 2],:);
clear V1 V2
normals = bsxfun(@times, normals, 1 ./ sqrt(sum(normals .* normals, 1)));
facets = cat(2, reshape(normals, 3, 1, []), facets);
clear normals
% Open the file for writing
permissions = {'w','wb+'};
fid = fopen(filename, permissions{asciiMode+1});
if (fid == -1)
error('stlwrite:cannotWriteFile', 'Unable to write to %s', filename);
end
% Write the file contents
if asciiMode
% Write HEADER
fprintf(fid,'solid %s\r\n',options.title);
% Write DATA
fprintf(fid,[...
'facet normal %.7E %.7E %.7E\r\n' ...
'outer loop\r\n' ...
'vertex %.7E %.7E %.7E\r\n' ...
'vertex %.7E %.7E %.7E\r\n' ...
'vertex %.7E %.7E %.7E\r\n' ...
'endloop\r\n' ...
'endfacet\r\n'], facets);
% Write FOOTER
fprintf(fid,'endsolid %s\r\n',options.title);
else % BINARY
% Write HEADER
fprintf(fid, '%-80s', options.title); % Title
fwrite(fid, size(facets, 3), 'uint32'); % Number of facets
% Write DATA
% Add one uint16(0) to the end of each facet using a typecasting trick
facets = reshape(typecast(facets(:), 'uint16'), 12*2, []);
facets(end+1,:) = 0;
fwrite(fid, facets, 'uint16');
end
% Close the file
fclose(fid);
fprintf('Wrote %d facets\n',size(facets, 3));
%% Input handling subfunctions
function [faces, vertices, options] = parseInputs(varargin)
% Determine input type
if isstruct(varargin{1}) % stlwrite('file', FVstruct, ...)
if ~all(isfield(varargin{1},{'vertices','faces'}))
error( 'Variable p must be a faces/vertices structure' );
end
faces = varargin{1}.faces;
vertices = varargin{1}.vertices;
options = parseOptions(varargin{2:end});
elseif isnumeric(varargin{1})
firstNumInput = cellfun(@isnumeric,varargin);
firstNumInput(find(~firstNumInput,1):end) = 0; % Only consider numerical input PRIOR to the first non-numeric
numericInputCnt = nnz(firstNumInput);
options = parseOptions(varargin{numericInputCnt+1:end});
switch numericInputCnt
case 3 % stlwrite('file', X, Y, Z, ...)
% Extract the matrix Z
Z = varargin{3};
% Convert scalar XY to vectors
ZsizeXY = fliplr(size(Z));
for i = 1:2
if isscalar(varargin{i})
varargin{i} = (0:ZsizeXY(i)-1) * varargin{i};
end
end
% Extract X and Y
if isequal(size(Z), size(varargin{1}), size(varargin{2}))
% X,Y,Z were all provided as matrices
[X,Y] = varargin{1:2};
elseif numel(varargin{1})==ZsizeXY(1) && numel(varargin{2})==ZsizeXY(2)
% Convert vector XY to meshgrid
[X,Y] = meshgrid(varargin{1}, varargin{2});
else
error('stlwrite:badinput', 'Unable to resolve X and Y variables');
end
% Convert to faces/vertices
if strcmp(options.triangulation,'delaunay')
faces = delaunay(X,Y);
vertices = [X(:) Y(:) Z(:)];
else
if ~exist('mesh2tri','file')
error('stlwrite:missing', '"mesh2tri" is required to convert X,Y,Z matrices to STL. It can be downloaded from:\n%s\n',...
'http://www.mathworks.com/matlabcentral/fileexchange/28327')
end
[faces, vertices] = mesh2tri(X, Y, Z, options.triangulation);
end
case 2 % stlwrite('file', FACES, VERTICES, ...)
faces = varargin{1};
vertices = varargin{2};
otherwise
error('stlwrite:badinput', 'Unable to resolve input types.');
end
end
function options = parseOptions(varargin)
IP = inputParser;
IP.addParamValue('mode', 'binary', @ischar)
IP.addParamValue('title', sprintf('Created by stlwrite.m %s',datestr(now)), @ischar);
IP.addParamValue('triangulation', 'delaunay', @ischar);
IP.addParamValue('facecolor',[], @isnumeric)
IP.parse(varargin{:});
options = IP.Results;
运行该程序包中的xyz2stl.mlapp,得到以下的界面:
(1)我读取的文件是csv格式,因此选择文件后,Field Delimiter选择Comma,文件头没有则为0,下面选择的参数默认即可。
(2)下一步选择输出的路径和文件名,其余参数默认,点击运行即可。
下面即为得到的stl文件。
致谢:感谢B站粉丝提供的问题。