├── .gitignore ├── LICENSE ├── README.md ├── drawing_settings_documentation.md ├── examples ├── Rocket_Ship_1.jpg ├── Rocket_Ship_1_Visualization.png ├── example_00.svg ├── example_01.svg ├── example_02.svg ├── example_03.svg ├── example_04.svg ├── example_05.svg ├── example_06.svg ├── example_07.svg ├── example_08.svg ├── example_09.svg ├── example_10.svg ├── example_11.svg ├── example_12.svg ├── example_13.svg ├── example_14.svg ├── example_15.svg ├── example_16.svg ├── example_17.svg ├── example_18.svg ├── example_19.svg ├── example_20.svg ├── example_21.svg ├── example_22.svg ├── example_23.svg ├── example_24.svg ├── example_25.svg ├── example_26.svg ├── example_27.svg ├── example_28.svg ├── example_29.svg ├── example_30.svg ├── example_31.svg ├── random_0077.svg ├── random_0104.svg ├── random_0115.svg ├── random_0117.svg └── random_0150.svg ├── index.html ├── scripts ├── download_factorio_raw_data.py ├── get_items.py ├── pyproject.toml └── uv.lock ├── test_blueprints ├── Belts.jpg ├── Heatpipes.jpg ├── Inserters.jpg ├── Pipes.jpg ├── Powerlines.jpg ├── Rails.jpg ├── Red and Green Wires.jpg ├── Space Ship.jpg ├── Tiles 1.jpg ├── Tiles 2.jpg └── test_blueprint_book.txt └── website ├── blueprintVisualizer.js ├── drawingSettings.js ├── entityProperties.js ├── favicon.ico ├── index.css └── startExampleBlueprintString.js /.gitignore: -------------------------------------------------------------------------------- 1 | factorio_data -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Piet Brömmel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Factorio Blueprint Visualizer 2 | 3 | I love the game Factorio and I really like the look of factories after growing for many hours or blueprints after tweaking them for perfection. That's why I created a [website](https://piebro.github.io/factorio-blueprint-visualizer/) to artfully visualize Factorio blueprints. 4 | 5 |

6 | 7 | 8 |

9 | 10 | With the website you can import Factorio blueprints as text and visualize them. You can tweak the drawing settings or create random ones. 11 | 12 | All buildings and tiles with their bounding boxes and belt, pipe, rail, inserter, wire and electricity connections can be visualized. Everything is drawn in vector graphics (SVG) to be able to view it in any resolution. 13 | 14 | With the latest update, Blueprints from Factorio before version 2.0 might not work correctly. You can import older blueprints to factorio and export them again to update them. 15 | 16 | ## Usage 17 | 18 | 1. Open Factorio and create a blueprint. 19 | 2. Export the blueprint as text (in the upper left corner, next to the "Delete" button). 20 | 3. Go to the [website](https://piebro.github.io/factorio-blueprint-visualizer/). 21 | 4. Click the "Upload Blueprint" button and paste the text into the text area. 22 | 5. Now you can test new random drawing settings (using the buttons or the arrow keys) or edit the current drawing settings. 23 | 24 | Documentation of the drawing settings can be found [here](drawing_settings_documentation.md). 25 | 26 | ## Examples 27 | 28 |

29 | 30 | 31 | 32 | 33 | 34 | 35 |

36 | 37 | The last three blueprints are by Josh Ventura and can be found [here](https://factorioprints.com/user/6QrnfqXIffQcWgHC6Xs4uHv1BGg2). 38 | 39 | ## Update [2025-01-08] 40 | 41 | - Factorio 2.0 and Factorio: Space Age are supported (Blueprints from earlier version might only work partially) 42 | - Ported everything from Python with Pyodide to Javascript for simplicity and performance. 43 | - Added ability to modify drawing settings. 44 | - Added support for tiles. 45 | - Lots of quality-of-life improvements. 46 | 47 | ## Ways to use this tool 48 | 49 | ### Text to Image 50 | 51 | I created a [dataset](https://huggingface.co/datasets/piebro/factorio-blueprint-visualizations) with images generated using this tool to finetune [SDXL](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0) (a text-to-image neural network). The model with examples can be found here: https://huggingface.co/piebro/factorio-blueprint-visualizations-sdxl-lora 52 | 53 | ### Pen Plotting 54 | 55 | I have a pen plotter, and one of my initial ideas was also to be able to plot my factories. You can create visualizations you can easily draw. I recommend using https://github.com/abey79/vpype for merging lines together before plotting. An example of a visualization for plotting is here: 56 | 57 |

58 | 59 |

60 | 61 | Another way to create plots from your factories is to use: https://github.com/drawscape-labs/factorio-cli. 62 | 63 | ## Factorio Tools to create blueprints 64 | 65 | - [Factorio SAT](https://github.com/R-O-C-K-E-T/Factorio-SAT) - Create optimal belt layouts with a SAT solver 66 | - [Factorio Verilog](https://github.com/redcrafter/verilog2factorio) - Convert Verilog code to factorio blueprints 67 | 68 | ## Development 69 | 70 | [uv](https://docs.astral.sh/uv/getting-started/installation/) is used for linting and formatting the python code with `uv run ruff check --fix` and `uv run ruff format`. 71 | 72 | ## Contribute 73 | 74 | Contributions to this project are welcome. Feel free to report bugs or post ideas. 75 | 76 | ## Statistics 77 | 78 | There is lightweight tracking with [Plausible](https://plausible.io/about) for the [website](https://piebro.github.io/factorio-blueprint-visualizer/) to get infos about how many people are visiting. Everyone who is interested can look at these stats here: https://plausible.io/piebro.github.io%2Ffactorio-blueprint-visualizer?period=all 79 | -------------------------------------------------------------------------------- /drawing_settings_documentation.md: -------------------------------------------------------------------------------- 1 | # Drawing Settings Documentation 2 | 3 | The drawing settings control how your Factorio blueprint is visualized. Settings are defined as an array of arrays, where each inner array represents a drawing instruction. The order of the settings defines the order in which they are drawn. 4 | 5 | ## Basic Format 6 | 7 | ```text 8 | [ 9 | ['setting-name-1', {svg-attributes and optional-parameters}], 10 | ['setting-name-2', {svg-attributes and optional-parameters}], 11 | ... 12 | ] 13 | ``` 14 | An example can be found in [drawingSettings.js](drawingSettings.js). 15 | 16 | ## Available Setting Names 17 | 18 | - `default settings`: Default settings for the blueprint if some settings are not specified. 19 | - `tiles`: Draws tiles. 20 | - `bbox`: Draws the bounding box of buildings. 21 | - `bbox-selection`: Draws the selection bounding box of buildings. 22 | - `bbox-collision`: Draws the collision bounding box of buildings. 23 | - `pipes`: Draws pipes. 24 | - `underground-pipes`: Draws underground pipes. 25 | - `belts`: Draws belts. 26 | - `underground-belts`: Draws underground belts. 27 | - `heat-pipes`: Draws heat pipes. 28 | - `power-lines`: Draws power lines. 29 | - `green-wire-lines`: Draws green wire lines. 30 | - `red-wire-lines`: Draws red wire lines. 31 | 32 | ## SVG Attributes 33 | 34 | - `fill`: Color for filling shapes 35 | - `fill-opacity`: Transparency of fill (0.0 to 1.0) 36 | - `stroke`: Color for lines/borders 37 | - `stroke-width`: Width of lines/borders 38 | - `stroke-linecap`: Style of line endings ('round', 'butt', 'square') 39 | - `stroke-opacity`: Transparency of lines (0.0 to 1.0) 40 | 41 | ## Optional Parameters 42 | 43 | These parameters can only be used in tiles and bbox settings. 44 | 45 | - `allow`: Array of strings, only draw entities that match any of these strings. 46 | - `deny`: Array of strings, do not draw entities that match any of these strings. 47 | - `scale`: Number, scale of the entity. 48 | - `rx`: Number, radius of the rounded corners for rectangles. 49 | - `ry`: Number, radius of the rounded corners for rectangles. 50 | 51 | `allow` and `deny` can be used with the name of the entity (e.g. `fast-inserter` or `assembling-machine-1`) or with the generic terms of the entity (e.g. `combat` or `inserter`). 52 | All generic terms can be found in [entityProperties.js](entityProperties.js). 53 | 54 | -------------------------------------------------------------------------------- /examples/Rocket_Ship_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/examples/Rocket_Ship_1.jpg -------------------------------------------------------------------------------- /examples/Rocket_Ship_1_Visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/examples/Rocket_Ship_1_Visualization.png -------------------------------------------------------------------------------- /examples/example_06.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#e3b5a4'}], ['svg', {'bbox-scale': 0.9, 'fill': 'none', 'stroke': '#773344', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['belts', {}], ['underground-belts', {'stroke-opacity': 0.6}], ['bbox', {'allow': ['producing-machines'], 'fill': '#e85f5c', 'stroke': 'none'}], ['bbox', {'allow': ['lab'], 'fill': '#9cfffa', 'stroke': 'none'}], ['bbox', {'allow': ['electricity'], 'fill': '#acf39d', 'stroke': 'none'}]]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 -------------------------------------------------------------------------------- /examples/example_07.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#98b06f'}], ['svg', {'bbox-scale': 0.85, 'stroke': '#442b48', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['pipes', {}], ['underground-pipes', {'stroke-opacity': 0.6}], ['bbox', {'deny': ['transportation', 'electricity'], 'fill': '#726e60'}]]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 -------------------------------------------------------------------------------- /examples/example_08.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#52414c'}], ['svg', {'bbox-scale': 0.9, 'stroke': '#5b8c5a', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['pipes', {}], ['underground-pipes', {'stroke-opacity': 0.6}], ['bbox', {'allow': ['producing-machines'], 'fill': '#e3655b', 'stroke': 'none'}], ['bbox', {'allow': ['beacon'], 'fill': '#d67ab1', 'stroke': 'none'}], ['bbox', {'allow': ['electricity'], 'fill': '#596157', 'stroke': 'none'}], ['bbox', {'allow': ['boiler'], 'fill': '#cfd186', 'stroke': 'none'}], ['bbox', {'allow': ['pump'], 'fill': '#cfd186', 'stroke': 'none'}]]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 -------------------------------------------------------------------------------- /examples/example_09.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#faf0ca'}], ['svg', {'fill': 'none', 'stroke': '#0d3b66', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['belts', {}], ['bbox', {'allow': ['transportation']}]]0eNqdnN1u2zoQhN9F127B5T/9KgdFkbRCYcBxDFs9OEGQdz92c+PGYjgzlwniL6MVyd2dlfw6Pe5/z8fT7rBM29dp9+P5cJ62/7xO592vw8P++rvl5ThP22m3zE/TZjo8PF1/Wk4Ph/Px+bR8eZz3y/S2mXaHn/N/09beNsMPn4/73bLMp5uP+bdvm2k+LLtlN78L+PPDy/fD76fHy19u7f7Tm+n4fL584Plw/U8XSKv2NW2ml2n7xft2gf/cneYf73+Qr7o+MH3vclbI7ob8NY3ZQWNXhB0JtifZiWAnMiZZY0O6C7Q+MrU+KqG3kLFoGhuKhTkkGM1uoWsYwzW28Pf1r+E8pCqOVBE7q5WxqgipKiNVxL5pbawqA6rMOTeSVWBZV9pYV8V0hZGuxuhKQ13eYbryQJc3Rtf4PnqP6WojXYHQZTbWFSFd1y37ua7E6PJjXZnhfZYj/Bq9YFfdzxKrVGxPeOOojS14MlCUOK0qyVDFY1p6x+Ceze9IOIKWhDHFkTlKbrZGWd8aIYlHZo/HbDVXx7wiHlE9XqU3ax5vq8AkHvvsnq/Ro6OPAkBzZJKS96xmr3UTCepUxB2GwaNWn8b1BReTVlj2cFmrCHs4tZLr8ap4nPR4TaycOrzkxOOkxzOxUunxvFipJGRTpsBm/wC09lHL/h7yDZJ2jGBw0TnA4LR1gMRa9A4CpFg0D6BwZKJGbPY3fA1nnBcROhjSPOhhGPMgji8uaimjh0ucF9G7SNY86HHUlNO7PCrlhDGviRV2h1ccaW504lZMTK09XZ40N3q6GBPB5bGuKKb8Hi+JHUmPl0nzpRc3tbPp6aqk+dLT1cSSpqOrOrGk8UhJU43u5wJA9WI/h2kOdD+HaI5iP4dpTmzp6IBJSGZrJARatDLGIWVM5e0DBwRXtQ8ccusabx8AmptqH2CaNfvAsCGZZh+A8Kgdd/f01bgkbaWA9KzdU5BeyCPEChDuKnWfBpmtrWnwgo1TndR+YtIvd1+jg9pZ4xy5l+bEjQlqVndmQVa3uSTiM4bPbOb5GPJ1bGGTA4ZlmjLv6FizYyyLwAIxbY5lEVp+Jm5IkE5vSCgi4oYENUfJE7qnx1U6Y7w7mp6lKQFKL5KhhNKr5C+hdCJptszSvZPmHyjdNG8KxXvNWkLxQXOIUHzUjBQUnzQ/BMWLz4Dc41czEv8QyMcDeB1bxXofVN3o7I+oDk7M/pjqILaeKN5TswUL0Ppjngq5TXYeo0ct2YH0RI1J0IiIKRTUXKipB6pZTJ2g5saNWEDRkZxAoFjTBi9gMKKaLEF84MYfaFCimINB1YkbPqCqszZzQVUXbjSBqq5iSg9QHoh0k2lA05PEJhOypy2JTSZIp5tMKCJik2mYZtX1MWiRJNX1cRied30MwRaxrARV088uY6qbWFZitzI7sazEgpLZd81cBZZ3ZrckRtW2pMPeI8qRKrDvqavZICepwHYNo2eqBEY1az4PqrlSJTCquUklMKgZfVrGcaKLaOygqj1XYaOqyRoVxUauiESxiavyUKxo3Djo5R0rtHHz8QRdx4rGDaqaNm4g1VU0bkDVVTRuULzXMkCB1mENksWC0rW3DlC69hICSs+aEYDii2YEoHjxlQUUL77BAOKb+HSdwwat1FM+Vmi8Fw+EFfy3zfv3QWxvvntiM+0fLrjr7y7Q7zdH5b/z6fz+wWqxNF9ibj5fDsm3/wGKPEex -------------------------------------------------------------------------------- /examples/example_13.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#2a9d8f'}], ['svg', {'bbox-scale': 0.85, 'stroke': '#e9c46a', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['belts', {}], ['underground-belts', {'stroke-opacity': 0.6}], ['bbox', {'allow': ['producing-machines'], 'fill': '#264653', 'stroke': 'none'}]]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 -------------------------------------------------------------------------------- /examples/example_15.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#52414c'}], ['svg', {'bbox-scale': 0.9, 'stroke': '#e3655b', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['belts', {}], ['underground-belts', {'stroke-opacity': 0.6}], ['pipes', {'stroke': '#596157'}], ['underground-pipes', {'stroke': '#596157', 'stroke-opacity': 0.6}], ['bbox', {'allow': ['producing-machines'], 'fill': '#5b8c5a', 'stroke': 'none'}], ['bbox', {'allow': ['beacon'], 'fill': '#cfd186', 'stroke': 'none'}], ['bbox', {'allow': ['electricity'], 'fill': '#d67ab1', 'stroke': 'none'}]]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 -------------------------------------------------------------------------------- /examples/example_16.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#006494'}], ['svg', {'bbox-scale': 0.9, 'stroke': '#13293d', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['belts', {}], ['underground-belts', {'stroke-opacity': 0.6}], ['bbox', {'allow': ['drills'], 'fill': '#5d675b', 'stroke': 'none'}], ['bbox', {'allow': ['producing-machines'], 'fill': '#f78e69', 'stroke': 'none'}], ['bbox', {'allow': ['furnaces'], 'fill': '#f7ef99', 'stroke': 'none'}], ['bbox', {'allow': ['electricity'], 'fill': '#f1bb87', 'stroke': 'none'}]]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 -------------------------------------------------------------------------------- /examples/example_17.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#52489c'}], ['svg', {'bbox-scale': 0.9, 'stroke': '#f45b69', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['belts', {}], ['underground-belts', {'stroke-opacity': 0.6}], ['bbox', {'allow': ['drills'], 'fill': '#4062bb', 'stroke': 'none'}], ['bbox', {'allow': ['producing-machines'], 'fill': '#ebebeb', 'stroke': 'none'}], ['bbox', {'allow': ['furnaces'], 'fill': '#59c3c3', 'stroke': 'none'}]]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 -------------------------------------------------------------------------------- /examples/example_18.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#ffba08'}], ['svg', {'bbox-scale': 0.9, 'fill': 'none', 'stroke': '#3f88c5', 'stroke-linecap': 'round', 'stroke-width': 0.25}], ['belts', {}], ['underground-belts', {'stroke-opacity': 0.6}], ['bbox', {'allow': ['producing-machines'], 'fill': '#1c3144', 'stroke': 'none'}], ['bbox', {'allow': ['lab'], 'fill': '#a2aebb', 'stroke': 'none'}], ['bbox', {'allow': ['electricity'], 'fill': '#d00000', 'stroke': 'none'}]]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 -------------------------------------------------------------------------------- /examples/example_19.svg: -------------------------------------------------------------------------------- 1 | [['meta', {'background': '#cfd186'}], ['svg', {'bbox-rx': 0, 'bbox-ry': 0, 'bbox-scale': 0.7, 'stroke': 'none', 'stroke-linecap': 'round', 'stroke-width': 0.12}], ['bbox', {'allow': ['decider-combinator'], 'fill': '#52414c'}], ['bbox', {'allow': ['arithmetic-combinator'], 'fill': '#596157'}], ['bbox', {'allow': ['constant-combinator', 'chests'], 'fill': '#e3655b'}], ['green-circuits', {'stroke': '#d67ab1'}], ['red-circuits', {'stroke': '#5b8c5a'}]]0eNrtWFtvmzAU/i9+nEiFbQgJUitN2vMe9zJViIDTWiI2MqZbVvHfZ5O0uQDGJu26SnmJZIdz8fm+c459nsGqqEkpKJMgfgZUkg2Ij/Y8UKQrUqi9jBdFKv+oHZpxVoH45zOo6ANLCy0otyVRH7XyHmDpRq9yktGciFnGNyvKUskFaJQ4y8lvEMPGG1WgDcmUyX4NqLn3AGGSSkp2/rSLbcLqzYoIZcKoyAMlr5QsZ9q+9si/CT2wBfEM3oTKTE4FyXb/B55WIQUvkhV5TJ+okldCa1pIIna2M17rGPqHA3pjx6OCs1n2SCoJmua+aW2wnclKC0H9I0iu9KuTPghC2PExaa59VlJUZDWVu7XS0+jInoUCOYYCfsJQILtQYMdQoH8cil+c58Q2GKcRwJ0IHGLVH4zg1WwlCSn2VrtBwKdBsHXojJ3oyAO9njv7G776mwoqHzdE0swM34vnkRV2B62J+junr0dcU1HJpAPdExWyVjuHMLZfzH7oOlURrSN5oZk6bxhi5Q0viUh3boCv378pYV7Lsu5Rb2SGPQ7LcxgGc6iLiLKChuANnPGbO+MX7PGDl+G3x8Ipwm+FlLdnz4EI0AG8TpBPc2hhwGDkw1Noh+Qiu7Iamdr+MKzYCta9SrucNEOhvCpT0XoVg1ugN8pt0tbpZC34JqFMAQzidVpUxBbv4/blgQPOfjOplc07+WoHVU9C9mK1mFxF8Udn4RdDDhqL8XkKYocUnJtTEELbHHQqr0vn6rqcXF3Dz9gdDeodwF0YmqMJntAZHn0dcqmR0Kn1XVAjh8oXtC+XUtTk3e8x51d8w0XGOWNN9xxovsYGtunfVTRAFOhGFPzRRPH/M6IsRuCPrBHznYjiW/PTt2QCmlYy8JUJ/VeiYUjQCGfwJUwY02ZgCrZkCnZjCro2l9NAY+uagJyYYN+zkCXSwbTuEF5rQu/NbRiRyNz3YXgJEeCINgNRQkuihNNKQngtCf33BAMkwSWDM3u9g1DPHafaL0VhMX2oDY1DbdPjbNqAP7QbbsDI+RWM3ma6MWke1XkGw9Mn8N3d+8+HjQMMI5HHHsFq+aRo0x4FLWAQLVGEgyAKUNA0fwH0XbaR -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Factorio Blueprint Visualizer 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 351 | 352 | 353 | 358 | 359 |

Factorio Blueprint Visualizer

360 | 361 |
362 | 363 | 367 | 369 |
370 | 371 |
372 | 373 |
374 | 378 | 379 | 380 | 381 | 382 | 386 |
387 | 388 |
389 |
390 | × 391 | 392 |
393 |
394 | 395 |
396 |
397 | × 398 | 399 |
400 |
401 | 402 | 403 | 404 | 405 | 406 | 407 | -------------------------------------------------------------------------------- /scripts/download_factorio_raw_data.py: -------------------------------------------------------------------------------- 1 | # /// script 2 | # dependencies = [ 3 | # "requests", 4 | # "luadata", 5 | # ] 6 | # /// 7 | 8 | import json 9 | import os 10 | import re 11 | 12 | import luadata 13 | import requests 14 | 15 | 16 | def download_and_parse_data(): 17 | # Download the data 18 | url = "https://gist.githubusercontent.com/Bilka2/6b8a6a9e4a4ec779573ad703d03c1ae7/raw" 19 | response = requests.get(url) 20 | if response.status_code != 200: 21 | raise Exception(f"Failed to download data: {response.status_code}") 22 | 23 | # Get the content 24 | content = response.text 25 | 26 | # Clean up the content to make it valid Lua 27 | # Remove the script header if present 28 | content = re.sub(r"^Script.*?: ", "", content) 29 | 30 | # Remove Lua multi-line comments 31 | content = re.sub(r"--\[=\[.*?\]=\]", "", content, flags=re.DOTALL) 32 | 33 | # Convert scientific notation to decimal notation 34 | def convert_scientific(match): 35 | num = float(match.group(0)) 36 | return f"{num:.10f}".rstrip("0").rstrip(".") 37 | 38 | content = re.sub(r"-?\d+\.?\d*[eE][+-]?\d+", convert_scientific, content) 39 | 40 | # Remove any trailing commas in arrays/tables 41 | content = re.sub(r",(\s*[}\]])", r"\1", content) 42 | 43 | # Ensure proper line endings 44 | content = content.replace("\r\n", "\n") 45 | 46 | try: 47 | a = luadata.unserialize(content, encoding="utf-8", multival=False) 48 | return a 49 | except Exception as e: 50 | print(f"Error position in content: {e}") 51 | # Save problematic content to a file for inspection 52 | with open("debug_lua_content.txt", "w") as f: 53 | f.write(content) 54 | raise 55 | 56 | 57 | def remove_layers_recursively(data): 58 | if isinstance(data, dict): 59 | # Remove 'layers' and 'sprites' keys if present 60 | data.pop("layers", None) 61 | data.pop("sprites", None) 62 | # Recursively process all values 63 | for value in data.values(): 64 | remove_layers_recursively(value) 65 | elif isinstance(data, list): 66 | # Recursively process all items in list 67 | for item in data: 68 | remove_layers_recursively(item) 69 | return data 70 | 71 | 72 | if __name__ == "__main__": 73 | data = download_and_parse_data() 74 | data.pop("achievement", None) 75 | data.pop("technology", None) 76 | data.pop("tips-and-tricks-item", None) 77 | data.pop("rail-planner", None) 78 | 79 | recipe_data = {"recipe": data.pop("recipe", {}), "recipe-category": data.pop("recipe-category", {})} 80 | 81 | item_data = { 82 | "item": data.pop("item", {}), 83 | "item-group": data.pop("item-group", {}), 84 | "item-subgroup": data.pop("item-subgroup", {}), 85 | } 86 | 87 | filtered_keys = [ 88 | "accumulator", 89 | "agricultural-tower", 90 | "arithmetic-combinator", 91 | "assembling-machine", 92 | "asteroid-collector", 93 | "beacon", 94 | "boiler", 95 | "burner-generator", 96 | "cargo-bay", 97 | "cargo-landing-pad", 98 | "constant-combinator", 99 | "container", 100 | "curved-rail-a", 101 | "curved-rail-b", 102 | "decider-combinator", 103 | "electric-pole", 104 | "electric-turret", 105 | "elevated-curved-rail-a", 106 | "elevated-curved-rail-b", 107 | "elevated-half-diagonal-rail", 108 | "elevated-straight-rail", 109 | "fluid-turret", 110 | "furnace", 111 | "fusion-generator", 112 | "fusion-reactor", 113 | "gate", 114 | "generator", 115 | "half-diagonal-rail", 116 | "heat-interface", 117 | "heat-pipe", 118 | "highlight-box", 119 | "inserter", 120 | "lab", 121 | "lamp", 122 | "lightning-attractor", 123 | "logistic-container", 124 | "mining-drill", 125 | "offshore-pump", 126 | "pipe", 127 | "pipe-to-ground", 128 | "power-switch", 129 | "programmable-speaker", 130 | "pump", 131 | "radar", 132 | "rail-chain-signal", 133 | "rail-ramp", 134 | "rail-remnants", 135 | "rail-signal", 136 | "rail-support", 137 | "reactor", 138 | "roboport", 139 | "rocket-silo", 140 | "selector-combinator", 141 | "solar-panel", 142 | "splitter", 143 | "storage-tank", 144 | "straight-rail", 145 | "thruster", 146 | "train-stop", 147 | "transport-belt", 148 | "underground-belt", 149 | "wall", 150 | ] 151 | filtered_data = {key: data[key] for key in filtered_keys} 152 | 153 | filtered_data = data 154 | 155 | filtered_data = remove_layers_recursively(filtered_data) 156 | 157 | # Create factorio_data directory if it doesn't exist 158 | os.makedirs("factorio_data", exist_ok=True) 159 | 160 | # Extract recipe-related data 161 | 162 | print(data.keys()) 163 | 164 | # Save recipe data to a separate file 165 | with open("factorio_data/recipe_data.json", "w", encoding="utf-8") as f: 166 | json.dump(recipe_data, f, indent=2, ensure_ascii=False) 167 | print("Recipe data saved to factorio_data/recipe_data.json") 168 | 169 | # Save item data to a separate file 170 | with open("factorio_data/item_data.json", "w", encoding="utf-8") as f: 171 | json.dump(item_data, f, indent=2, ensure_ascii=False) 172 | print("Item data saved to factorio_data/item_data.json") 173 | 174 | # Save remaining data 175 | with open("factorio_data/factorio_data.json", "w", encoding="utf-8") as f: 176 | json.dump(filtered_data, f, indent=2, ensure_ascii=False) 177 | print("Main data saved to factorio_data/factorio_data.json") 178 | -------------------------------------------------------------------------------- /scripts/get_items.py: -------------------------------------------------------------------------------- 1 | import json 2 | from math import ceil 3 | 4 | DIRECTION_4_TO_OFFSET = [[0, -1], [1, 0], [0, 1], [-1, 0]] 5 | 6 | CUSTOM_GENERIC_TERMS = { 7 | "labs": ["lab", "biolab"], 8 | "pipes": ["pipe", "pipe-to-ground"], 9 | "power-distribution": [ 10 | "big-electric-pole", 11 | "medium-electric-pole", 12 | "small-electric-pole", 13 | "substation", 14 | "power-switch", 15 | ], 16 | "power-generation": [ 17 | "boiler", 18 | "steam-engine", 19 | "solar-panel", 20 | "nuclear-reactor", 21 | "fusion-generator", 22 | "fusion-reactor", 23 | "steam-turbine", 24 | ], 25 | "robotic-logistics": [ 26 | "roboport", 27 | "active-provider-chest", 28 | "buffer-chest", 29 | "passive-provider-chest", 30 | "requester-chest", 31 | "storage-chest", 32 | ], 33 | 34 | } 35 | 36 | CUSTOM_RENAME_GENERIC_TERMS = { 37 | "inserter": "inserters", # because inserter is also already an item name 38 | "belt": "belts", 39 | "train-transport": "rails" 40 | } 41 | 42 | 43 | def load_json_file(filename): 44 | """Load and return JSON data from a file.""" 45 | with open(filename) as file: 46 | return json.load(file) 47 | 48 | 49 | def get_subgroup_mappings(item_json): 50 | """Create mapping of subgroups to their parent groups.""" 51 | subgroup_to_group = {} 52 | valid_groups = {"combat", "logistics", "production", "space"} 53 | 54 | for subgroup in item_json["item-subgroup"].values(): 55 | subgroup_name = subgroup["name"] 56 | group_name = subgroup["group"] 57 | if group_name in valid_groups: 58 | subgroup_to_group[subgroup_name] = group_name 59 | 60 | return subgroup_to_group 61 | 62 | 63 | def get_base_items(item_json, subgroup_to_group): 64 | """Get base items and their properties.""" 65 | items = {} 66 | for item in item_json["item"].values(): 67 | if "subgroup" not in item: 68 | continue 69 | 70 | subgroup_name = item["subgroup"] 71 | item_name = item["name"] 72 | 73 | if subgroup_name in subgroup_to_group: 74 | items[item_name] = { 75 | "group": subgroup_to_group[subgroup_name], 76 | "subgroup": subgroup_name, 77 | } 78 | 79 | return items 80 | 81 | 82 | def add_special_items(items): 83 | """Add special items that can't be built directly.""" 84 | special_items = [ 85 | "straight-rail", 86 | "half-diagonal-rail", 87 | "curved-rail-a", 88 | "curved-rail-b", 89 | "rail-ramp", 90 | "rail-support", 91 | "elevated-straight-rail", 92 | "elevated-half-diagonal-rail", 93 | "elevated-curved-rail-a", 94 | "elevated-curved-rail-b", 95 | ] 96 | 97 | for item in special_items: 98 | items[item] = { 99 | "group": "logistics", 100 | "subgroup": "train-transport", 101 | } 102 | return items 103 | 104 | 105 | def process_pipe_connections(data, key2): 106 | """Process pipe connections from fluid boxes.""" 107 | pipe_connections = [] 108 | for key3 in data.keys(): 109 | if key3 == "fluid_boxes": 110 | for fluid_box in data[key3]: 111 | pipe_connections.extend(fluid_box["pipe_connections"]) 112 | elif key3.endswith("fluid_box"): 113 | pipe_connections.extend(data[key3]["pipe_connections"]) 114 | 115 | return process_connections(pipe_connections, key2) 116 | 117 | 118 | def process_connections(connections, item_name): 119 | """Process connection positions and return target positions.""" 120 | target_positions = [] 121 | for connection in connections: 122 | if item_name == "pumpjack": # Special case handling 123 | connection["position"] = connection["positions"][0] 124 | 125 | offset = DIRECTION_4_TO_OFFSET[connection["direction"] // 4] 126 | pos = connection["position"] 127 | target_position = [pos[0] + offset[0], pos[1] + offset[1]] 128 | target_positions.append([pos, target_position]) 129 | return target_positions 130 | 131 | 132 | def process_heat_connections(data, key2): 133 | """Process heat connections from energy source and heat buffer.""" 134 | heat_connections = [] 135 | for heat_source in ["energy_source", "heat_buffer"]: 136 | if heat_source in data and isinstance(data[heat_source], dict) and "connections" in data[heat_source]: 137 | heat_connections.extend(data[heat_source]["connections"]) 138 | 139 | return process_connections(heat_connections, key2) 140 | 141 | 142 | def get_fluid_recipes(recipe_json): 143 | """Get recipes that involve fluids.""" 144 | fluid_recipes = [] 145 | for recipe in recipe_json["recipe"].values(): 146 | if has_fluid_component(recipe): 147 | fluid_recipes.append(recipe["name"]) 148 | return fluid_recipes 149 | 150 | 151 | def has_fluid_component(recipe): 152 | """Check if a recipe has fluid ingredients or results.""" 153 | # Check ingredients 154 | if "ingredients" in recipe: 155 | for ingredient in recipe["ingredients"]: 156 | if isinstance(ingredient, dict) and ingredient.get("type") == "fluid": 157 | return True 158 | 159 | # Check results 160 | if "results" in recipe: 161 | for result in recipe["results"]: 162 | if isinstance(result, dict) and result.get("type") == "fluid": 163 | return True 164 | return False 165 | 166 | 167 | def get_tile_layers(factorio_json): 168 | """Extract tile names and their layers from factorio data.""" 169 | tiles = [] 170 | if "tile" in factorio_json: 171 | for tile_name, tile_data in factorio_json["tile"].items(): 172 | if tile_name == "landfill": 173 | tiles.append((tile_name, -1)) 174 | elif "minable" in tile_data: 175 | tiles.append((tile_name, tile_data["layer"])) 176 | 177 | # Sort by layer number ascending 178 | tiles.sort(key=lambda x: x[1]) 179 | return [name for name, _ in tiles] 180 | 181 | def generate_js_output(sorted_items, pipe_positions, heat_positions, fluid_recipes, tile_layers): 182 | """Generate JavaScript output strings.""" 183 | js_output = ["// This file is generated by get_items.py\n"] 184 | js_output.append("const entityNameToProperties = {") 185 | current_group = None 186 | current_subgroup = None 187 | 188 | # Add sorted items 189 | for item_name, item_data in sorted_items.items(): 190 | if item_data["group"] != current_group: 191 | js_output.append(f"\n // Group: {item_data['group'].capitalize()}") 192 | current_group = item_data["group"] 193 | if item_data["subgroup"] != current_subgroup: 194 | js_output.append(f" // Subgroup: {item_data['subgroup']}") 195 | current_subgroup = item_data["subgroup"] 196 | 197 | js_output.append(format_item_entry(item_name, item_data)) 198 | 199 | js_output.append("}") 200 | 201 | # Add generic terms 202 | generic_terms = collect_generic_terms(sorted_items) 203 | 204 | # Rename some generic terms 205 | renamed_terms = {} 206 | for key, terms in generic_terms.items(): 207 | new_key = CUSTOM_RENAME_GENERIC_TERMS.get(key, key) 208 | renamed_terms[new_key] = sorted(list(terms)) 209 | generic_terms = renamed_terms 210 | 211 | generic_terms.update(CUSTOM_GENERIC_TERMS) 212 | js_output.extend( 213 | [ 214 | "\nconst buildingGenericTerms = {", 215 | *[f' "{group}": {json.dumps(terms)},' for group, terms in sorted(generic_terms.items())], 216 | "}", 217 | ] 218 | ) 219 | 220 | # Add pipe target positions 221 | js_output.extend( 222 | [ 223 | "\nconst itemToPipeTargetPositions = {", 224 | *[f' "{item}": {pos!s},' for item, pos in sorted(pipe_positions.items())], 225 | "}", 226 | ] 227 | ) 228 | 229 | # Add heat target positions 230 | js_output.extend( 231 | [ 232 | "\nconst itemToHeatTargetPositions = {", 233 | *[f' "{item}": {pos!s},' for item, pos in sorted(heat_positions.items())], 234 | "}", 235 | ] 236 | ) 237 | 238 | # Add fluid recipes 239 | js_output.extend( 240 | ["\nconst fluidRecipes = {", *[f' "{recipe}": true,' for recipe in sorted(fluid_recipes)], "}"] 241 | ) 242 | 243 | # Add tile layers 244 | js_output.extend( 245 | ["\nconst artificialTilesSortedByLayer = [", *[f' "{tile}",' for tile in tile_layers], "]"] 246 | ) 247 | 248 | return js_output 249 | 250 | 251 | def format_item_entry(item_name, item_data): 252 | """Format a single item entry for JavaScript output.""" 253 | item_data["genericTerms"] = [item_data["group"], item_data["subgroup"]] 254 | 255 | props = { 256 | "genericTerms": item_data["genericTerms"], 257 | "size": [ 258 | ceil(item_data["collision_box"][1][0] - item_data["collision_box"][0][0]), 259 | ceil(item_data["collision_box"][1][1] - item_data["collision_box"][0][1]), 260 | ], 261 | "selection_size": [ 262 | item_data["selection_box"][1][0] - item_data["selection_box"][0][0], 263 | item_data["selection_box"][1][1] - item_data["selection_box"][0][1], 264 | ], 265 | "collision_size": [ 266 | item_data["collision_box"][1][0] - item_data["collision_box"][0][0], 267 | item_data["collision_box"][1][1] - item_data["collision_box"][0][1], 268 | ], 269 | } 270 | if item_name in [ 271 | "curved-rail-a", 272 | "curved-rail-b", 273 | "elevated-curved-rail-a", 274 | "elevated-curved-rail-b", 275 | "elevated-half-diagonal-rail", 276 | "elevated-straight-rail", 277 | "half-diagonal-rail", 278 | "straight-rail", 279 | ]: 280 | props["size"] = [1, 1] 281 | props["selection_size"] = [1, 1] 282 | props["collision_size"] = [1, 1] 283 | 284 | if "pipe_connection_target_positions" in item_data: 285 | props["pipeConnectionTargetPositions"] = item_data["pipe_connection_target_positions"] 286 | 287 | return f' "{item_name}": {props!s},' 288 | 289 | 290 | def collect_generic_terms(sorted_items): 291 | """Collect all generic terms grouped by their group and subgroups.""" 292 | generic_terms = {} 293 | 294 | # Initialize mappings 295 | for item_name, item_data in sorted_items.items(): 296 | group = item_data["group"] 297 | subgroup = item_data["subgroup"] 298 | 299 | # Initialize group-to-items mapping 300 | if group not in generic_terms: 301 | generic_terms[group] = set() 302 | generic_terms[group].add(item_name) 303 | 304 | # Initialize subgroup-to-items mapping 305 | if subgroup not in generic_terms: 306 | generic_terms[subgroup] = set() 307 | generic_terms[subgroup].add(item_name) 308 | 309 | # Convert sets to sorted lists 310 | return {key: sorted(list(terms)) for key, terms in generic_terms.items()} 311 | 312 | 313 | def main(): 314 | # Load JSON data 315 | item_json = load_json_file("factorio_data/item_data.json") 316 | factorio_json = load_json_file("factorio_data/factorio_data.json") 317 | recipe_json = load_json_file("factorio_data/recipe_data.json") 318 | 319 | # Process data 320 | subgroup_to_group = get_subgroup_mappings(item_json) 321 | items = get_base_items(item_json, subgroup_to_group) 322 | items = add_special_items(items) 323 | 324 | # Process connections 325 | item_to_pipe_target_positions = {} 326 | item_to_heat_target_positions = {} 327 | 328 | for key1, data1 in factorio_json.items(): 329 | if isinstance(data1, dict): 330 | for key2, data2 in data1.items(): 331 | if key2 not in items: 332 | continue 333 | if "collision_box" not in data2 or "selection_box" not in data2: 334 | continue 335 | 336 | items[key2].update( 337 | {"collision_box": data2["collision_box"], "selection_box": data2["selection_box"]} 338 | ) 339 | 340 | pipe_targets = process_pipe_connections(data2, key2) 341 | if pipe_targets: 342 | item_to_pipe_target_positions[key2] = pipe_targets 343 | 344 | heat_targets = process_heat_connections(data2, key2) 345 | if heat_targets: 346 | item_to_heat_target_positions[key2] = heat_targets 347 | 348 | # Remove items without collision box and sort 349 | items = {name: data for name, data in items.items() if "collision_box" in data} 350 | sorted_items = dict(sorted(items.items(), key=lambda x: (x[1]["group"], x[1]["subgroup"], x[0]))) 351 | 352 | # Generate output 353 | fluid_recipes = get_fluid_recipes(recipe_json) 354 | tile_layers = get_tile_layers(factorio_json) 355 | js_output = generate_js_output( 356 | sorted_items, item_to_pipe_target_positions, item_to_heat_target_positions, fluid_recipes, tile_layers 357 | ) 358 | 359 | # Write output 360 | with open("entityProperties.js", "w") as file: 361 | file.write("\n".join(js_output)) 362 | 363 | 364 | if __name__ == "__main__": 365 | main() 366 | -------------------------------------------------------------------------------- /scripts/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "factorio-blueprint-visualizer" 3 | version = "1.2" 4 | description = "A project to generate visualizations for Factorio blueprints" 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "ruff", 9 | ] 10 | 11 | license = { file = "LICENSE.md" } 12 | 13 | [tool.ruff] 14 | line-length = 110 15 | 16 | [tool.ruff.lint] 17 | select = [ 18 | "F", # Pyflakes 19 | "UP", # pyupgrade 20 | "B018", # Found useless expression. 21 | "I", # isort 22 | "RUF", # ruff 23 | ] 24 | ignore = [ 25 | "E501", # line too long - will be fixed in format 26 | ] 27 | 28 | [tool.ruff.format] 29 | quote-style = "double" 30 | indent-style = "space" -------------------------------------------------------------------------------- /scripts/uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.10" 3 | 4 | [[package]] 5 | name = "factorio-blueprint-visualizer" 6 | version = "1.2" 7 | source = { virtual = "." } 8 | dependencies = [ 9 | { name = "ruff" }, 10 | ] 11 | 12 | [package.metadata] 13 | requires-dist = [{ name = "ruff" }] 14 | 15 | [[package]] 16 | name = "ruff" 17 | version = "0.8.6" 18 | source = { registry = "https://pypi.org/simple" } 19 | sdist = { url = "https://files.pythonhosted.org/packages/da/00/089db7890ea3be5709e3ece6e46408d6f1e876026ec3fd081ee585fef209/ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5", size = 3473116 } 20 | wheels = [ 21 | { url = "https://files.pythonhosted.org/packages/d7/28/aa07903694637c2fa394a9f4fe93cf861ad8b09f1282fa650ef07ff9fe97/ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3", size = 10628735 }, 22 | { url = "https://files.pythonhosted.org/packages/2b/43/827bb1448f1fcb0fb42e9c6edf8fb067ca8244923bf0ddf12b7bf949065c/ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1", size = 10386758 }, 23 | { url = "https://files.pythonhosted.org/packages/df/93/fc852a81c3cd315b14676db3b8327d2bb2d7508649ad60bfdb966d60738d/ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807", size = 10007808 }, 24 | { url = "https://files.pythonhosted.org/packages/94/e9/e0ed4af1794335fb280c4fac180f2bf40f6a3b859cae93a5a3ada27325ae/ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25", size = 10861031 }, 25 | { url = "https://files.pythonhosted.org/packages/82/68/da0db02f5ecb2ce912c2bef2aa9fcb8915c31e9bc363969cfaaddbc4c1c2/ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d", size = 10388246 }, 26 | { url = "https://files.pythonhosted.org/packages/ac/1d/b85383db181639019b50eb277c2ee48f9f5168f4f7c287376f2b6e2a6dc2/ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75", size = 11424693 }, 27 | { url = "https://files.pythonhosted.org/packages/ac/b7/30bc78a37648d31bfc7ba7105b108cb9091cd925f249aa533038ebc5a96f/ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315", size = 12141921 }, 28 | { url = "https://files.pythonhosted.org/packages/60/b3/ee0a14cf6a1fbd6965b601c88d5625d250b97caf0534181e151504498f86/ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188", size = 11692419 }, 29 | { url = "https://files.pythonhosted.org/packages/ef/d6/c597062b2931ba3e3861e80bd2b147ca12b3370afc3889af46f29209037f/ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf", size = 12981648 }, 30 | { url = "https://files.pythonhosted.org/packages/68/84/21f578c2a4144917985f1f4011171aeff94ab18dfa5303ac632da2f9af36/ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117", size = 11251801 }, 31 | { url = "https://files.pythonhosted.org/packages/6c/aa/1ac02537c8edeb13e0955b5db86b5c050a1dcba54f6d49ab567decaa59c1/ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe", size = 10849857 }, 32 | { url = "https://files.pythonhosted.org/packages/eb/00/020cb222252d833956cb3b07e0e40c9d4b984fbb2dc3923075c8f944497d/ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d", size = 10470852 }, 33 | { url = "https://files.pythonhosted.org/packages/00/56/e6d6578202a0141cd52299fe5acb38b2d873565f4670c7a5373b637cf58d/ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a", size = 10972997 }, 34 | { url = "https://files.pythonhosted.org/packages/be/31/dd0db1f4796bda30dea7592f106f3a67a8f00bcd3a50df889fbac58e2786/ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76", size = 11317760 }, 35 | { url = "https://files.pythonhosted.org/packages/d4/70/cfcb693dc294e034c6fed837fa2ec98b27cc97a26db5d049345364f504bf/ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764", size = 8799729 }, 36 | { url = "https://files.pythonhosted.org/packages/60/22/ae6bcaa0edc83af42751bd193138bfb7598b2990939d3e40494d6c00698c/ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905", size = 9673857 }, 37 | { url = "https://files.pythonhosted.org/packages/91/f8/3765e053acd07baa055c96b2065c7fab91f911b3c076dfea71006666f5b0/ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162", size = 9149556 }, 38 | ] 39 | -------------------------------------------------------------------------------- /test_blueprints/Belts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Belts.jpg -------------------------------------------------------------------------------- /test_blueprints/Heatpipes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Heatpipes.jpg -------------------------------------------------------------------------------- /test_blueprints/Inserters.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Inserters.jpg -------------------------------------------------------------------------------- /test_blueprints/Pipes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Pipes.jpg -------------------------------------------------------------------------------- /test_blueprints/Powerlines.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Powerlines.jpg -------------------------------------------------------------------------------- /test_blueprints/Rails.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Rails.jpg -------------------------------------------------------------------------------- /test_blueprints/Red and Green Wires.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Red and Green Wires.jpg -------------------------------------------------------------------------------- /test_blueprints/Space Ship.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Space Ship.jpg -------------------------------------------------------------------------------- /test_blueprints/Tiles 1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Tiles 1.jpg -------------------------------------------------------------------------------- /test_blueprints/Tiles 2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/test_blueprints/Tiles 2.jpg -------------------------------------------------------------------------------- /website/drawingSettings.js: -------------------------------------------------------------------------------- 1 | const EXAMPLE_SETTINGS = [ 2 | ["how to use: https://github.com/piebro/factorio-blueprint-visualizer/blob/master/draw_setting_documentation.md"], 3 | ["default settings", {'background': '#a2aebb', 'fill': 'none', 'fill-opacity': 1, 'stroke': 'none', 'stroke-linecap': 'round', 'stroke-width': 0.3, 'stroke-opacity': 1, 'scale': 0.85, 'rx': 0.1, 'ry': 0.1}], 4 | ["tiles", {'fill': '#420217', 'stroke': '#f3ffbd', 'stroke-width': 0.15, 'deny': [], 'scale': 0.65}], 5 | 6 | ["pipes", {'stroke': '#c84c09'}], 7 | ["underground-pipes", {'stroke': '#c84c09'}], 8 | ["belts", {'stroke': '#f3ffbd'}], 9 | ["underground-belts", {'stroke': '#f3ffbd'}], 10 | ["inserters", {'stroke': '#f3ffbd'}], 11 | 12 | ["bbox", {'fill': '#247ba0', 'deny': ["pipe", "pipe-to-ground", "belts", "inserters", "solar-panel", "accumulator", "asteroid-collector", "cargo-bay", "space-platform-hub", "thruster", "rails"]}], 13 | ["bbox", {'fill': '#ff1654', 'allow': ["solar-panel"]}], 14 | ["bbox", {'fill': '#436436', 'allow': ["accumulator"]}], 15 | ["bbox", {'fill': '#70c1b3', 'allow': ["cargo-bay"]}], 16 | ["bbox", {'fill': '#b2dbbf', 'allow': ["asteroid-collector", "thruster"]}], 17 | ["bbox", {'fill': '#b2dbbf', 'allow': ["space-platform-hub"], "scale": 0.95}], 18 | 19 | ["rails", {'stroke': '#b2dbbf'}], 20 | ["heat-pipes", {'stroke': '#b2dbbf'}], 21 | 22 | ] 23 | 24 | const PREDEFINED_COLOR_PALETTES = [ 25 | [ 26 | ['#2d7dd2', '#97cc04'], 27 | ['#6d213c', '#946846'], 28 | ['#54457f', '#ac7b84'], 29 | ['#0d3b66', '#faf0ca'], 30 | ['#1446a0', '#db3069'], 31 | ['#ff6666', '#ccff66'], 32 | ['#251605', '#c57b57'], 33 | ["#353535", "#ffffff"], 34 | ["#34344a", "#80475e"], 35 | ["#e9d758", "#297373"], 36 | ["#c2c1c2", "#42213d"], 37 | ["#333745", "#e63462"], 38 | ["#0d3b66", "#faf0ca"], 39 | ["#f55d3e", "#878e88"], 40 | ["#fe4a49", "#2ab7ca"], 41 | ["#444545", "#b5ffe9"], 42 | ["#89b6a5", "#4c3b4d"], 43 | ["#f05d5e", "#0f7173"], 44 | ["#50514f", "#b4adea"], 45 | ],[ 46 | ['#ff4e00', '#8ea604', '#f5bb00'], 47 | ['#a54657', '#582630', '#f7ee7f'], 48 | ['#464d77', '#36827f', '#f9db6d'], 49 | ['#264653', '#2a9d8f', '#e9c46a'], 50 | ['#ef767a', '#456990', '#49beaa'], 51 | ['#eca400', '#eaf8bf', '#006992'], 52 | ['#ef946c', '#c4a77d', '#70877f'], 53 | ["#313715", "#d16014", "#939f5c"], 54 | ["#442b48", "#726e60", "#98b06f"], 55 | ["#002a32", "#c4a29e", "#eba6a9"], 56 | ["#51a3a3", "#75485e", "#cb904d"], 57 | ["#0d3b66", "#faf0ca", "#f4d35e"], 58 | ["#f2c57c", "#ddae7e", "#7fb685"], 59 | ["#28536b", "#c2948a", "#7ea8be"], 60 | ["#3a2e39", "#1e555c", "#f4d8cd"], 61 | ["#ffa69e", "#ff7e6b", "#8c5e58"], 62 | ["#f4d06f", "#ff8811", "#9dd9d2"], 63 | ["#fbfef9", "#191923", "#0e79b2"], 64 | ["#bf4e30", "#c6ccb2", "#093824"], 65 | ],[ 66 | ['#ec0b43', '#58355e', '#7ae7c7', '#d6ffb7'], 67 | ['#bfae48', '#5fad41', '#2d936c', '#391463'], 68 | ['#c9cba3', '#ffe1a8', '#e26d5c', '#723d46'], 69 | ['#d7263d', '#02182b', '#0197f6', '#448fa3'], 70 | ['#042a2b', '#5eb1bf', '#cdedf6', '#ef7b45'], 71 | ['#540d6e', '#ee4266', '#ffd23f', '#f3fcf0'], 72 | ['#ffb997', '#f67e7d', '#843b62', '#0b032d'], 73 | ["#06070e", "#29524a", "#94a187", "#c5afa0"], 74 | ["#f1bf98", "#e1f4cb", "#bacba9", "#717568"], 75 | ["#ccd7c5", "#efd2cb", "#c7a27c", "#d65780"], 76 | ["#ecebe4", "#cc998d", "#16f4d0", "#429ea6"], 77 | ["#004777", "#a30000", "#ff7700", "#efd28d"], 78 | ["#aba9bf", "#beb7df", "#d4f2d2", "#34113f"], 79 | ],[ 80 | ['#d6ffb7', '#f5ff90', '#ffc15e', '#ff9f1c', '#080357'], 81 | ['#52489c', '#4062bb', '#59c3c3', '#ebebeb', '#f45b69'], 82 | ['#ee6055', '#60d394', '#aaf683', '#ffd97d', '#ff9b85'], 83 | ['#55dde0', '#33658a', '#2f4858', '#f6ae2d', '#f26419'], 84 | ['#fcde9c', '#ffa552', '#ba5624', '#381d2a', '#c4d6b0'], 85 | ['#1c3144', '#d00000', '#ffba08', '#a2aebb', '#3f88c5'], 86 | ['#acf39d', '#e85f5c', '#9cfffa', '#773344', '#e3b5a4'], 87 | ],[ 88 | ['#6dd3ce', '#c8e9a0', '#f7a278', '#a13d63', '#351e29', '#2e282a'], 89 | ['#74b3ce', '#508991', '#172a3a', '#004346', '#09bc8a', '#bda0bc'], 90 | ['#6f1d1b', '#bb9457', '#432818', '#99582a', '#ffe6a7', '#020887'], 91 | ['#6d213c', '#946846', '#baab68', '#e3c16f', '#faff70', '#ffa9e7'], 92 | ['#bfb48f', '#564e58', '#904e55', '#f2efe9', '#252627', '#97dffc'], 93 | ['#0fa3b1', '#d9e5d6', '#eddea4', '#f7a072', '#ff9b42', '#e08dac'], 94 | ['#52414c', '#596157', '#5b8c5a', '#cfd186', '#e3655b', '#d67ab1'], 95 | ],[ 96 | ['#58355e', '#e03616', '#fff689', '#cfffb0', '#5998c5', '#8eb1c7', '#12eaea'], 97 | ['#abe188', '#f7ef99', '#f1bb87', '#f78e69', '#5d675b', '#13293d', '#006494'], 98 | ['#2f4b26', '#3e885b', '#85bda6', '#bedcfe', '#c0d7bb', '#62466b', '#45364b'], 99 | ['#212738', '#f97068', '#d1d646', '#edf2ef', '#57c4e5', '#8b85c1', '#d4cdf4'], 100 | ['#fcde9c', '#ffa552', '#ba5624', '#381d2a', '#c4d6b0', '#adaabf', '#020402'], 101 | ['#e3e7af', '#a2a77f', '#eff1c5', '#035e7b', '#002e2c', '#c9b1bd', '#2e0219'], 102 | ['#9cfffa', '#acf39d', '#b0c592', '#a97c73', '#af3e4d', '#4281a4', '#c1666b'], 103 | ],[ 104 | ['#247ba0', '#70c1b3', '#b2dbbf', '#f3ffbd', '#ff1654', '#436436', '#c84c09', '#420217'], 105 | ['#f0b67f', '#fe5f55', '#d6d1b1', '#c7efcf', '#eef5db', '#9d44b5', '#525252', '#272727'], 106 | ['#c0caad', '#9da9a0', '#654c4f', '#b26e63', '#cec075', '#00120b', '#35605a', '#004346'], 107 | ['#f2c57c', '#ddae7e', '#7fb685', '#426a5a', '#ef6f6c', '#466365', '#c4c6e7', '#baa5ff'], 108 | ['#87b38d', '#22031f', '#cc76a1', '#dd9296', '#f2b7c6', '#d17b0f', '#247ba0', '#449dd1'], 109 | ['#a20021', '#f52f57', '#f79d5c', '#f3752b', '#ededf4', '#048a81', '#06d6a0', '#54c6eb'], 110 | ['#d9e5d6', '#00a7e1', '#eddea4', '#f7a072', '#ff9b42', '#426a5a', '#ef6f6c', '#e05263'], 111 | ],[ 112 | ['#b2aa8e', '#0c1b33', '#7a306c', '#03b5aa', '#dbfe87', '#a44200', '#3a5743', '#226ce0', '#ff6b6b'], 113 | ['#bbbe64', '#eaf0ce', '#c0c5c1', '#7d8491', '#443850', '#655a7c', '#3e442b', '#f93943', '#445e93'], 114 | ['#272932', '#4d7ea8', '#828489', '#9e90a2', '#b6c2d9', '#f2d0a9', '#95f2d9', '#1cfeba', '#7cdedc'], 115 | ['#664c43', '#873d48', '#dc758f', '#e3d3e4', '#00ffcd', '#f3f9d2', '#bdc4a7', '#92b4a7', '#55d6be'], 116 | ['#c9f2c7', '#aceca1', '#96be8c', '#629460', '#243119', '#fa8334', '#388697', '#271033', '#30011e'], 117 | ['#808d8e', '#766c7f', '#947eb0', '#a3a5c3', '#a9d2d5', '#ff5a5f', '#f3a712', '#eec584', '#122c34'], 118 | ['#54457f', '#ac7b84', '#4c243b', '#b84a62', '#f5a6e6', '#638475', '#90e39a', '#ddf093', '#8b9556'], 119 | ],[ 120 | ['#d6f8d6', '#7fc6a4', '#5d737e', '#55505c', '#faf33e', '#3b252c', '#d6bbc0', '#d0a3bf', '#a77464', '#88292f'], 121 | ['#ff4d80', '#ff3e41', '#df367c', '#883955', '#4c3549', '#dde8b9', '#e8d2ae', '#acbdba', '#2e2f2f', '#051014'], 122 | ['#c9daea', '#03f7eb', '#00b295', '#191516', '#ab2346', '#0b5d1e', '#053b06', '#000000', '#51344d', '#6f5060'], 123 | ['#ca2e55', '#ffe0b5', '#8a6552', '#462521', '#bdb246', '#69a197', '#000000', '#1b2d2a', '#104547', '#6cd4ff'], 124 | ['#010001', '#2b0504', '#874000', '#bc5f04', '#f4442e', '#acf39d', '#9cfffa', '#e3b5a4', '#87bcde', '#cff27e'], 125 | ['#faa275', '#ff8c61', '#ce6a85', '#985277', '#5c374c', '#0d3b66', '#4a2545', '#000001', '#90aa86', '#461220'], 126 | ['#c4b7cb', '#bbc7ce', '#bfedef', '#98e2c6', '#545c52', '#2e4052', '#ffc857', '#412234', '#07004d', '#564e58'], 127 | ] 128 | ]; 129 | 130 | function round(num) { 131 | return Math.round(num * 100) / 100; 132 | } 133 | 134 | function randomSettings(bbox=false) { 135 | let svgSettings = {}; 136 | if (bbox) { 137 | if (Math.random() < 0.4) { 138 | svgSettings['fill'] = 'color' 139 | }; 140 | if (Math.random() < 0.1) { 141 | svgSettings['fill-opacity'] = round(Math.random() * 0.6 + 0.4); 142 | }; 143 | if (Math.random() < 0.3) { 144 | if (Math.random() < 0.1) { 145 | svgSettings['scale'] = round(Math.random() * 3 + 1); 146 | } else { 147 | svgSettings['scale'] = round(Math.random()); 148 | } 149 | } 150 | if (Math.random() < 0.2) { 151 | svgSettings['rx'] = Math.round(Math.random() * 100) / 100; 152 | svgSettings['ry'] = Math.round(Math.random() * 100) / 100; 153 | if (Math.random() < 0.8) { 154 | svgSettings['ry'] = svgSettings['rx']; 155 | } 156 | } 157 | } 158 | if (Math.random() < 0.4) { 159 | svgSettings['stroke'] = 'color'; 160 | } 161 | if (Math.random() < 0.1) { 162 | if (Math.random() < 0.5) { 163 | svgSettings['stroke-linecap'] = 'butt'; 164 | } else { 165 | svgSettings['stroke-linecap'] = 'square'; 166 | } 167 | } 168 | if (Math.random() < 0.4) { 169 | svgSettings['stroke-width'] = round(Math.random()); 170 | } 171 | if (Math.random() < 0.4) { 172 | svgSettings['stroke-opacity'] = round(Math.random() * 0.6 + 0.4); 173 | } 174 | return svgSettings; 175 | } 176 | 177 | function getRandomSettings() { 178 | let settings = []; 179 | settings.push(["default settings", 180 | {'background': 'color', 'fill': 'none', 'fill-opacity': 1, 'stroke': 'color', 'stroke-linecap': 'round', 'stroke-width': 0.3, 'stroke-opacity': 1, 'scale': 0.85, 'rx': 0.1, 'ry': 0.1}, 181 | ]); 182 | 183 | if (Math.random() < 0.8) { 184 | settings.push(["tiles", {'fill': 'color', 'stroke': 'color', 'stroke-width': 0.15}, {'deny': [], 'scale': 0.7}]) 185 | } 186 | 187 | const SETTING_NAMES = ["belts", "underground-belts", "pipes", "underground-pipes", "heat-pipes", "inserters", "rails", "power-lines", "green-wire-lines", "red-wire-lines"]; 188 | let temp_settings = []; 189 | 190 | const numSettings = Math.floor(Math.random() * 6); 191 | for (let i = 0; i < numSettings; i++) { 192 | const settingName = SETTING_NAMES[Math.floor(Math.random() * SETTING_NAMES.length)]; 193 | const settings = randomSettings(false); 194 | temp_settings.push([settingName, settings]); 195 | } 196 | 197 | const bboxCount = Math.floor(Math.random() * 5); 198 | const genericBuildingTerms = Object.keys(buildingGenericTerms); 199 | const buildingTerms = Object.keys(entityNameToProperties); 200 | const allTerms = [...genericBuildingTerms, ...genericBuildingTerms, ...buildingTerms]; 201 | 202 | for (let i = 0; i < bboxCount; i++) { 203 | const buildingTermCount = Math.floor(Math.random() * 5) + 1; 204 | const group = shuffleArray([...allTerms]).slice(0, buildingTermCount); 205 | const bboxGroupType = Math.random() < 0.8 ? "allow" : "deny"; 206 | const settings = randomSettings(true); 207 | temp_settings.push(["bbox", {[bboxGroupType]: group, ...settings}]); 208 | } 209 | 210 | settings = [...settings, ...shuffleArray(temp_settings)]; 211 | 212 | // Replace any "color" values with incrementing hex colors 213 | let noneColorCounter = 0; 214 | for (let s of settings) { 215 | if (typeof s[1] === 'object' && s[1] !== null) { 216 | for (let key of ['stroke', 'fill', 'background']) { 217 | if (key in s[1] && s[1][key] === 'color') { 218 | const hexColor = '#' + noneColorCounter.toString(16).padStart(6, '0'); 219 | s[1][key] = hexColor; 220 | noneColorCounter++; 221 | } 222 | } 223 | } 224 | } 225 | 226 | return settingsChangeColors(settings, 10, true); 227 | } 228 | 229 | function deepCopy(obj) { 230 | return JSON.parse(JSON.stringify(obj)); 231 | } 232 | 233 | function settingsChangeColors(settings, colorCount = null, changeBackground = true) { 234 | settings = deepCopy(settings); 235 | const originalColors = {}; 236 | let keysThatHaveAColor = ["stroke", "fill"]; 237 | if (changeBackground) { 238 | keysThatHaveAColor.push("background"); 239 | } 240 | 241 | for (let s of settings) { 242 | // Only check for stroke/fill if s[1] is an object and not null 243 | if (typeof s[1] === 'object' && s[1] !== null) { 244 | for (let key of keysThatHaveAColor) { 245 | if (key in s[1] && s[1][key] !== "none") { 246 | if (!(s[1][key] in originalColors)) { 247 | originalColors[s[1][key]] = [[s[1], key]]; 248 | } else { 249 | originalColors[s[1][key]].push([s[1], key]); 250 | } 251 | } 252 | } 253 | } 254 | } 255 | 256 | const originalColorsList = Object.keys(originalColors); 257 | shuffleArray(originalColorsList); 258 | 259 | if (colorCount === null) { 260 | colorCount = Math.min(10, originalColorsList.length); 261 | } else { 262 | colorCount = Math.min(colorCount, originalColorsList.length); 263 | } 264 | colorCount = Math.min(10, colorCount); 265 | 266 | const paletteIndex = colorCount - 2; 267 | const palettes = PREDEFINED_COLOR_PALETTES[paletteIndex]; 268 | const colorPalette = [...palettes[Math.floor(Math.random() * palettes.length)]]; 269 | shuffleArray(colorPalette); 270 | 271 | for (let i = 0; i < originalColorsList.length; i++) { 272 | const originalColor = originalColorsList[i]; 273 | for (let [entry, key] of originalColors[originalColor]) { 274 | entry[key] = colorPalette[i % colorPalette.length]; 275 | } 276 | } 277 | 278 | return settings; 279 | } 280 | 281 | function shuffleArray(array) { 282 | for (let i = array.length - 1; i > 0; i--) { 283 | const j = Math.floor(Math.random() * (i + 1)); 284 | [array[i], array[j]] = [array[j], array[i]]; 285 | } 286 | return array; 287 | } 288 | -------------------------------------------------------------------------------- /website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piebro/factorio-blueprint-visualizer/bc107c99be432e1fa31a7691ec7c707f3bad0bb9/website/favicon.ico -------------------------------------------------------------------------------- /website/index.css: -------------------------------------------------------------------------------- 1 | img, svg { 2 | width:auto; 3 | height:auto; 4 | max-width: 92vw; 5 | max-height: 76vh; 6 | margin: auto; 7 | display: block; 8 | padding-top: 10px; 9 | padding-bottom: 10px; 10 | } 11 | 12 | .button-container { 13 | text-align: center; 14 | float:center; 15 | } 16 | 17 | .btn { 18 | background-color: #04AA6D; 19 | color: white; 20 | padding: 16px; 21 | font-size: 16px; 22 | border: none; 23 | cursor: pointer; 24 | } 25 | 26 | .btn:hover{ 27 | background-color: #3e8e41; 28 | } 29 | 30 | .header-links { 31 | font-size: 20px; 32 | text-align: right; 33 | padding: 10px 20px; 34 | } 35 | 36 | .header-links a { 37 | color: #04AA6D; 38 | text-decoration: none; 39 | margin-left: 20px; 40 | } 41 | 42 | .header-links a:hover { 43 | text-decoration: underline; 44 | } 45 | 46 | .setting-window { 47 | display: none; 48 | position: fixed; 49 | z-index: 1; 50 | left: 0; 51 | top: 0; 52 | width: 100%; 53 | height: 100%; 54 | pointer-events: none; /* Allow clicks to pass through the overlay */ 55 | overflow: hidden; /* Hide scrollbar */ 56 | } 57 | 58 | .setting-window-content { 59 | padding: 0px 4px 0px 4px; 60 | position: absolute; 61 | width: 80%; 62 | max-width: 600px; 63 | cursor: move; 64 | background-color: #04AA6D; 65 | pointer-events: auto; 66 | resize: both; 67 | overflow: auto; 68 | min-width: 300px; 69 | min-height: 200px; 70 | height: 60vh; 71 | } 72 | 73 | .close { 74 | color: white; 75 | position: absolute; 76 | right: 10px; 77 | top: 0px; 78 | font-size: 28px; 79 | font-weight: bold; 80 | cursor: pointer; 81 | pointer-events: auto; 82 | z-index: 2; 83 | } 84 | 85 | .close:hover { 86 | color: #ddd; 87 | } 88 | 89 | #settingsText { 90 | width: 100%; 91 | height: 60vh; 92 | margin-top: 10px; 93 | font-family: monospace; 94 | } 95 | 96 | .CodeMirror { 97 | font-family: monospace; 98 | font-size: 14px; 99 | margin: 30px 0px 0px 0px; 100 | } 101 | 102 | /* Add these new tooltip styles */ 103 | .tooltip { 104 | position: relative; 105 | display: inline-block; 106 | } 107 | 108 | .tooltip .tooltiptext { 109 | visibility: hidden; 110 | width: 200px; 111 | background-color: #555; 112 | color: #fff; 113 | text-align: center; 114 | border-radius: 6px; 115 | padding: 5px; 116 | position: absolute; 117 | z-index: 1; 118 | bottom: 125%; 119 | left: 50%; 120 | transform: translateX(-50%); 121 | opacity: 0; 122 | transition: opacity 0.3s; 123 | } 124 | 125 | .tooltip:hover .tooltiptext { 126 | visibility: visible; 127 | opacity: 1; 128 | } 129 | 130 | /* Hide tooltips on mobile devices */ 131 | @media (max-width: 768px) { 132 | .tooltip .tooltiptext { 133 | display: none; 134 | } 135 | } --------------------------------------------------------------------------------