From 5ad717f132f4f6f7cec9b08e0d12e1d1a1986987 Mon Sep 17 00:00:00 2001 From: lasse Date: Tue, 1 Apr 2025 08:40:08 +0000 Subject: [PATCH] Upload files to "/" --- config.json | 69 ++++ visualization_data.js | 742 ++++++++++++++++++++++++++++++++++++++++ wall_calculator.py | 259 ++++++++++++++ wall_visualization.html | 490 ++++++++++++++++++++++++++ 4 files changed, 1560 insertions(+) create mode 100644 config.json create mode 100644 visualization_data.js create mode 100644 wall_calculator.py create mode 100644 wall_visualization.html diff --git a/config.json b/config.json new file mode 100644 index 0000000..e8d151e --- /dev/null +++ b/config.json @@ -0,0 +1,69 @@ +{ + "board": { + "name": "Naturheld 140", + "width": 1.25, + "height": 0.6 + }, + "walls": [ + { + "name": "AZ 1", + "shape": "rectangular", + "width": 2.55, + "height": 2.65, + "heightFirstRow": 0.3, + "windows": [] + }, + { + "name": "AZ 2", + "shape": "rectangular", + "width": 3.62, + "height": 2.5, + "heightFirstRow": 0.3, + "windows": [ + { + "left": 1.0, + "bottom": 0.72, + "width": 1.52, + "height": 1.57 + } + ] + }, + { + "name": "GZ 1", + "shape": "rectangular", + "width": 3.60, + "height": 2.5, + "heightFirstRow": 0.3, + "windows": [ + { + "left": 1.06, + "bottom": 0.75, + "width": 1.52, + "height": 1.57 + } + ] + }, + { + "name": "GZ 2", + "shape": "rectangular", + "width": 4.0, + "height": 2.65, + "heightFirstRow": 0.3, + "windows": [] + }, + { + "name": "KZ", + "shape": "triangular", + "width": 7.2, + "height": 4.22, + "windows": [ + { + "left": 2.83, + "bottom": 0.95, + "width": 1.6, + "height": 1.4 + } + ] + } + ] +} \ No newline at end of file diff --git a/visualization_data.js b/visualization_data.js new file mode 100644 index 0000000..27c52bb --- /dev/null +++ b/visualization_data.js @@ -0,0 +1,742 @@ +const wallsData = [ + { + "wall": { + "name": "AZ 1", + "width": 2.55, + "height": 2.65, + "shape": "rectangular", + "windows": [] + }, + "board": { + "width": 1.25, + "height": 0.6 + }, + "boards": [ + { + "x": 0, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 1.25, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 2.5, + "y": 0, + "width": 0.04999999999999982, + "height": 0.3, + "is_cut": true + }, + { + "x": 0, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0.3, + "width": 0.04999999999999982, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0.8999999999999999, + "width": 0.04999999999999982, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 1.5, + "width": 0.04999999999999982, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.5500000000000003, + "is_cut": true + }, + { + "x": 1.25, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.5500000000000003, + "is_cut": true + }, + { + "x": 2.5, + "y": 2.0999999999999996, + "width": 0.04999999999999982, + "height": 0.5500000000000003, + "is_cut": true + } + ] + }, + { + "wall": { + "name": "AZ 2", + "width": 3.62, + "height": 2.5, + "shape": "rectangular", + "windows": [ + { + "left": 1.0, + "bottom": 0.72, + "width": 1.52, + "height": 1.57 + } + ] + }, + "board": { + "width": 1.25, + "height": 0.6 + }, + "boards": [ + { + "x": 0, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 1.25, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 2.5, + "y": 0, + "width": 1.12, + "height": 0.3, + "is_cut": true + }, + { + "x": 0, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0.3, + "width": 1.12, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0.8999999999999999, + "width": 1.12, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 1.5, + "width": 1.12, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.40000000000000036, + "is_cut": true + }, + { + "x": 1.25, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.40000000000000036, + "is_cut": true + }, + { + "x": 2.5, + "y": 2.0999999999999996, + "width": 1.12, + "height": 0.40000000000000036, + "is_cut": true + } + ] + }, + { + "wall": { + "name": "GZ 1", + "width": 3.6, + "height": 2.5, + "shape": "rectangular", + "windows": [ + { + "left": 1.06, + "bottom": 0.75, + "width": 1.52, + "height": 1.57 + } + ] + }, + "board": { + "width": 1.25, + "height": 0.6 + }, + "boards": [ + { + "x": 0, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 1.25, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 2.5, + "y": 0, + "width": 1.1, + "height": 0.3, + "is_cut": true + }, + { + "x": 0, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0.3, + "width": 1.1, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0.8999999999999999, + "width": 1.1, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 1.5, + "width": 1.1, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.40000000000000036, + "is_cut": true + }, + { + "x": 1.25, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.40000000000000036, + "is_cut": true + }, + { + "x": 2.5, + "y": 2.0999999999999996, + "width": 1.1, + "height": 0.40000000000000036, + "is_cut": true + } + ] + }, + { + "wall": { + "name": "GZ 2", + "width": 4.0, + "height": 2.65, + "shape": "rectangular", + "windows": [] + }, + "board": { + "width": 1.25, + "height": 0.6 + }, + "boards": [ + { + "x": 0, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 1.25, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 2.5, + "y": 0, + "width": 1.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 3.75, + "y": 0, + "width": 0.25, + "height": 0.3, + "is_cut": true + }, + { + "x": 0, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0.3, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 3.75, + "y": 0.3, + "width": 0.25, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0.8999999999999999, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 3.75, + "y": 0.8999999999999999, + "width": 0.25, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 1.5, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 3.75, + "y": 1.5, + "width": 0.25, + "height": 0.6, + "is_cut": true + }, + { + "x": 0, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.5500000000000003, + "is_cut": true + }, + { + "x": 1.25, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.5500000000000003, + "is_cut": true + }, + { + "x": 2.5, + "y": 2.0999999999999996, + "width": 1.25, + "height": 0.5500000000000003, + "is_cut": true + }, + { + "x": 3.75, + "y": 2.0999999999999996, + "width": 0.25, + "height": 0.5500000000000003, + "is_cut": true + } + ] + }, + { + "wall": { + "name": "KZ", + "width": 7.2, + "height": 4.22, + "shape": "triangular", + "windows": [ + { + "left": 2.83, + "bottom": 0.95, + "width": 1.6, + "height": 1.4 + } + ] + }, + "board": { + "width": 1.25, + "height": 0.6 + }, + "boards": [ + { + "x": 0.0, + "y": 0, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.25, + "y": 0, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.5, + "y": 0, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 3.75, + "y": 0, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 5.0, + "y": 0, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 6.25, + "y": 0, + "width": 0.9500000000000002, + "height": 0.6, + "is_cut": true + }, + { + "x": 0.5118483412322274, + "y": 0.6, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 1.7618483412322274, + "y": 0.6, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 3.0118483412322274, + "y": 0.6, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 4.261848341232227, + "y": 0.6, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 5.511848341232227, + "y": 0.6, + "width": 1.1763033175355453, + "height": 0.6, + "is_cut": true + }, + { + "x": 1.0236966824644549, + "y": 1.2, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.273696682464455, + "y": 1.2, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 3.523696682464455, + "y": 1.2, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 4.773696682464455, + "y": 1.2, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 6.023696682464455, + "y": 1.2, + "width": 0.1526066350710895, + "height": 0.6, + "is_cut": true + }, + { + "x": 1.5355450236966823, + "y": 1.7999999999999998, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 2.7855450236966823, + "y": 1.7999999999999998, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 4.035545023696683, + "y": 1.7999999999999998, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 5.285545023696683, + "y": 1.7999999999999998, + "width": 0.3789099526066355, + "height": 0.6, + "is_cut": true + }, + { + "x": 2.0473933649289098, + "y": 2.4, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 3.2973933649289098, + "y": 2.4, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 4.54739336492891, + "y": 2.4, + "width": 0.6052132701421802, + "height": 0.6, + "is_cut": true + }, + { + "x": 2.5592417061611377, + "y": 3.0, + "width": 1.25, + "height": 0.6, + "is_cut": false + }, + { + "x": 3.8092417061611377, + "y": 3.0, + "width": 0.8315165876777253, + "height": 0.6, + "is_cut": true + }, + { + "x": 3.071090047393365, + "y": 3.6, + "width": 1.05781990521327, + "height": 0.6, + "is_cut": true + }, + { + "x": 3.582938388625592, + "y": 4.199999999999999, + "width": 0.034123222748816316, + "height": 0.020000000000000018, + "is_cut": true + } + ] + } +]; \ No newline at end of file diff --git a/wall_calculator.py b/wall_calculator.py new file mode 100644 index 0000000..ce33042 --- /dev/null +++ b/wall_calculator.py @@ -0,0 +1,259 @@ +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() \ No newline at end of file diff --git a/wall_visualization.html b/wall_visualization.html new file mode 100644 index 0000000..553873f --- /dev/null +++ b/wall_visualization.html @@ -0,0 +1,490 @@ + + + + + + Wall Board Coverage Visualization + + + +
+

Wall Board Coverage Visualization

+
+
+
+
+ Full Boards +
+
+
+ Cut Boards +
+
+
+
+
+ + + + + \ No newline at end of file