You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
9.8 KiB
253 lines
9.8 KiB
11 months ago
|
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 triangulation 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 chars) 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 quads
|
||
|
% 'b' - Back slash division of quadrilaterals
|
||
|
% 'x' - Cross division of quadrilaterals
|
||
|
% Note that 'f', 'b', or 't' triangulations now use an
|
||
|
% inbuilt version of FEX entry 28327, "mesh2tri".
|
||
|
%
|
||
|
% FACECOLOR - Single colour (1-by-3) or one-colour-per-face (N-by-3)
|
||
|
% vector of RGB colours, for face/vertex input. RGB range
|
||
|
% is 5 bits (0:31), stored in VisCAM/SolidView format
|
||
|
% (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL)
|
||
|
%
|
||
|
% Example 1:
|
||
|
% % Write binary STL from face/vertex data
|
||
|
% tmpvol = false(20,20,20); % Empty voxel volume
|
||
|
% tmpvol(8:12,8:12,5:15) = 1; % Turn some voxels on
|
||
|
% fv = isosurface(~tmpvol, 0.5); % Make patch w. faces "out"
|
||
|
% 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')
|
||
|
%
|
||
|
% Example 3:
|
||
|
% % Write binary STL with coloured faces
|
||
|
% cVals = fv.vertices(fv.faces(:,1),3); % Colour by Z height.
|
||
|
% cLims = [min(cVals) max(cVals)]; % Transform height values
|
||
|
% nCols = 255; cMap = jet(nCols); % onto an 8-bit colour map
|
||
|
% fColsDbl = interp1(linspace(cLims(1),cLims(2),nCols),cMap,cVals);
|
||
|
% fCols8bit = fColsDbl*255; % Pass cols in 8bit (0-255) RGB triplets
|
||
|
% stlwrite('testCol.stl',fv,'FaceColor',fCols8bit)
|
||
|
|
||
|
% Original idea adapted from surf2stl by Bill McDonald. Huge speed
|
||
|
% improvements implemented by Oliver Woodford. Non-Delaunay triangulation
|
||
|
% of quadrilateral surface courtesy of Kevin Moerman. FaceColor
|
||
|
% implementation by Grant Lohsen.
|
||
|
%
|
||
|
% Author: Sven Holcombe, 11-24-11
|
||
|
|
||
|
|
||
|
% Check valid filename path
|
||
|
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, []);
|
||
|
% Set the last bit to 0 (default) or supplied RGB
|
||
|
facets(end+1,:) = options.facecolor;
|
||
|
fwrite(fid, facets, 'uint16');
|
||
|
end
|
||
|
|
||
|
% Close the file
|
||
|
fclose(fid);
|
||
|
% fprintf('Wrote %d facets\n',size(facets, 2));
|
||
|
|
||
|
|
||
|
%% 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
|
||
|
|
||
|
if ~isempty(options.facecolor) % Handle colour preparation
|
||
|
facecolor = uint16(options.facecolor);
|
||
|
%Set the Valid Color bit (bit 15)
|
||
|
c0 = bitshift(ones(size(faces,1),1,'uint16'),15);
|
||
|
%Red color (10:15), Blue color (5:9), Green color (0:4)
|
||
|
c0 = bitor(bitshift(bitand(2^6-1, facecolor(:,1)),10),c0);
|
||
|
c0 = bitor(bitshift(bitand(2^11-1, facecolor(:,2)),5),c0);
|
||
|
c0 = bitor(bitand(2^6-1, facecolor(:,3)),c0);
|
||
|
options.facecolor = c0;
|
||
|
else
|
||
|
options.facecolor = 0;
|
||
|
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.addParamValue('facecolour',[], @isnumeric)
|
||
|
IP.parse(varargin{:});
|
||
|
options = IP.Results;
|
||
|
if ~isempty(options.facecolour)
|
||
|
options.facecolor = options.facecolour;
|
||
|
end
|
||
|
|
||
|
function [F,V]=mesh2tri(X,Y,Z,tri_type)
|
||
|
% function [F,V]=mesh2tri(X,Y,Z,tri_type)
|
||
|
%
|
||
|
% Available from http://www.mathworks.com/matlabcentral/fileexchange/28327
|
||
|
% Included here for convenience. Many thanks to Kevin Mattheus Moerman
|
||
|
% kevinmoerman@hotmail.com
|
||
|
% 15/07/2010
|
||
|
%------------------------------------------------------------------------
|
||
|
|
||
|
[J,I]=meshgrid(1:1:size(X,2)-1,1:1:size(X,1)-1);
|
||
|
|
||
|
switch tri_type
|
||
|
case 'f'%Forward slash
|
||
|
TRI_I=[I(:),I(:)+1,I(:)+1; I(:),I(:),I(:)+1];
|
||
|
TRI_J=[J(:),J(:)+1,J(:); J(:),J(:)+1,J(:)+1];
|
||
|
F = sub2ind(size(X),TRI_I,TRI_J);
|
||
|
case 'b'%Back slash
|
||
|
TRI_I=[I(:),I(:)+1,I(:); I(:)+1,I(:)+1,I(:)];
|
||
|
TRI_J=[J(:)+1,J(:),J(:); J(:)+1,J(:),J(:)+1];
|
||
|
F = sub2ind(size(X),TRI_I,TRI_J);
|
||
|
case 'x'%Cross
|
||
|
TRI_I=[I(:)+1,I(:); I(:)+1,I(:)+1; I(:),I(:)+1; I(:),I(:)];
|
||
|
TRI_J=[J(:),J(:); J(:)+1,J(:); J(:)+1,J(:)+1; J(:),J(:)+1];
|
||
|
IND=((numel(X)+1):numel(X)+prod(size(X)-1))';
|
||
|
F = sub2ind(size(X),TRI_I,TRI_J);
|
||
|
F(:,3)=repmat(IND,[4,1]);
|
||
|
Fe_I=[I(:),I(:)+1,I(:)+1,I(:)]; Fe_J=[J(:),J(:),J(:)+1,J(:)+1];
|
||
|
Fe = sub2ind(size(X),Fe_I,Fe_J);
|
||
|
Xe=mean(X(Fe),2); Ye=mean(Y(Fe),2); Ze=mean(Z(Fe),2);
|
||
|
X=[X(:);Xe(:)]; Y=[Y(:);Ye(:)]; Z=[Z(:);Ze(:)];
|
||
|
end
|
||
|
|
||
|
V=[X(:),Y(:),Z(:)];
|