Created
April 23, 2026 15:54
-
-
Save adielfernandez/b901d7acc4a4cc748dbdfa55a8c211d6 to your computer and use it in GitHub Desktop.
Utility script for removing duplicate prime pillars from gcode generated by Simplify3D when printing with dual extruders
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Remove Duplicate Prime Pillars from G-Code | |
| This script parses a gcode file and removes duplicate prime pillar | |
| sections within each layer. It keeps the first "; feature prime pillar" per layer | |
| and removes all subsequent duplicates (from the duplicate's marker to the next | |
| "; feature " line). | |
| Requirements: | |
| Python 3.9 or later (uses pathlib.Path.with_stem()) | |
| No external dependencies - uses only the standard library | |
| Usage: | |
| python RemoveDuplicatePrimePillars.py <input.gcode> [--output <output.gcode>] [--dry-run] | |
| NOTE: This was designed for S3D-generated gcode and may not work | |
| correctly with other slicers or custom gcode formats. | |
| Always back up your original files before running the script! | |
| """ | |
| import argparse | |
| import re | |
| import sys | |
| from pathlib import Path | |
| def parse_args(): | |
| """Parse command line arguments.""" | |
| parser = argparse.ArgumentParser( | |
| description="Remove duplicate prime pillar sections from G-Code files.", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| Examples: | |
| python RemoveDuplicatePrimePillars.py print.gcode | |
| python RemoveDuplicatePrimePillars.py print.gcode --output print_cleaned.gcode | |
| python RemoveDuplicatePrimePillars.py print.gcode --dry-run | |
| """ | |
| ) | |
| parser.add_argument( | |
| "input", | |
| type=str, | |
| help="Input G-Code file path" | |
| ) | |
| parser.add_argument( | |
| "--output", "-o", | |
| type=str, | |
| default=None, | |
| help="Output G-Code file path (default: <input>_cleaned.gcode)" | |
| ) | |
| parser.add_argument( | |
| "--dry-run", "-n", | |
| action="store_true", | |
| help="Preview changes without modifying any files" | |
| ) | |
| return parser.parse_args() | |
| def find_markers(lines): | |
| """ | |
| Find all layer and feature markers in the gcode. | |
| Returns: | |
| layer_lines: list of (line_number, layer_number) tuples | |
| prime_pillar_lines: list of line numbers with "; feature prime pillar" | |
| feature_lines: list of line numbers with any "; feature " marker | |
| """ | |
| layer_pattern = re.compile(r'^; layer (\d+)') | |
| prime_pillar_marker = "; feature prime pillar" | |
| feature_marker = "; feature " | |
| layer_lines = [] | |
| prime_pillar_lines = [] | |
| feature_lines = [] | |
| for i, line in enumerate(lines): | |
| stripped = line.strip() | |
| # Check for layer marker | |
| layer_match = layer_pattern.match(stripped) | |
| if layer_match: | |
| layer_num = int(layer_match.group(1)) | |
| layer_lines.append((i, layer_num)) | |
| # Check for prime pillar marker | |
| if stripped == prime_pillar_marker: | |
| prime_pillar_lines.append(i) | |
| # Check for any feature marker | |
| if stripped.startswith(feature_marker): | |
| feature_lines.append(i) | |
| return layer_lines, prime_pillar_lines, feature_lines | |
| def find_duplicates(layer_lines, prime_pillar_lines, feature_lines, total_lines): | |
| """ | |
| Find duplicate prime pillar sections within each layer. | |
| Returns: | |
| List of (start_line, end_line, layer_num) tuples for sections to remove. | |
| start_line is inclusive, end_line is exclusive. | |
| """ | |
| duplicates = [] | |
| # Add a virtual "end of file" layer boundary | |
| layer_boundaries = [line_num for line_num, _ in layer_lines] | |
| layer_boundaries.append(total_lines) | |
| for i, (layer_start, layer_num) in enumerate(layer_lines): | |
| layer_end = layer_boundaries[i + 1] | |
| # Find all prime pillars within this layer | |
| pillars_in_layer = [ | |
| pp for pp in prime_pillar_lines | |
| if layer_start <= pp < layer_end | |
| ] | |
| # If more than one, mark duplicates (skip the first one) | |
| if len(pillars_in_layer) > 1: | |
| for dup_line in pillars_in_layer[1:]: | |
| # Find the next feature marker after this duplicate | |
| next_feature = None | |
| for fl in feature_lines: | |
| if fl > dup_line: | |
| next_feature = fl | |
| break | |
| # If no next feature, extend to layer end | |
| if next_feature is None: | |
| next_feature = layer_end | |
| duplicates.append((dup_line, next_feature, layer_num)) | |
| return duplicates | |
| def remove_duplicates(lines, duplicates): | |
| """ | |
| Remove duplicate sections from lines. | |
| Returns: | |
| New list of lines with duplicates removed. | |
| """ | |
| # Create a set of line indices to remove | |
| lines_to_remove = set() | |
| for start, end, _ in duplicates: | |
| for i in range(start, end): | |
| lines_to_remove.add(i) | |
| # Build new lines list excluding removed lines | |
| new_lines = [ | |
| line for i, line in enumerate(lines) | |
| if i not in lines_to_remove | |
| ] | |
| return new_lines | |
| def main(): | |
| args = parse_args() | |
| # Validate input file | |
| input_path = Path(args.input) | |
| if not input_path.exists(): | |
| print(f"Error: Input file '{args.input}' not found.", file=sys.stderr) | |
| sys.exit(1) | |
| # Determine output path | |
| if args.output: | |
| output_path = Path(args.output) | |
| else: | |
| output_path = input_path.with_stem(input_path.stem + "_cleaned") | |
| # Read input file | |
| print(f"Reading: {input_path}") | |
| with open(input_path, 'r', encoding='utf-8') as f: | |
| lines = f.readlines() | |
| original_line_count = len(lines) | |
| print(f"Original lines: {original_line_count:,}") | |
| # Find markers | |
| layer_lines, prime_pillar_lines, feature_lines = find_markers(lines) | |
| print(f"Layers found: {len(layer_lines)}") | |
| print(f"Prime pillars found: {len(prime_pillar_lines)}") | |
| # Find duplicates | |
| duplicates = find_duplicates(layer_lines, prime_pillar_lines, feature_lines, len(lines)) | |
| if not duplicates: | |
| print("\nNo duplicate prime pillars found. File is clean!") | |
| sys.exit(0) | |
| # Report duplicates | |
| print(f"\nDuplicate prime pillars found: {len(duplicates)}") | |
| # Group by layer for reporting | |
| layers_with_dups = {} | |
| for start, end, layer_num in duplicates: | |
| if layer_num not in layers_with_dups: | |
| layers_with_dups[layer_num] = [] | |
| layers_with_dups[layer_num].append((start + 1, end - start)) # +1 for 1-indexed line numbers | |
| for layer_num in sorted(layers_with_dups.keys()): | |
| dups = layers_with_dups[layer_num] | |
| print(f" Layer {layer_num}: {len(dups)} duplicate(s)") | |
| for line_num, line_count in dups: | |
| print(f" - Line {line_num:,} ({line_count} lines)") | |
| # Calculate lines to remove | |
| total_lines_to_remove = sum(end - start for start, end, _ in duplicates) | |
| print(f"\nTotal lines to remove: {total_lines_to_remove:,}") | |
| if args.dry_run: | |
| print("\n[DRY RUN] No changes made.") | |
| sys.exit(0) | |
| # Remove duplicates | |
| new_lines = remove_duplicates(lines, duplicates) | |
| new_line_count = len(new_lines) | |
| # Write output file | |
| print(f"\nWriting: {output_path}") | |
| with open(output_path, 'w', encoding='utf-8') as f: | |
| f.writelines(new_lines) | |
| print(f"New line count: {new_line_count:,}") | |
| print(f"Lines removed: {original_line_count - new_line_count:,}") | |
| print("\nDone!") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment