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