ChatGPT helped me make this example workaround in the meantime, however more work is needed to export the drill files properly as they will currently be inncorrect for blind or buried vias. The naming convention for layers also assumes {metal, dielectric, metal, dielectric, metal...}.
% Assuming 'pcb' is your original pcbStack object with all layers
% Get the list of layers
allLayers = pcb.Layers;
numLayers = length(allLayers);
% Create an output folder to store the Gerber files
outputFolder = 'GerberFiles';
if ~exist(outputFolder, 'dir')
mkdir(outputFolder);
end
% Initialize a cell array to collect all generated Gerber files
allGerberFiles = {};
% Process the Top Layer (Outer Layer)
layerIdx = 1;
fprintf('Processing Top Layer %d...\n', layerIdx);
% Get the top metal layer
metalLayer1 = allLayers{layerIdx};
% Get the dielectric layer between the top and the next metal layer
dielectricLayer = allLayers{layerIdx + 1};
% Build the minimal pcbStack for the top layer
minimalPcb = pcbStack;
minimalPcb.Name = sprintf('Layer%d_PCB', layerIdx);
% Set BoardThickness before Layers
minimalPcb.BoardThickness = dielectricLayer.Thickness;
% Set the Layers (only include the top metal layer and dielectric below)
minimalPcb.Layers = {metalLayer1, dielectricLayer};
% Extract FeedLocations from pcb
feedLocations = pcb.FeedLocations;
if ~isempty(feedLocations)
% Map the layer indices in feedLocations to minimalPcb layers
% Only keep feed locations on layerIdx
feedLayers = feedLocations(:, 3);
% Identify feed locations on the current layer
validFeeds = (feedLayers == layerIdx);
if any(validFeeds)
% Map layer indices
feedLocations = feedLocations(validFeeds, :);
feedLocations(:, 3) = 1; % Map to minimalPcb layer 1
% Assign to minimalPcb
minimalPcb.FeedLocations = feedLocations;
else
% No feed locations on this layer
minimalPcb.FeedLocations = [0,0,1];
end
else
minimalPcb.FeedLocations = [];
end
% Assign vias connecting to this layer (if any)
% For the top layer, we consider vias starting or ending at layerIdx
viaIndices = (pcb.ViaLocations(:, 3) == layerIdx) | (pcb.ViaLocations(:, 4) == layerIdx);
viaLocations = pcb.ViaLocations(viaIndices, 1:2);
startLayers = pcb.ViaLocations(viaIndices, 3);
endLayers = pcb.ViaLocations(viaIndices, 4);
% Map layers to minimalPcb layers (layerIdx maps to 1)
startLayers(startLayers == layerIdx) = 1;
endLayers(endLayers == layerIdx) = 1;
% Exclude vias not connected within this minimalPcb
validVias = (startLayers <= 2) & (endLayers <= 2);
viaLocations = viaLocations(validVias, :);
startLayers = startLayers(validVias);
endLayers = endLayers(validVias);
% Assign vias to the minimalPcb
minimalPcb.ViaLocations = [viaLocations, startLayers, endLayers]
minimalPcb.ViaDiameter = pcb.ViaDiameter;
% Create the PCBWriter object
W = PCBServices.PCBWayWriter; % Or PCBServices.MayhewWriter
W.Filename = sprintf('Layer%d', layerIdx);
% Disable the website launch by setting PostWriteFcn to an empty function
W.PostWriteFcn = @(obj)disp('PostWriteFcn disabled.');
% Create the PCBWriter object
PcbWriteObj = PCBWriter(minimalPcb, W);
% Configure PCBWriter to avoid generating unnecessary files
PcbWriteObj.Solderpaste = false; % Disable paste masks if not needed
PcbWriteObj.Soldermask = 'top'; % Generate only top soldermask
% Remove default design info to prevent extra files
removeDefaultDesignInfo(PcbWriteObj);
% Generate the Gerber files without creating a zip archive
try
gerberWrite(PcbWriteObj);
fprintf('Generated Gerber files for Top Layer %d.\n', layerIdx);
% If the output is a ZIP file, extract its contents
zipFile = [W.Filename, '.zip'];
if isfile(zipFile)
unzip(zipFile, outputFolder); % Extract ZIP to output folder
% delete(fullfile(outputFolder, [W.Filename, '.zip'])); % Delete the ZIP file after extraction
else
% Move the generated files to the output folder
movefile([W.Filename, '*'], outputFolder);
end
% Delete unnecessary files
delete(fullfile(outputFolder, [W.Filename, '.gbl'])); % Delete bottom copper layer file
delete(fullfile(outputFolder, [W.Filename, '.gbo'])); % Delete bottom silkscreen
delete(fullfile(outputFolder, [W.Filename, '.gpi'])); % Delete photoplotter info
delete(fullfile(outputFolder, [W.Filename, '.ipc'])); % Delete IPC netlist if not needed
% Store the filenames
gerberFiles = dir(fullfile(outputFolder, [W.Filename, '*']));
gerberFileNames = {gerberFiles.name}';
allGerberFiles = [allGerberFiles; fullfile(outputFolder, gerberFileNames)];
catch ME
fprintf('Error generating Gerber files for Top Layer %d: %s\n', layerIdx, ME.message);
end
% Process Inner Layers
for layerIdx = 2:numLayers-1
% Check if the current layer is a metal layer
if isa(allLayers{layerIdx}, 'antenna.Shape') || isa(allLayers{layerIdx}, 'antenna.Group')
fprintf('Processing Inner Layer %d...\n', 0.5+layerIdx/2);
% Get the metal layer
metalLayer = allLayers{layerIdx};
% Get the dielectric layers above and below
dielectricLayerAbove = allLayers{layerIdx - 1};
dielectricLayerBelow = allLayers{layerIdx + 1};
AboveThickness = dielectricLayerAbove.Thickness;
BelowThickness = dielectricLayerBelow.Thickness;
% Build the minimal pcbStack for the inner layer
minimalPcb = pcbStack;
minimalPcb.Name = sprintf('Layer%d_PCB', 0.5+layerIdx/2);
% Adjust dielectric layers' thicknesses and BoardThickness
minimalPcb.BoardThickness = BelowThickness + AboveThickness;
minimalPcb.Layers = {dielectricLayerAbove, metalLayer, dielectricLayerBelow};
% No FeedLocations for inner layers
minimalPcb.FeedLocations = [0,0,2];
% No vias for inner layers
minimalPcb.ViaLocations = [];
% Create the PCBWriter object
W = PCBServices.PCBWayWriter;
W.Filename = sprintf('Layer%d', 0.5+layerIdx/2);
% Disable the website launch by setting PostWriteFcn to an empty function
W.PostWriteFcn = @(obj)disp('PostWriteFcn disabled.');
% Create the PCBWriter object
PcbWriteObj = PCBWriter(minimalPcb, W);
% Configure PCBWriter to generate only copper layers
PcbWriteObj.Soldermask = 'none';
PcbWriteObj.Solderpaste = false;
removeDefaultDesignInfo(PcbWriteObj);
% Generate the Gerber files without creating a zip archive
try
gerberWrite(PcbWriteObj); % Add 'Archive', false
fprintf('Generated Gerber files for Inner Layer %d.\n', 0.5+layerIdx/2);
% If the output is a ZIP file, extract its contents
zipFile = [W.Filename, '.zip'];
if isfile(zipFile)
unzip(zipFile, outputFolder); % Extract ZIP to output folder
% delete(fullfile(outputFolder, [W.Filename, '.zip'])); % Delete the ZIP file after extraction
else
% Move the generated files to the output folder
movefile([W.Filename, '*'], outputFolder);
end
% Delete unnecessary files
delete(fullfile(outputFolder, [W.Filename, '.gto']));
delete(fullfile(outputFolder, [W.Filename, '.gbo']));
delete(fullfile(outputFolder, [W.Filename, '.gts']));
delete(fullfile(outputFolder, [W.Filename, '.gpi']));
delete(fullfile(outputFolder, [W.Filename, '.ipc']));
delete(fullfile(outputFolder, [W.Filename, '.gko']));
delete(fullfile(outputFolder, [W.Filename, '.gbl']));
renameInnerCopper(outputFolder, 0.5+layerIdx/2);
% Store the filenames
gerberFiles = dir(fullfile(outputFolder, [W.Filename, '*']));
gerberFileNames = {gerberFiles.name}';
allGerberFiles = [allGerberFiles; fullfile(outputFolder, gerberFileNames)];
catch ME
fprintf('Error generating Gerber files for Inner Layer %d: %s\n', 0.5+layerIdx/2, ME.message);
end
end
end
% Process the Bottom Layer (Outer Layer)
layerIdx = numLayers;
fprintf('Processing Bottom Layer %d...\n', 0.5+layerIdx/2);
% Get the bottom metal layer
metalLayerN = allLayers{layerIdx};
% Get the dielectric layer above it
dielectricLayer = allLayers{layerIdx - 1};
% Build the minimal pcbStack for the bottom layer
minimalPcb = pcbStack;
minimalPcb.Name = sprintf('Layer%d_PCB', 0.5+layerIdx/2);
% Set BoardThickness before Layers
minimalPcb.BoardThickness = dielectricLayer.Thickness;
% Set the Layers (include dielectric above and bottom metal layer)
minimalPcb.Layers = {dielectricLayer, metalLayerN};
% Extract FeedLocations from pcb (if any on bottom layer)
feedLocations = pcb.FeedLocations;
if ~isempty(feedLocations)
% Map the layer indices in feedLocations to minimalPcb layers
% Only keep feed locations on layerIdx
feedLayers = feedLocations(:, 3);
% Identify feed locations on the current layer
validFeeds = (feedLayers == layerIdx);
if any(validFeeds)
% Map layer indices
feedLocations = feedLocations(validFeeds, :);
feedLocations(:, 3) = 2; % Map to minimalPcb layer 2
% Assign to minimalPcb
minimalPcb.FeedLocations = feedLocations;
else
% No feed locations on this layer
minimalPcb.FeedLocations = [0,0,2];
end
else
minimalPcb.FeedLocations = [];
end
% Assign vias connecting to this layer (if any)
viaIndices = (pcb.ViaLocations(:, 3) == layerIdx) | (pcb.ViaLocations(:, 4) == layerIdx);
viaLocations = pcb.ViaLocations(viaIndices, 1:2);
startLayers = pcb.ViaLocations(viaIndices, 3);
endLayers = pcb.ViaLocations(viaIndices, 4);
% Map layers to minimalPcb layers (layerIdx maps to 2)
startLayers(startLayers == layerIdx - 1) = 1; % Dielectric layer
startLayers(startLayers == layerIdx) = 2; % Metal layer
endLayers(endLayers == layerIdx - 1) = 1;
endLayers(endLayers == layerIdx) = 2;
% Exclude vias not connected within this minimalPcb
validVias = (startLayers >= 1 & startLayers <= 2) & (endLayers >= 1 & endLayers <= 2);
viaLocations = viaLocations(validVias, :);
startLayers = startLayers(validVias);
endLayers = endLayers(validVias);
% Assign vias to the minimalPcb
minimalPcb.ViaLocations = [viaLocations, startLayers, endLayers];
minimalPcb.ViaDiameter = pcb.ViaDiameter;
% Create the PCBWriter object
W = PCBServices.PCBWayWriter;
W.Filename = sprintf('Layer%d', 0.5+layerIdx/2);
% Disable the website launch by setting PostWriteFcn to an empty function
W.PostWriteFcn = @(obj)disp('PostWriteFcn disabled.');
% Create the PCBWriter object
PcbWriteObj = PCBWriter(minimalPcb, W);
% Configure PCBWriter to avoid generating unnecessary files
PcbWriteObj.Solderpaste = false;
PcbWriteObj.Soldermask = 'bottom'; % Generate only bottom soldermask
removeDefaultDesignInfo(PcbWriteObj);
% Generate the Gerber files without creating a zip archive
try
gerberWrite(PcbWriteObj); % Add 'Archive', false
fprintf('Generated Gerber files for Bottom Layer %d.\n', 0.5+layerIdx/2);
% If the output is a ZIP file, extract its contents
zipFile = [W.Filename, '.zip'];
if isfile(zipFile)
unzip(zipFile, outputFolder); % Extract ZIP to output folder
% delete(fullfile(outputFolder, [W.Filename, '.zip'])); % Delete the ZIP file after extraction
else
% Move the generated files to the output folder
movefile([W.Filename, '*'], outputFolder);
end
% Delete unnecessary files
delete(fullfile(outputFolder, [W.Filename, '.gtl'])); % Delete top copper layer file
delete(fullfile(outputFolder, [W.Filename, '.gto'])); % Delete top silkscreen
delete(fullfile(outputFolder, [W.Filename, '.gpi'])); % Delete photoplotter info
delete(fullfile(outputFolder, [W.Filename, '.ipc'])); % Delete IPC netlist if not needed
% Store the filenames
gerberFiles = dir(fullfile(outputFolder, [W.Filename, '*']));
gerberFileNames = {gerberFiles.name}';
allGerberFiles = [allGerberFiles; fullfile(outputFolder, gerberFileNames)];
catch ME
fprintf('Error generating Gerber files for Bottom Layer %d: %s\n', 0.5+layerIdx/2, ME.message);
end
function renameInnerCopper(outputFolder, layerIdx)
% Constructs the expected original and new filenames
originalFilename = sprintf('Layer%d.gtl', layerIdx);
newExtension = sprintf('.g%d', layerIdx);
newFilename = sprintf('Layer%d%s', layerIdx, newExtension);
originalPath = fullfile(outputFolder, originalFilename);
newPath = fullfile(outputFolder, newFilename);
% Check if the original file exists before renaming
if isfile(originalPath)
movefile(originalPath, newPath);
fprintf('Renamed %s to %s.\n', originalFilename, newFilename);
else
fprintf('Warning: Expected file %s not found for renaming.\n', originalFilename);
end
end