From 8bd84d5e04a80ef4b55b43450b2de2fce67a0d22 Mon Sep 17 00:00:00 2001 From: TenTech <45683060+TengerTechnologies@users.noreply.github.com> Date: Sat, 1 Feb 2025 19:21:37 +0100 Subject: [PATCH] Add files via upload --- bricklayersNonPlanarInfill.py | 389 ++++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 bricklayersNonPlanarInfill.py diff --git a/bricklayersNonPlanarInfill.py b/bricklayersNonPlanarInfill.py new file mode 100644 index 0000000..0fdbb0d --- /dev/null +++ b/bricklayersNonPlanarInfill.py @@ -0,0 +1,389 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Copyright (c) [2025] [Roman Tenger] +import re +import sys +import logging +import os +import argparse +import math + +# Get the directory where the script is located +script_dir = os.path.dirname(os.path.abspath(__file__)) + +# Configure logging to save in the script's directory +log_file_path = os.path.join(script_dir, "z_shift_log.txt") +logging.basicConfig( + filename=log_file_path, + filemode="w", + level=logging.INFO, + format="%(asctime)s - %(message)s" +) + +# Add these constants from nonPlanarInfill.py +DEFAULT_AMPLITUDE = 0.6 # Default Z variation in mm +DEFAULT_FREQUENCY = 1.1 # Default frequency of the sine wave +SEGMENT_LENGTH = 1.0 # Split infill lines into segments of this length (mm) + +# Add these helper functions from nonPlanarInfill.py +def segment_line(x1, y1, x2, y2, segment_length): + """Divide a line into smaller segments.""" + segments = [] + total_length = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) + num_segments = max(1, int(total_length // segment_length)) + + for i in range(num_segments + 1): + t = i / num_segments + x = x1 + t * (x2 - x1) + y = y1 + t * (y2 - y1) + segments.append((x, y)) + + logging.debug(f"Segmented line ({x1}, {y1}) -> ({x2}, {y2}) into {len(segments)} segments.") + return segments + +def reset_modulation_state(): + """Reset parameters for Z-modulation to avoid propagating patterns.""" + global last_sx + last_sx = 0 + +def update_layer_bounds(current_z, solid_infill_heights): + """Update the bounds for non-planar processing based on current Z height.""" + global last_bottom_layer, next_top_layer + lower_layers = [z for z in solid_infill_heights if z < current_z] + upper_layers = [z for z in solid_infill_heights if z > current_z] + if lower_layers: + last_bottom_layer = max(lower_layers) + if upper_layers: + next_top_layer = min(upper_layers) + +def process_nonplanar_infill(lines, current_z, amplitude, frequency, solid_infill_heights): + """Process only the non-planar infill modifications.""" + modified_lines = [] + in_infill = False + last_bottom_layer = 0 + next_top_layer = float('inf') + processed_indices = set() + + def update_layer_bounds(current_z): + nonlocal last_bottom_layer, next_top_layer + lower_layers = [z for z in solid_infill_heights if z < current_z] + upper_layers = [z for z in solid_infill_heights if z > current_z] + if lower_layers: + last_bottom_layer = max(lower_layers) + if upper_layers: + next_top_layer = min(upper_layers) + + for line_num, line in enumerate(lines): + if line.startswith('G1') and 'Z' in line: + z_match = re.search(r'Z([-+]?\d*\.?\d+)', line) + if z_match: + current_z = float(z_match.group(1)) + update_layer_bounds(current_z) + + if ';TYPE:Internal infill' in line: + in_infill = True + modified_lines.append(line) + continue + elif line.startswith(';TYPE:'): + in_infill = False + + if in_infill and line_num not in processed_indices and line.startswith('G1') and 'E' in line: + processed_indices.add(line_num) + match = re.search(r'X([-+]?\d*\.?\d+)\s*Y([-+]?\d*\.?\d+)\s*E([-+]?\d*\.?\d+)', line) + if match: + x1, y1, e = map(float, match.groups()) + next_line_index = line_num + 1 + + if next_line_index < len(lines): + next_line = lines[next_line_index] + next_match = re.search(r'X([-+]?\d*\.?\d+)\s*Y([-+]?\d*\.?\d+)', next_line) + if next_match: + x2, y2 = map(float, next_match.groups()) + segments = segment_line(x1, y1, x2, y2, SEGMENT_LENGTH) + + distance_to_top = next_top_layer - current_z + distance_to_bottom = current_z - last_bottom_layer + total_distance = next_top_layer - last_bottom_layer + if total_distance > 0: + scaling_factor = min(distance_to_top, distance_to_bottom) / total_distance + else: + scaling_factor = 1.0 + + extrusion_per_segment = e / len(segments) + + for i, (sx, sy) in enumerate(segments): + z_mod = current_z + amplitude * scaling_factor * math.sin(frequency * sx) + + # Simple correction factor based on segment height difference + dz = abs(z_mod - current_z) + segment_2d = SEGMENT_LENGTH + segment_3d = math.sqrt(segment_2d**2 + dz**2) + correction_factor = segment_3d / segment_2d + + modified_lines.append( + f"G1 X{sx:.3f} Y{sy:.3f} Z{z_mod:.3f} " + f"E{(extrusion_per_segment * correction_factor):.5f} ; Correction factor: {correction_factor:.3f} Original E: {extrusion_per_segment:.5f}\n" + ) + continue + + modified_lines.append(line) + + return modified_lines + +def process_wall_shifting(lines, layer_height, extrusion_multiplier, enable_wall_reorder=True): + """Process only the wall shifting modifications.""" + current_layer = 0 + current_z = 0.0 + perimeter_type = None + perimeter_block_count = 0 + inside_perimeter_block = False + previous_g1_movement = None + previous_f_speed = None + z_shift = layer_height * 0.5 + + # Add buffers for shifted and non-shifted walls (only used if wall_reorder is enabled) + shifted_wall_buffer = [] + nonshifted_wall_buffer = [] + current_wall_buffer = [] + + total_layers = sum(1 for line in lines if line.startswith(";AFTER_LAYER_CHANGE")) + modified_lines = [] + + for line in lines: + # Detect layer changes + if line.startswith("G1 Z"): + z_match = re.search(r'Z([-\d.]+)', line) + if z_match: + current_z = float(z_match.group(1)) + current_layer = int(current_z / layer_height) + perimeter_block_count = 0 # Reset block counter for new layer + logging.info(f"Layer {current_layer} detected at Z={current_z:.3f}") + modified_lines.append(line) + continue + + # Detect perimeter types from PrusaSlicer comments + if ";TYPE:External perimeter" in line or ";TYPE:Outer wall" in line: + if enable_wall_reorder: + # Output any buffered walls when switching to external perimeter + if shifted_wall_buffer or nonshifted_wall_buffer: + # Output non-shifted walls first + for wall in nonshifted_wall_buffer: + modified_lines.extend(wall) + # Then output shifted walls + for wall in shifted_wall_buffer: + modified_lines.extend(wall) + # Clear buffers + shifted_wall_buffer = [] + nonshifted_wall_buffer = [] + + perimeter_type = "external" + inside_perimeter_block = False + logging.info(f"External perimeter detected at layer {current_layer}") + modified_lines.append(line) + elif ";TYPE:Perimeter" in line or ";TYPE:Inner wall" in line: + perimeter_type = "internal" + inside_perimeter_block = False + if enable_wall_reorder: + current_wall_buffer = [] # Start a new wall buffer + logging.info(f"Internal perimeter block started at layer {current_layer}") + modified_lines.append(line) + elif ";TYPE:" in line: # Reset for other types + if enable_wall_reorder: + # Output any remaining buffered walls + if shifted_wall_buffer or nonshifted_wall_buffer: + for wall in nonshifted_wall_buffer: + modified_lines.extend(wall) + for wall in shifted_wall_buffer: + modified_lines.extend(wall) + shifted_wall_buffer = [] + nonshifted_wall_buffer = [] + + perimeter_type = None + inside_perimeter_block = False + modified_lines.append(line) + + # Group lines into perimeter blocks + elif perimeter_type == "internal" and line.startswith("G1") and "X" in line and "Y" in line and "E" in line: + # Start a new perimeter block if not already inside one + if not inside_perimeter_block: + perimeter_block_count += 1 + inside_perimeter_block = True + if enable_wall_reorder: + current_wall_buffer = [] # Start a new wall buffer + + # Add the cached movement command first + if previous_g1_movement: + if enable_wall_reorder: + current_wall_buffer.append(f"{previous_g1_movement};Previous position\n") + current_wall_buffer.append(f"G1 F{previous_f_speed:.3f} ; F speed from previous G1 movement\n") + + + # Set Z height and determine if wall is shifted + is_shifted = perimeter_block_count % 2 == 1 + if is_shifted: + adjusted_z = current_z + z_shift + z_command = f"G1 Z{adjusted_z:.3f} ; Shifted Z for block #{perimeter_block_count}\n" + else: + z_command = f"G1 Z{current_z:.3f} ; Reset Z for block #{perimeter_block_count}\n" + + if enable_wall_reorder: + current_wall_buffer.append(z_command) + else: + modified_lines.append(z_command) + + # Process the current line (including extrusion adjustments) + if is_shifted: + e_match = re.search(r'E([-\d.]+)', line) + if e_match: + e_value = float(e_match.group(1)) + original_line = line + if current_layer == 1: # First layer + new_e_value = e_value * 1.5 # 50% more extrusion + line = re.sub(r'E[-\d.]+', f'E{new_e_value:.5f}', line).strip() + line += f" ; Adjusted E for first layer (1.5x), block #{perimeter_block_count}\n" + elif current_layer == total_layers - 1: # Last layer + new_e_value = e_value * 0.5 # 50% less extrusion + line = re.sub(r'E[-\d.]+', f'E{new_e_value:.5f}', line).strip() + line += f" ; Adjusted E for last layer (0.5x), block #{perimeter_block_count}\n" + else: # Regular layers + line += f" ; current layer: {current_layer} total layers: {total_layers} \n" + new_e_value = e_value * extrusion_multiplier + line = re.sub(r'E[-\d.]+', f'E{new_e_value:.5f}', line).strip() + line += f" ; Adjusted E for regular layer ({extrusion_multiplier}x), block #{perimeter_block_count}\n" + + + if enable_wall_reorder: + current_wall_buffer.append(line) + else: + modified_lines.append(line) + + + + elif perimeter_type == "internal" and line.startswith("G1") and "X" in line and "Y" in line and "F" in line: + # End of perimeter block + if inside_perimeter_block: + if enable_wall_reorder: + current_wall_buffer.append(line) + # Add Z reset for shifted blocks + if is_shifted: + current_wall_buffer.append(f"G1 Z{current_z:.3f} ; Reset Z after shifted block #{perimeter_block_count}\n") + # Add completed wall to appropriate buffer + if is_shifted: + shifted_wall_buffer.append(current_wall_buffer) + else: + nonshifted_wall_buffer.append(current_wall_buffer) + else: + modified_lines.append(line) + if is_shifted: + modified_lines.append(f"G1 Z{current_z:.3f} ; Reset Z after shifted block #{perimeter_block_count}\n") + inside_perimeter_block = False + + elif perimeter_type == "internal" and line.startswith("G1") and "F" in line: #fix for Fspeed movements inside perimeter blocks + if enable_wall_reorder: + current_wall_buffer.append(line) + else: + modified_lines.append(line) + # Cache G1 movements with X and Y coordinates and F speeds + if line.startswith("G1"): + if "X" in line and "Y" in line: + previous_g1_movement = line.strip() + logging.info(f"Cached G1 movement: {previous_g1_movement}") + if "F" in line: + f_match = re.search(r'F([\d.]+)', line) + if f_match: + previous_f_speed = float(f_match.group(1)) + logging.info(f"Cached F speed: {previous_f_speed}") + + # Add non-wall lines directly to output + if not inside_perimeter_block and not perimeter_type == "internal": + modified_lines.append(line) + + return modified_lines + +def get_layer_height(gcode_lines): + """Extract layer height from G-code header comments""" + for line in gcode_lines: + if "layer_height =" in line.lower(): + match = re.search(r'layer_height = (\d*\.?\d+)', line, re.IGNORECASE) + if match: + return float(match.group(1)) + return None + +def process_gcode(input_file, extrusion_multiplier, enable_nonplanar=False, enable_wall_reorder=True, amplitude=DEFAULT_AMPLITUDE, frequency=DEFAULT_FREQUENCY): + logging.info("Starting G-code processing") + logging.info(f"Input file: {input_file}") + + # Read the input G-code + with open(input_file, 'r') as infile: + lines = infile.readlines() + + # Get layer height from G-code + layer_height = get_layer_height(lines) + if layer_height is None: + layer_height = 0.2 # Default fallback value + logging.warning(f"Could not detect layer height from G-code, using default value: {layer_height}mm") + else: + logging.info(f"Detected layer height from G-code: {layer_height}mm") + + # First pass: Process non-planar infill if enabled + if enable_nonplanar: + logging.info("Processing non-planar infill modifications...") + solid_infill_heights = [] + current_z = 0.0 + + # Collect solid infill heights + for line in lines: + if line.startswith('G1') and 'Z' in line: + z_match = re.search(r'Z([-+]?\d*\.?\d+)', line) + if z_match: + current_z = float(z_match.group(1)) + if ';TYPE:Solid infill' in line: + solid_infill_heights.append(current_z) + logging.info(f"Found solid infill at Z={current_z}") + + # Process non-planar infill + lines = process_nonplanar_infill(lines, current_z, amplitude, frequency, solid_infill_heights) + logging.info("Non-planar infill processing completed") + + # Second pass: Process wall shifting + logging.info("Processing wall shifting modifications...") + modified_lines = process_wall_shifting(lines, layer_height, extrusion_multiplier, enable_wall_reorder) + logging.info("Wall shifting processing completed") + + # Write the final modified G-code + with open(input_file, 'w') as outfile: + outfile.writelines(modified_lines) + + logging.info("G-code processing completed") + logging.info(f"Log file saved at {log_file_path}") + +# Main execution +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Post-process G-code for Z-shifting, extrusion adjustments, and non-planar infill.") + parser.add_argument("input_file", help="Path to the input G-code file") + parser.add_argument("-extrusionMultiplier", type=float, default=1, help="Extrusion multiplier (default: 1.0)") + parser.add_argument("-nonPlanar", type=int, choices=[0, 1], default=0, help="Enable non-planar infill (0=off, 1=on)") + parser.add_argument("-wallReorder", type=int, choices=[0, 1], default=1, help="Enable wall reordering (0=off, 1=on)") + parser.add_argument("-amplitude", type=float, default=DEFAULT_AMPLITUDE, help=f"Amplitude of the Z modulation (default: {DEFAULT_AMPLITUDE})") + parser.add_argument("-frequency", type=float, default=DEFAULT_FREQUENCY, help=f"Frequency of the Z modulation (default: {DEFAULT_FREQUENCY})") + args = parser.parse_args() + + process_gcode( + input_file=args.input_file, + extrusion_multiplier=args.extrusionMultiplier, + enable_nonplanar=bool(args.nonPlanar), + enable_wall_reorder=bool(args.wallReorder), + amplitude=args.amplitude, + frequency=args.frequency, + ) \ No newline at end of file