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')
0001 function stlwrite(filename, varargin) 0002 %STLWRITE Write STL file from patch or surface data. 0003 % 0004 % STLWRITE(FILE,fv) writes a stereolithography (STL) file to FILE for a triangulated 0005 % patch defined by FV (a structure with fields 'vertices' and 'faces'). 0006 % 0007 % STLWRITE(FILE,FACES,VERTICES) takes faces and vertices separately, rather than in an FV struct 0008 % 0009 % STLWRITE(FILE,X,Y,Z) creates an STL file from surface data in X, Y, and Z. STLWRITE triangulates 0010 % this gridded data into a triangulated surface using triangulations options specified below. X, Y 0011 % and Z can be two-dimensional arrays with the same size. If X and Y are vectors with length equal 0012 % to SIZE(Z,2) and SIZE(Z,1), respectively, they are passed through MESHGRID to create gridded 0013 % data. If X or Y are scalar values, they are used to specify the X and Y spacing between grid 0014 % points. 0015 % 0016 % STLWRITE(...,'PropertyName',VALUE,'PropertyName',VALUE,...) writes an STL file using the 0017 % following property values: 0018 % 0019 % MODE - File is written using 'binary' (default) or 'ascii'. 0020 % 0021 % TITLE - Header text (max 80 characters) written to the STL file. 0022 % 0023 % TRIANGULATION - When used with gridded data, TRIANGULATION is either: 0024 % 'delaunay' - (default) Delaunay triangulation of X, Y 0025 % 'f' - Forward slash division of grid quadrilaterals 0026 % 'b' - Back slash division of quadrilaterals 0027 % 'x' - Cross division of quadrilaterals 0028 % Note that 'f', 'b', or 't' triangulations require FEX entry 28327, "mesh2tri". 0029 % 0030 % FACECOLOR - (not currently implemented) When used with face/vertex input, specifies the 0031 % colour of each triangle face. If users request this feature, I will attempt to 0032 % implement it. 0033 % 0034 % Example 1: 0035 % % Write binary STL from face/vertex data 0036 % tmpvol = zeros(20,20,20); % Empty voxel volume 0037 % tmpvol(8:12,8:12,5:15) = 1; % Turn some voxels on 0038 % fv = isosurface(tmpvol, 0.99); % Create the patch object 0039 % stlwrite('test.stl',fv) % Save to binary .stl 0040 % 0041 % Example 2: 0042 % % Write ascii STL from gridded data 0043 % [X,Y] = deal(1:40); % Create grid reference 0044 % Z = peaks(40); % Create grid height 0045 % stlwrite('test.stl',X,Y,Z,'mode','ascii') 0046 0047 % Original idea adapted from surf2stl by Bill McDonald. Huge speed 0048 % improvements implemented by Oliver Woodford. Non-Delaunay triangulation 0049 % of quadrilateral surface input requires mesh2tri by Kevin Moerman. 0050 % 0051 % Author: Sven Holcombe, 11-24-11 0052 0053 0054 % % Check valid filename path 0055 % narginchk(2, inf) 0056 % path = fileparts(filename); 0057 % if ~isempty(path) && ~exist(path,'dir') 0058 % error('Directory "%s" does not exist.',path); 0059 % end 0060 0061 % Get faces, vertices, and user-defined options for writing 0062 [faces, vertices, options] = parseInputs(varargin{:}); 0063 asciiMode = strcmp( options.mode ,'ascii'); 0064 0065 % Create the facets 0066 facets = single(vertices'); 0067 facets = reshape(facets(:,faces'), 3, 3, []); 0068 0069 % Compute their normals 0070 V1 = squeeze(facets(:,2,:) - facets(:,1,:)); 0071 V2 = squeeze(facets(:,3,:) - facets(:,1,:)); 0072 normals = V1([2 3 1],:) .* V2([3 1 2],:) - V2([2 3 1],:) .* V1([3 1 2],:); 0073 clear V1 V2 0074 normals = bsxfun(@times, normals, 1 ./ sqrt(sum(normals .* normals, 1))); 0075 facets = cat(2, reshape(normals, 3, 1, []), facets); 0076 clear normals 0077 0078 % Open the file for writing 0079 permissions = {'w','wb+'}; 0080 fid = fopen(filename, permissions{asciiMode+1}); 0081 if (fid == -1) 0082 error('stlwrite:cannotWriteFile', 'Unable to write to %s', filename); 0083 end 0084 0085 % Write the file contents 0086 if asciiMode 0087 % Write HEADER 0088 fprintf(fid,'solid %s\r\n',options.title); 0089 % Write DATA 0090 fprintf(fid,[... 0091 'facet normal %.7E %.7E %.7E\r\n' ... 0092 'outer loop\r\n' ... 0093 'vertex %.7E %.7E %.7E\r\n' ... 0094 'vertex %.7E %.7E %.7E\r\n' ... 0095 'vertex %.7E %.7E %.7E\r\n' ... 0096 'endloop\r\n' ... 0097 'endfacet\r\n'], facets); 0098 % Write FOOTER 0099 fprintf(fid,'endsolid %s\r\n',options.title); 0100 0101 else % BINARY 0102 % Write HEADER 0103 fprintf(fid, '%-80s', options.title); % Title 0104 fwrite(fid, size(facets, 3), 'uint32'); % Number of facets 0105 % Write DATA 0106 % Add one uint16(0) to the end of each facet using a typecasting trick 0107 facets = reshape(typecast(facets(:), 'uint16'), 12*2, []); 0108 facets(end+1,:) = 0; 0109 fwrite(fid, facets, 'uint16'); 0110 end 0111 0112 % Close the file 0113 fclose(fid); 0114 fprintf('Wrote %d facets\n',size(facets, 3)); 0115 0116 0117 %% Input handling subfunctions 0118 function [faces, vertices, options] = parseInputs(varargin) 0119 % Determine input type 0120 if isstruct(varargin{1}) % stlwrite('file', FVstruct, ...) 0121 if ~all(isfield(varargin{1},{'vertices','faces'})) 0122 error( 'Variable p must be a faces/vertices structure' ); 0123 end 0124 faces = varargin{1}.faces; 0125 vertices = varargin{1}.vertices; 0126 options = parseOptions(varargin{2:end}); 0127 0128 elseif isnumeric(varargin{1}) 0129 firstNumInput = cellfun(@isnumeric,varargin); 0130 firstNumInput(find(~firstNumInput,1):end) = 0; % Only consider numerical input PRIOR to the first non-numeric 0131 numericInputCnt = nnz(firstNumInput); 0132 0133 options = parseOptions(varargin{numericInputCnt+1:end}); 0134 switch numericInputCnt 0135 case 3 % stlwrite('file', X, Y, Z, ...) 0136 % Extract the matrix Z 0137 Z = varargin{3}; 0138 0139 % Convert scalar XY to vectors 0140 ZsizeXY = fliplr(size(Z)); 0141 for i = 1:2 0142 if isscalar(varargin{i}) 0143 varargin{i} = (0:ZsizeXY(i)-1) * varargin{i}; 0144 end 0145 end 0146 0147 % Extract X and Y 0148 if isequal(size(Z), size(varargin{1}), size(varargin{2})) 0149 % X,Y,Z were all provided as matrices 0150 [X,Y] = varargin{1:2}; 0151 elseif numel(varargin{1})==ZsizeXY(1) && numel(varargin{2})==ZsizeXY(2) 0152 % Convert vector XY to meshgrid 0153 [X,Y] = meshgrid(varargin{1}, varargin{2}); 0154 else 0155 error('stlwrite:badinput', 'Unable to resolve X and Y variables'); 0156 end 0157 0158 % Convert to faces/vertices 0159 if strcmp(options.triangulation,'delaunay') 0160 faces = delaunay(X,Y); 0161 vertices = [X(:) Y(:) Z(:)]; 0162 else 0163 if ~exist('mesh2tri','file') 0164 error('stlwrite:missing', '"mesh2tri" is required to convert X,Y,Z matrices to STL. It can be downloaded from:\n%s\n',... 0165 'http://www.mathworks.com/matlabcentral/fileexchange/28327') 0166 end 0167 [faces, vertices] = mesh2tri(X, Y, Z, options.triangulation); 0168 end 0169 0170 case 2 % stlwrite('file', FACES, VERTICES, ...) 0171 faces = varargin{1}; 0172 vertices = varargin{2}; 0173 0174 otherwise 0175 error('stlwrite:badinput', 'Unable to resolve input types.'); 0176 end 0177 0178 end 0179 0180 function options = parseOptions(varargin) 0181 IP = inputParser; 0182 IP.addParamValue('mode', 'binary', @ischar) 0183 IP.addParamValue('title', sprintf('Created by stlwrite.m %s',datestr(now)), @ischar); 0184 IP.addParamValue('triangulation', 'delaunay', @ischar); 0185 IP.addParamValue('facecolor',[], @isnumeric) 0186 IP.parse(varargin{:}); 0187 options = IP.Results;