├── .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'}]] 0eNrFXe1OI0cQfJf9bZ/m+4NXiVDEmQ23irGRgVPQiXfP+kiM7zKzXdWKuX+AcNPdVT073VOzfBs+b5/Hh8O0exquvg3TZr97HK5++zY8Tne7m+3xZ08vD+NwNUxP4/2wGnY398fv9tN2fRj/mHbj4WV4XQ3T7nb8a7iyryvxo5sv4/20udmuH7Y38199/7B7vV4N4+5peprGNye+f/Py++75/vN4mK2fbDxMs+HV8LB/nH95vzv+rdnA2tpPcTW8DFelfIqvR19+suBwC7Vtwf9gYf20X98d9s+725Ytc+7NaridDuPm7RdSw3LQWK4/W3YNyxG3XLqGWy4nKZ1VyGaWDDgB0AIb6HhQ8dz4LpytpFtD4EkRxYp1YISgrVP4BlHN4vVhKKpZvDwihxNeHZmDSaqOJHDbZtRAD+bCh4ahjFdNpVB2Rgi5CCE7qTZslB4RDrbQ8wGvARsorjq8CGyiyOqiFHWWok4K3yC2uYxb5p5fDi8Qx63+rgr5dEHgoTewhd5WxeLRRYot3iksQ1h7L0UtLXoerxGXKbZ4qUa8lzBNsIVedHgteEcx1uO14APHFqkWvLSiBqPwDeJbwGvEJ4otAa+RYCmkAv58CZ7LRhCQCtJ+PETYQg/rpIgOa7Dw2gmRw1pqQ4K0Vofabq0Xer/qfnZtRm/+evrec9/cfr3Zbcbb9dHcw2G/GR8fp93dbPDYjT8erc0/vX2eP/p1dmR9P3+9nT/oW95FA3v3C5yzoHP5F/jmQN9OW6KPdM6Dzp2e4R/pXACdOy34H+lcBJ07rVAf6Zw4lbH/rsE1tlejKM5l3lehnolCjBjsuTFxtY3va+X9eDs936/H7fz7h2mzfthvl8ZQ/9jfjdPdl8/758NxypjCdWuyJe2+T5OyXgISvrNYh278rad0EqeYXvKNmGI6CpsUWGzMAjYz0tk24YkiQaUUSFUiETxJNRIlA3iFkPyQ9ttZ8Czj2+1EsSNbkh1lsXBXObXIkaX6qFL8xADHU9BkaYNtg+QbPq60kQMnkeDYvIDODHXxTXSkurFJSoG027bS2pzF6Yy0gha8RJyjKFKkCaaTFpZCTGcCRZHiSYq4tESRtCqlRZEiVYmTVrCCV4m3HDziCEfaXBVihOM5eAoJj48L8Mxg19iER5znSItYlXZXXloDqlQlQdoAVGI+YyiKVGmGGaTVpeIzzMDtz2okKRLCEkWa5VulEgnSClazpnlIEDgFb21SxzvizPi01cjYmTHednScs8bCJnLPhFMEmKAzUUN0Hrmbu7bpoGhqEgYL3m50YcHHmu8tEUiarDANwlXQLqnLpYpa6GWOEFJYjjGikCKLvjm+U8IoZz3axPQybwNqoRsdvosqHGVt4i1jjLXEYbEh6VIUrReIdoWboh5YjiiTzKHlrMI0BpesthAfeM7DJnqlwugqKkcaUVjhohhgUvRzGO9chjutrncFNtFNP76hem8oMep6ozCNUZeRVxSONIS+4r1FxBD3+AbMRzIhAe4Le2TyUeEdyAS8ijy58/QZbme7gRfYRK+KRJlF8JIXhM4ikFvoYOGmsOudg030csRIJwLHrxAUprHCCnhVhMRRl1BbvPeNPb0qIbAwoq1CS0z7tiovIOwai4bXl/WNKeRHfWNOMTWxxmAqaQ/PTd5MNo2E3i2Q/mDBmrej89Nh+Zfx5uvL95PyzeFm86f6nNzGyI85Gulq1lVU1FXDdmjazvygA4a5oJOOBZQrP49A83p2Li8yyFycQcQZvyFBFg/5s4gDccqfSJokvJDL5VHgxxQw3RL9FIIBzvw0AYanoOOEBfZUvulH05rxKj6zfSn+ZMtvD1CURS2CiyIShBzh1P+jTMl4Ibt0eSQi363DnEv8vg1GWXGkCyNU0K56gUGVb6vRvBa8ls9sX4pBRbGhRlE+01WQukNrGoentjalbbZ4zbY0QGQSlRVnm9HQu1QYNVvb2LOW+APBRrRtaooy1iBHS+hYI4tG5SWi/UxWw19lRjNZrVbW2ab+XErOmCb9CV3G2R/BEi4qM4zIB0KaYWTIIn20CSNGjDvZLEplJVdVLfRtbjjwSstquwC5Mz0GJ1jtrfgz7et18y9Z8LS2m1RHyDaqHLlXNFUQfxwh1LCGQ9+JSg3r5TwSN5kdG3vmpcULIBX+pjmcyaqVAze5fywlZ5t6MWeNptXEEi5qPKy443GEysOKOx5HvCLDVhI04i0ZzrKZFE+ynZzJxF+4h2PPvCR7AaWilTp36F9XzuU2/St6gt5PrDOa7rYXPCH4OAsbo5Fz/LE5SgFRCuKtnEm8hLxhY1fI0hdQSvy7AOBMZq2UvE3/zspPvIDDRzbZFT1S75OBkI94sjt1XjWq6JGBkYyQUwMnvpQjGDmTgZfbL0QbVUedGQs3EUeduedh1kxJMjKIcsSrOs4mRBljpVQ16yTGTshIzmY6GDjBamYmWF6JV3aczXqwvAavObQFcxLwQ9suZlEzdgFjT4oxBxh6VoxjQDaIwkY5qVUxhMFySshQ2DKIFj567gUeneLoGUM8agYTYE6DYoAC5lScS8glSmhKrGGzmjUzDzCtRTPzAPNaNafoWE7Ed0BY+VGYrGa0geU1Oc0gAYzda0YeGGZJvN4rb68IzYezbF6TZpAAxp5xPUA39qLRA4C4V03nj+U1q2YUWF6zeGu+innNqskElldCw+HZ7VUOmqkCmNeoUTaAOUm4sqGLWdYMEMC8Fk3zD8ZeNc0/hlkRb9cXMa+EjiKwG+szHQWhAq4/Kka2092Xp/9BMeLO5BaiN/Hy3gSNvvVi3kRc23N5Z5JCc3oxZzKuYHSX96Zo9JQX86bimkJ7cW+q0SgcL+aNxSvq8vVdnUa7dzFv8JXYX/65UHGp3LF3b9vQ3DD5bg14wbg4pc6yfxke9vZtaARy/42xuQOrmpskWP68+KoVI4XuRc2OlU04dMjYN+H5ISOWf2/QV0gseKe4nIEiKFWABSDM8Fyub6MoZmcoAppbGGD+rIGlMd3YZXmNTBHr4HlQ34ZXzINADCz8OroF/zQ3G1AcpTpwAI4Znp/0bRTFjAPFQHODAcyfg99G149d/L8zXuaI+C4UL+NI/O8ZX5cwuH7bNc123v9B3Gr4Oh4e3xJZbMjV5ZCqS6a8vv4NVI5H5A==
--------------------------------------------------------------------------------
/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 | Upload Blueprint
363 |
364 | Random Drawing Setting
365 | You can also use the arrow keys
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 | ←
376 | You can also use the arrow keys
377 |
378 | Show Entity Count
379 | Modify Drawing Settings
380 | Random Color Pallet
381 | Save
382 |
383 | →
384 | You can also use the arrow keys
385 |
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 | }
--------------------------------------------------------------------------------