import json import math from dataclasses import dataclass from typing import List, Tuple, Dict import webbrowser import os @dataclass class Window: left: float # position from left edge bottom: float # position from bottom edge width: float height: float @dataclass class Dimensions: name: str width: float height: float shape: str = "rectangular" windows: List[Window] = None heightFirstRow: float = None def __post_init__(self): if self.windows is None: self.windows = [] @dataclass class Board: x: float # x position y: float # y position width: float height: float is_cut: bool @dataclass class BoardUsage: full_boards: int cut_boards: List[float] # List of cut board lengths in meters last_row_height: float # Height of the last row in meters waste: float # Total waste in square meters board_positions: List[Board] # List of all boards with their positions @dataclass class WallResult: wall: Dimensions usage: BoardUsage def load_config(filename: str) -> Tuple[List[Dimensions], Dimensions]: """Load walls and board dimensions from config file.""" with open(filename, 'r') as f: config = json.load(f) walls = [] for wall_config in config['walls']: windows = [] if 'windows' in wall_config: for w in wall_config['windows']: windows.append(Window( left=w['left'], bottom=w['bottom'], width=w['width'], height=w['height'] )) wall = Dimensions( name=wall_config['name'], width=wall_config['width'], height=wall_config['height'], shape=wall_config.get('shape', 'rectangular'), windows=windows, heightFirstRow=wall_config.get('heightFirstRow', None) ) walls.append(wall) board = Dimensions(**config['board']) return walls, board def get_row_width(y: float, wall: Dimensions) -> float: """Calculate the width of the wall at a given height.""" if wall.shape == "rectangular": return wall.width elif wall.shape == "triangular": # For equilateral triangle, calculate width at given height # At y=0 (bottom), width is wall.width # At y=wall.height (top), width is 0 return wall.width * (1 - y/wall.height) return 0 def get_row_offset(y: float, wall: Dimensions) -> float: """Calculate the x-offset for a row at given height in triangular walls.""" if wall.shape != "triangular": return 0 # For triangular shape, calculate offset based on similar triangles # At y=0 (bottom), offset is 0 # At y=wall.height (top), offset is wall.width/2 return (y / wall.height) * (wall.width / 2) def calculate_board_usage(wall: Dimensions, board: Dimensions) -> BoardUsage: """Calculate how many boards are needed to cover the wall.""" # Calculate the height of the first row if specified first_row_height = wall.heightFirstRow if wall.heightFirstRow is not None else board.height # Calculate remaining height after first row remaining_height = wall.height - first_row_height # Calculate number of full-height rows needed for remaining height full_rows = math.floor(remaining_height / board.height) # Calculate the height of the last row last_row_height = remaining_height - (full_rows * board.height) if last_row_height < 0.01: # If less than 1cm remaining, ignore it last_row_height = 0 full_rows = math.floor(remaining_height / board.height) rows = 1 + full_rows + (1 if last_row_height > 0 else 0) # First row + full rows + last row if needed full_boards = 0 cut_boards = [] waste = 0 board_positions = [] # Process each row for row in range(rows): # Calculate y position from bottom if row == 0: current_y = 0 current_row_height = first_row_height else: current_y = first_row_height + ((row - 1) * board.height) current_row_height = last_row_height if row == rows - 1 else board.height # Calculate the width of the wall at this height row_width = get_row_width(current_y, wall) next_row_width = get_row_width(current_y + current_row_height, wall) # For triangular shape, we need to handle trapezoidal sections if wall.shape == "triangular": # Use the wider base for board calculations to ensure coverage row_width = max(row_width, next_row_width) # Calculate x offset for triangular walls x_offset = get_row_offset(current_y, wall) current_x = x_offset remaining_width = row_width while remaining_width > 0: if remaining_width >= board.width: full_boards += 1 board_positions.append(Board( x=current_x, y=current_y, width=board.width, height=current_row_height, is_cut=current_row_height != board.height )) current_x += board.width remaining_width -= board.width # Calculate waste if board height is cut if current_row_height != board.height: waste += (board.width * (board.height - current_row_height)) else: if remaining_width > 0: cut_boards.append(remaining_width) board_positions.append(Board( x=current_x, y=current_y, width=remaining_width, height=current_row_height, is_cut=True )) # Calculate waste considering both width and height cuts width_waste = board.width - remaining_width height_waste = board.height - current_row_height if current_row_height != board.height else 0 waste += (width_waste * current_row_height) + (remaining_width * height_waste) remaining_width = 0 return BoardUsage(full_boards, cut_boards, last_row_height, waste, board_positions) def generate_visualization_data(wall_results: List[WallResult], board: Dimensions) -> List[Dict]: """Generate data for the visualization of multiple walls.""" return [{ "wall": { "name": result.wall.name, "width": result.wall.width, "height": result.wall.height, "shape": result.wall.shape, "windows": [ { "left": w.left, "bottom": w.bottom, "width": w.width, "height": w.height } for w in result.wall.windows ] }, "board": { "width": board.width, "height": board.height }, "boards": [ { "x": b.x, "y": b.y, "width": b.width, "height": b.height, "is_cut": b.is_cut } for b in result.usage.board_positions ] } for result in wall_results] def main(): # Load configuration walls, board = load_config('config.json') # Calculate board usage for each wall wall_results = [] total_full_boards = 0 total_cut_boards = 0 total_waste = 0 for wall in walls: usage = calculate_board_usage(wall, board) wall_results.append(WallResult(wall, usage)) total_full_boards += usage.full_boards total_cut_boards += len(usage.cut_boards) total_waste += usage.waste # Print results for each wall for result in wall_results: print(f"\n{result.wall.name}:") print(f"Wall dimensions: {result.wall.width}m x {result.wall.height}m") print(f"Wall shape: {result.wall.shape}") print(f"Full boards needed: {result.usage.full_boards}") print(f"Cut boards needed: {len(result.usage.cut_boards)}") print("Cut board lengths:", [f"{length:.2f}m" for length in result.usage.cut_boards]) print(f"Last row height: {result.usage.last_row_height:.2f}m") print(f"Waste: {result.usage.waste:.2f} square meters") # Print total summary print("\nTotal Summary:") print(f"Total full boards needed: {total_full_boards}") print(f"Total cut boards needed: {total_cut_boards}") print(f"Total waste: {total_waste:.2f} square meters") print(f"Total boards needed: {total_full_boards + total_cut_boards}") # Generate visualization data viz_data = generate_visualization_data(wall_results, board) with open('visualization_data.js', 'w') as f: f.write(f"const wallsData = {json.dumps(viz_data, indent=2)};") # Open visualization in browser webbrowser.open('file://' + os.path.abspath('wall_visualization.html')) if __name__ == "__main__": main()