mirror of
				https://github.com/TengerTechnologies/Bricklayers.git
				synced 2025-02-07 13:24:45 +00:00 
			
		
		
		
	Add files via upload
This commit is contained in:
		
							parent
							
								
									b1ae816011
								
							
						
					
					
						commit
						8bd84d5e04
					
				
							
								
								
									
										389
									
								
								bricklayersNonPlanarInfill.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										389
									
								
								bricklayersNonPlanarInfill.py
									
									
									
									
									
										Normal file
									
								
							| @ -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 <https://www.gnu.org/licenses/>. | ||||
| # | ||||
| # 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, | ||||
|     ) | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user