porta 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- porta-1.0.0/.github/workflows/ci.yml +41 -0
- porta-1.0.0/.github/workflows/publish.yml +25 -0
- porta-1.0.0/.gitignore +27 -0
- porta-1.0.0/CLAUDE.md +53 -0
- porta-1.0.0/LICENSE +21 -0
- porta-1.0.0/PKG-INFO +131 -0
- porta-1.0.0/README.md +104 -0
- porta-1.0.0/docs/door.md +126 -0
- porta-1.0.0/docs/img/adjacency.svg +43 -0
- porta-1.0.0/docs/img/align_default.svg +31 -0
- porta-1.0.0/docs/img/align_end.svg +31 -0
- porta-1.0.0/docs/img/align_start.svg +31 -0
- porta-1.0.0/docs/img/auto-flank.svg +43 -0
- porta-1.0.0/docs/img/auto-shift.svg +36 -0
- porta-1.0.0/docs/img/auto-single.svg +40 -0
- porta-1.0.0/docs/img/auto-union.svg +32 -0
- porta-1.0.0/docs/img/capstone.svg +74 -0
- porta-1.0.0/docs/img/corner.svg +34 -0
- porta-1.0.0/docs/img/door-capstone.svg +78 -0
- porta-1.0.0/docs/img/door-declarations.svg +38 -0
- porta-1.0.0/docs/img/door-multi.svg +28 -0
- porta-1.0.0/docs/img/door-outside.svg +29 -0
- porta-1.0.0/docs/img/door-overview.svg +35 -0
- porta-1.0.0/docs/img/door-standalone.svg +36 -0
- porta-1.0.0/docs/img/flank.svg +43 -0
- porta-1.0.0/docs/img/flank_align.svg +35 -0
- porta-1.0.0/docs/img/overview.svg +37 -0
- porta-1.0.0/docs/img/readme.svg +47 -0
- porta-1.0.0/docs/img/root.svg +25 -0
- porta-1.0.0/docs/img/shift.svg +49 -0
- porta-1.0.0/docs/img/span.svg +28 -0
- porta-1.0.0/docs/room.md +362 -0
- porta-1.0.0/examples/manor.porta +22 -0
- porta-1.0.0/pyproject.toml +74 -0
- porta-1.0.0/src/build_figures.py +70 -0
- porta-1.0.0/src/porta/__init__.py +9 -0
- porta-1.0.0/src/porta/cli.py +101 -0
- porta-1.0.0/src/porta/errors.py +60 -0
- porta-1.0.0/src/porta/layout.py +639 -0
- porta-1.0.0/src/porta/model.py +139 -0
- porta-1.0.0/src/porta/parser.py +350 -0
- porta-1.0.0/src/porta/py.typed +0 -0
- porta-1.0.0/src/porta/render.py +232 -0
- porta-1.0.0/tests/fixtures/layouts/corridor.porta +4 -0
- porta-1.0.0/tests/fixtures/layouts/corridor.svg +41 -0
- porta-1.0.0/tests/fixtures/layouts/external-door-wide.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/external-door-wide.svg +20 -0
- porta-1.0.0/tests/fixtures/layouts/external-door.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/external-door.svg +20 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-chain.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-chain.svg +34 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-corner.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-corner.svg +30 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-fill.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-fill.svg +36 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-height.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-height.svg +30 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-shift.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-shift.svg +27 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-up.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-up.svg +30 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-width.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/match-anchor-width.svg +30 -0
- porta-1.0.0/tests/fixtures/layouts/same-direction-auto.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/same-direction-auto.svg +30 -0
- porta-1.0.0/tests/fixtures/layouts/same-direction.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/same-direction.svg +30 -0
- porta-1.0.0/tests/fixtures/layouts/snug-fit-auto.porta +4 -0
- porta-1.0.0/tests/fixtures/layouts/snug-fit-auto.svg +42 -0
- porta-1.0.0/tests/fixtures/layouts/snug-fit.porta +4 -0
- porta-1.0.0/tests/fixtures/layouts/snug-fit.svg +42 -0
- porta-1.0.0/tests/fixtures/layouts/three-room-incidental-door.porta +4 -0
- porta-1.0.0/tests/fixtures/layouts/three-room-incidental-door.svg +36 -0
- porta-1.0.0/tests/fixtures/layouts/three-room-shift-east.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/three-room-shift-east.svg +28 -0
- porta-1.0.0/tests/fixtures/layouts/three-room-shift-south.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/three-room-shift-south.svg +28 -0
- porta-1.0.0/tests/fixtures/layouts/three-room-square.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/three-room-square.svg +28 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-full.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-full.svg +27 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-offset.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-offset.svg +27 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-up-offset.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-up-offset.svg +27 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-up-wide.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-up-wide.svg +27 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-wide.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-door-wide.svg +27 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-align-end-shift.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-align-end-shift.svg +25 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-align-end.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-align-end.svg +25 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-overhang-end.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-overhang-end.svg +23 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-overhang.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-overhang.svg +23 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-shift-east.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-shift-east.svg +22 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-shift-west.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down-shift-west.svg +22 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-down.svg +21 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-align-end-shift.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-align-end-shift.svg +25 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-align-end.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-align-end.svg +25 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-overhang-end.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-overhang-end.svg +23 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-overhang.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-overhang.svg +23 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-shift-north.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-shift-north.svg +22 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-shift-south.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left-shift-south.svg +22 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-left.svg +21 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-no-door.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-no-door.svg +26 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-align-end-shift.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-align-end-shift.svg +25 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-align-end.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-align-end.svg +25 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-overhang-end.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-overhang-end.svg +23 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-overhang.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-overhang.svg +23 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-shift-north.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-shift-north.svg +22 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-shift-south.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right-shift-south.svg +22 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-right.svg +21 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-two-doors.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-two-doors.svg +32 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-align-end-shift.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-align-end-shift.svg +25 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-align-end.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-align-end.svg +25 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-overhang-end.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-overhang-end.svg +23 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-overhang.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-overhang.svg +23 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-shift-east.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-shift-east.svg +22 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-shift-west.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up-shift-west.svg +22 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up.porta +2 -0
- porta-1.0.0/tests/fixtures/layouts/two-room-up.svg +21 -0
- porta-1.0.0/tests/fixtures/layouts/union-horizontal.porta +3 -0
- porta-1.0.0/tests/fixtures/layouts/union-horizontal.svg +30 -0
- porta-1.0.0/tests/fixtures/manor.svg +125 -0
- porta-1.0.0/tests/test_cli.py +126 -0
- porta-1.0.0/tests/test_layout.py +675 -0
- porta-1.0.0/tests/test_model.py +25 -0
- porta-1.0.0/tests/test_parser.py +371 -0
- porta-1.0.0/tests/test_render.py +303 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
check:
|
|
14
|
+
name: ${{ matrix.name }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
include:
|
|
20
|
+
- { name: test, cmd: "pytest" }
|
|
21
|
+
- { name: lint, cmd: "ruff check ." }
|
|
22
|
+
- { name: format, cmd: "ruff format --check ." }
|
|
23
|
+
- { name: types, cmd: "mypy" }
|
|
24
|
+
- { name: docs, cmd: "python src/build_figures.py && git diff --exit-code" }
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v6
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.14"
|
|
30
|
+
enable-cache: true
|
|
31
|
+
- run: uv run --extra dev ${{ matrix.cmd }}
|
|
32
|
+
|
|
33
|
+
build:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: astral-sh/setup-uv@v6
|
|
38
|
+
with:
|
|
39
|
+
python-version: "3.14"
|
|
40
|
+
enable-cache: true
|
|
41
|
+
- run: uv build
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI via Trusted Publishing (OIDC) — no stored token.
|
|
4
|
+
# Fires when a GitHub release is published; can also be run by hand
|
|
5
|
+
# (Actions -> publish -> Run workflow) to publish an already-made release.
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
environment: pypi
|
|
18
|
+
permissions:
|
|
19
|
+
id-token: write # required for Trusted Publishing
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: astral-sh/setup-uv@v6
|
|
23
|
+
- run: uv run --extra dev pytest
|
|
24
|
+
- run: uv build
|
|
25
|
+
- run: uv publish
|
porta-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.eggs/
|
|
8
|
+
|
|
9
|
+
# Virtual envs / tooling
|
|
10
|
+
.venv/
|
|
11
|
+
.uv/
|
|
12
|
+
uv.lock
|
|
13
|
+
|
|
14
|
+
# Test / coverage
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
|
|
19
|
+
# Editors / OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
.idea/
|
|
22
|
+
.vscode/
|
|
23
|
+
|
|
24
|
+
# Rendered output (examples may emit these locally)
|
|
25
|
+
*.svg
|
|
26
|
+
!docs/**/*.svg
|
|
27
|
+
!tests/fixtures/**/*.svg
|
porta-1.0.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# porta
|
|
2
|
+
|
|
3
|
+
A standalone Python package: a relational DSL for authoring D&D floor plans and
|
|
4
|
+
rendering them to SVG. CLI-driven, zero runtime dependencies.
|
|
5
|
+
|
|
6
|
+
The user-facing reference is [`docs/room.md`](docs/room.md) (rooms, placement,
|
|
7
|
+
auto-dimensions, validation) and [`docs/door.md`](docs/door.md) (doors); for
|
|
8
|
+
exact behaviour the code is the source of truth. The body below is just working
|
|
9
|
+
conventions.
|
|
10
|
+
|
|
11
|
+
## Orientation
|
|
12
|
+
|
|
13
|
+
- **What it does** — parse a `.porta` spec (rooms + relational placement) →
|
|
14
|
+
resolve geometry by DAG propagation → render SVG.
|
|
15
|
+
- **Package layout** — `src/porta/`: `cli.py` (argparse entry), `parser.py`
|
|
16
|
+
(`.porta` → model), `model.py` (dataclasses), `layout.py` (relations →
|
|
17
|
+
coordinates, validation), `render.py` (model → SVG/ASCII), `errors.py` (the
|
|
18
|
+
`PortaError` hierarchy, each carrying a source line). Tests in `tests/`.
|
|
19
|
+
`src/build_figures.py` regenerates the doc figures (a dev tool, not shipped).
|
|
20
|
+
- **Consumer** — the `isles` D&D vault (sibling repo) installs porta via
|
|
21
|
+
`uv add --editable ../porta`. The `.porta` sources and rendered SVGs live in
|
|
22
|
+
*that* repo, not here. porta knows nothing about isles.
|
|
23
|
+
|
|
24
|
+
## Workflow
|
|
25
|
+
|
|
26
|
+
- Run with **`uv`**: `uv run porta draw <in>.porta -o <out>.svg`.
|
|
27
|
+
- Gates (all run in CI on push/PR; run them locally before pushing):
|
|
28
|
+
`uv run --extra dev pytest` · `ruff check .` · `ruff format --check .` ·
|
|
29
|
+
`mypy` · and a `docs` job that rebuilds the figures and fails on any drift.
|
|
30
|
+
- Doc figures (`docs/img/`) are generated from fenced ` ```porta ` examples
|
|
31
|
+
(those with a path on the fence) in `README.md` and `docs/*.md` by
|
|
32
|
+
`src/build_figures.py` — don't hand-edit them, and keep the examples valid
|
|
33
|
+
(every one is solved on each build).
|
|
34
|
+
- Use **`python`**, never `python3`.
|
|
35
|
+
- Use **relative paths** in shell/git commands.
|
|
36
|
+
- When handing the user a path to open, avoid spaces in it.
|
|
37
|
+
|
|
38
|
+
## Conventions
|
|
39
|
+
|
|
40
|
+
- Modern type hints (`list[str]`, `X | None`); dataclasses for the model.
|
|
41
|
+
- Google-style docstrings on public functions.
|
|
42
|
+
- Keep the runtime dependency-free: SVG via stdlib string/XML templating.
|
|
43
|
+
- Small, pure, testable functions — especially in `layout.py`, where geometry
|
|
44
|
+
resolution and overlap detection should be unit-tested on tiny inputs.
|
|
45
|
+
- Tests: prefer `pytest.mark.parametrize` for families of similar cases (valid
|
|
46
|
+
vs. invalid inputs, error conditions, geometry fixtures) over copy-pasted
|
|
47
|
+
near-identical test functions. Once a test is parametrized, adding a case is
|
|
48
|
+
one line — so be liberal and keep coverage comprehensive (give each case a
|
|
49
|
+
readable `id`). Reserve standalone test functions for genuinely distinct
|
|
50
|
+
assertions.
|
|
51
|
+
- Tests mirror the source: one `tests/test_<module>.py` per `src/porta/<module>.py`
|
|
52
|
+
(e.g. `test_layout.py` covers all of `layout.py`). Error types in `errors.py`
|
|
53
|
+
are tested where they're raised, not in a separate file.
|
porta-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 William J. Bradshaw
|
|
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.
|
porta-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: porta
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A relational DSL for authoring and rendering SVG floorplans.
|
|
5
|
+
Project-URL: Homepage, https://github.com/willbradshaw/porta
|
|
6
|
+
Project-URL: Repository, https://github.com/willbradshaw/porta
|
|
7
|
+
Project-URL: Issues, https://github.com/willbradshaw/porta/issues
|
|
8
|
+
Author: Will Bradshaw
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cartography,dnd,dsl,floorplan,svg,ttrpg
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Games/Entertainment :: Role-Playing
|
|
19
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.14
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# `porta`
|
|
29
|
+
|
|
30
|
+
`porta` is two things:
|
|
31
|
+
|
|
32
|
+
1. A **DSL** for concisely defining a relational floorplan; and
|
|
33
|
+
2. A **software package** for deterministically converting a floorplan into
|
|
34
|
+
a clean SVG map.
|
|
35
|
+
|
|
36
|
+
```porta docs/img/readme.svg
|
|
37
|
+
room hall "Great Hall" 40x20 root
|
|
38
|
+
room parlour "Parlour" 20x20 left-of hall
|
|
39
|
+
room kitchen "Kitchen" 20x20 right-of hall
|
|
40
|
+
room study "Study" ?x20 down-of parlour
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
<img alt="Four rooms rendered to an SVG floor plan" src="docs/img/readme.svg" width="60%">
|
|
44
|
+
|
|
45
|
+
## The `porta` DSL
|
|
46
|
+
|
|
47
|
+
`porta` floorplans are defined in `.porta` files adhering to a strict specification.
|
|
48
|
+
Each room is defined in a single line relative to one or more anchor rooms,
|
|
49
|
+
forming a dependency graph all the way back to the initial root room. This model
|
|
50
|
+
creates a 1-to-1 correspondence between the floorplan and the final SVG map it
|
|
51
|
+
produces, allowing `porta` to rapidly and deterministically generate one from
|
|
52
|
+
the other.
|
|
53
|
+
|
|
54
|
+
Files are read line by line: a `#` starts a comment that runs to the end of the
|
|
55
|
+
line (a `#` inside a quoted name is literal), and blank lines are ignored.
|
|
56
|
+
|
|
57
|
+
For more on the `porta` DSL specification, see the following documentation:
|
|
58
|
+
|
|
59
|
+
- [**Rooms**](docs/room.md) — the `room` statement, room positioning,
|
|
60
|
+
auto-dimensions (`?`), and the layout resolution procedure.
|
|
61
|
+
- [**Doors**](docs/door.md) — default doors & how to modify them, and
|
|
62
|
+
the `door` statement for explicitly adding non-default doors.
|
|
63
|
+
|
|
64
|
+
## The `porta` tool
|
|
65
|
+
|
|
66
|
+
Once a valid floorplan has been written, `porta draw` converts it into an
|
|
67
|
+
SVG map in a two-step process. First, the dependency graph is traversed
|
|
68
|
+
and the relations in the floorplan are converted into a coordinate system.
|
|
69
|
+
Second, that coordinate system is used to deterministically generate an
|
|
70
|
+
SVG file.
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
porta draw plan.porta -o plan.svg
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The solved coordinate system can also be viewed and debugged directly
|
|
77
|
+
as an ASCII grid:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
porta draw plan.porta --debug-ascii
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A plan that can't be solved (due to overlaps, missing anchors, gaps
|
|
84
|
+
between a room and its anchor, etc) will fail and raise an error.
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
`porta` is published on PyPI and can be installed with `pip`:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
pip install porta
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Alternatively, install it from a checkout of this repo. Add the `[dev]` extra
|
|
95
|
+
to pull in the test and lint tools as well:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
pip install -e . # editable install
|
|
99
|
+
pip install -e ".[dev]" # ... plus pytest, ruff, and mypy
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Why `porta`?
|
|
103
|
+
|
|
104
|
+
`porta` is built to be:
|
|
105
|
+
|
|
106
|
+
- **Text-based** — a plan is plain text: editable in any editor, diffable, and
|
|
107
|
+
version-controlled alongside your campaign notes. No WYSIWYG, no binary files.
|
|
108
|
+
- **Concise** — a terse syntax with strong defaults; a whole plan is a handful
|
|
109
|
+
of short lines.
|
|
110
|
+
- **Deterministic** — no procedural generation and no randomness; the same plan
|
|
111
|
+
always renders the same map.
|
|
112
|
+
- **Relational** — rooms are placed against other rooms, never drawn by hand or
|
|
113
|
+
given raw coordinates.
|
|
114
|
+
- **Spatial** — the output is a flush, to-scale floor plan in tabletop
|
|
115
|
+
conventions, not a flowchart or connectivity graph.
|
|
116
|
+
- **Fast** — placement is a single pass over a dependency graph, with no
|
|
117
|
+
constraint solver to run.
|
|
118
|
+
- **AI-friendly** — terse, text-only, and predictable, so a language model can
|
|
119
|
+
read and write plans reliably (this one falls out of the rest).
|
|
120
|
+
|
|
121
|
+
No existing tool hits all of these:
|
|
122
|
+
|
|
123
|
+
- **Procedural and AI generators** (Watabou, Inkarnate) aren't deterministic or
|
|
124
|
+
controllable — they invent a layout instead of rendering yours.
|
|
125
|
+
- **GUI map editors** (Dungeondraft, Dungeon Scrawl) aren't text: mouse-driven,
|
|
126
|
+
binary, and kept apart from your notes.
|
|
127
|
+
- **Diagram tools** (Mermaid, Graphviz, D2) give connectivity, not a spatial,
|
|
128
|
+
to-scale map.
|
|
129
|
+
- **Hand-written SVG** is text, but neither concise nor relational — verbose and
|
|
130
|
+
easy to get wrong.
|
|
131
|
+
|
porta-1.0.0/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# `porta`
|
|
2
|
+
|
|
3
|
+
`porta` is two things:
|
|
4
|
+
|
|
5
|
+
1. A **DSL** for concisely defining a relational floorplan; and
|
|
6
|
+
2. A **software package** for deterministically converting a floorplan into
|
|
7
|
+
a clean SVG map.
|
|
8
|
+
|
|
9
|
+
```porta docs/img/readme.svg
|
|
10
|
+
room hall "Great Hall" 40x20 root
|
|
11
|
+
room parlour "Parlour" 20x20 left-of hall
|
|
12
|
+
room kitchen "Kitchen" 20x20 right-of hall
|
|
13
|
+
room study "Study" ?x20 down-of parlour
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
<img alt="Four rooms rendered to an SVG floor plan" src="docs/img/readme.svg" width="60%">
|
|
17
|
+
|
|
18
|
+
## The `porta` DSL
|
|
19
|
+
|
|
20
|
+
`porta` floorplans are defined in `.porta` files adhering to a strict specification.
|
|
21
|
+
Each room is defined in a single line relative to one or more anchor rooms,
|
|
22
|
+
forming a dependency graph all the way back to the initial root room. This model
|
|
23
|
+
creates a 1-to-1 correspondence between the floorplan and the final SVG map it
|
|
24
|
+
produces, allowing `porta` to rapidly and deterministically generate one from
|
|
25
|
+
the other.
|
|
26
|
+
|
|
27
|
+
Files are read line by line: a `#` starts a comment that runs to the end of the
|
|
28
|
+
line (a `#` inside a quoted name is literal), and blank lines are ignored.
|
|
29
|
+
|
|
30
|
+
For more on the `porta` DSL specification, see the following documentation:
|
|
31
|
+
|
|
32
|
+
- [**Rooms**](docs/room.md) — the `room` statement, room positioning,
|
|
33
|
+
auto-dimensions (`?`), and the layout resolution procedure.
|
|
34
|
+
- [**Doors**](docs/door.md) — default doors & how to modify them, and
|
|
35
|
+
the `door` statement for explicitly adding non-default doors.
|
|
36
|
+
|
|
37
|
+
## The `porta` tool
|
|
38
|
+
|
|
39
|
+
Once a valid floorplan has been written, `porta draw` converts it into an
|
|
40
|
+
SVG map in a two-step process. First, the dependency graph is traversed
|
|
41
|
+
and the relations in the floorplan are converted into a coordinate system.
|
|
42
|
+
Second, that coordinate system is used to deterministically generate an
|
|
43
|
+
SVG file.
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
porta draw plan.porta -o plan.svg
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The solved coordinate system can also be viewed and debugged directly
|
|
50
|
+
as an ASCII grid:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
porta draw plan.porta --debug-ascii
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A plan that can't be solved (due to overlaps, missing anchors, gaps
|
|
57
|
+
between a room and its anchor, etc) will fail and raise an error.
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
`porta` is published on PyPI and can be installed with `pip`:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pip install porta
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Alternatively, install it from a checkout of this repo. Add the `[dev]` extra
|
|
68
|
+
to pull in the test and lint tools as well:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
pip install -e . # editable install
|
|
72
|
+
pip install -e ".[dev]" # ... plus pytest, ruff, and mypy
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Why `porta`?
|
|
76
|
+
|
|
77
|
+
`porta` is built to be:
|
|
78
|
+
|
|
79
|
+
- **Text-based** — a plan is plain text: editable in any editor, diffable, and
|
|
80
|
+
version-controlled alongside your campaign notes. No WYSIWYG, no binary files.
|
|
81
|
+
- **Concise** — a terse syntax with strong defaults; a whole plan is a handful
|
|
82
|
+
of short lines.
|
|
83
|
+
- **Deterministic** — no procedural generation and no randomness; the same plan
|
|
84
|
+
always renders the same map.
|
|
85
|
+
- **Relational** — rooms are placed against other rooms, never drawn by hand or
|
|
86
|
+
given raw coordinates.
|
|
87
|
+
- **Spatial** — the output is a flush, to-scale floor plan in tabletop
|
|
88
|
+
conventions, not a flowchart or connectivity graph.
|
|
89
|
+
- **Fast** — placement is a single pass over a dependency graph, with no
|
|
90
|
+
constraint solver to run.
|
|
91
|
+
- **AI-friendly** — terse, text-only, and predictable, so a language model can
|
|
92
|
+
read and write plans reliably (this one falls out of the rest).
|
|
93
|
+
|
|
94
|
+
No existing tool hits all of these:
|
|
95
|
+
|
|
96
|
+
- **Procedural and AI generators** (Watabou, Inkarnate) aren't deterministic or
|
|
97
|
+
controllable — they invent a layout instead of rendering yours.
|
|
98
|
+
- **GUI map editors** (Dungeondraft, Dungeon Scrawl) aren't text: mouse-driven,
|
|
99
|
+
binary, and kept apart from your notes.
|
|
100
|
+
- **Diagram tools** (Mermaid, Graphviz, D2) give connectivity, not a spatial,
|
|
101
|
+
to-scale map.
|
|
102
|
+
- **Hand-written SVG** is text, but neither concise nor relational — verbose and
|
|
103
|
+
easy to get wrong.
|
|
104
|
+
|
porta-1.0.0/docs/door.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Doors
|
|
2
|
+
|
|
3
|
+
By default, `porta` draws a **door** on every wall between a declared
|
|
4
|
+
[room](room.md) and its anchor. Explicit [door declarations](#door-declarations)
|
|
5
|
+
and [statements](#the-door-statement) are needed only to change a door from
|
|
6
|
+
its default settings (remove it, resize it, move it) or to add an additional door
|
|
7
|
+
in a non-default location.
|
|
8
|
+
|
|
9
|
+
> [!NOTE]
|
|
10
|
+
> Doors are drawn as thick black marks straddling the walls between
|
|
11
|
+
> the rooms they connect. They only appear in rendered SVG; the
|
|
12
|
+
> ASCII debug view (`porta draw <plan>.porta --debug-ascii`)
|
|
13
|
+
> shows the room layout without them.
|
|
14
|
+
|
|
15
|
+
## Default doors
|
|
16
|
+
|
|
17
|
+
Every [relation](room.md#relations) connecting two rooms is given a
|
|
18
|
+
door by default. These default doors are 5 feet wide and positioned
|
|
19
|
+
as close to the centre of the wall as possible. If they cannot be placed
|
|
20
|
+
fully centrally due to the 5-foot wall grid, they are positioned
|
|
21
|
+
immediately above (for vertical walls) or to the left of the center
|
|
22
|
+
(for horizontal walls).
|
|
23
|
+
|
|
24
|
+
```porta img/door-overview.svg
|
|
25
|
+
room hall "Hall" 20x20 root
|
|
26
|
+
room kitchen "Kitchen" 20x20 right-of hall
|
|
27
|
+
room study "Study" 20x20 down-of hall
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
<img alt="Three rooms with a default door on each shared wall" src="img/door-overview.svg" width="70%">
|
|
31
|
+
|
|
32
|
+
## Door declarations
|
|
33
|
+
|
|
34
|
+
The door drawn by a [relation](room.md#relations) can be modified by
|
|
35
|
+
a **door declaration** added to the end of that relation. There are two
|
|
36
|
+
types of door declaration:
|
|
37
|
+
|
|
38
|
+
- A `no-door` declaration suppresses the default door on that relation.
|
|
39
|
+
- A `door*` declaration changes the **width** and/or **offset** of the
|
|
40
|
+
door: `door=W` sets the door width to `W` feet, `door@O` sets the
|
|
41
|
+
start of the door to `O` feet from the start of the wall, and
|
|
42
|
+
`door=W@O` sets both.
|
|
43
|
+
|
|
44
|
+
```porta img/door-declarations.svg
|
|
45
|
+
room r "Root" 20x20 root
|
|
46
|
+
room a "Room A" 20x20 right-of r no-door
|
|
47
|
+
room b "Room B" 20x20 down-of r door=10
|
|
48
|
+
room c "Room C" 20x20 right-of b door@15
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
<img alt="Four rooms with various modifications to their doors" src="img/door-declarations.svg" width="70%">
|
|
52
|
+
|
|
53
|
+
## The `door` statement
|
|
54
|
+
|
|
55
|
+
Some doors aren't tied to a positioning relation. A standalone **`door`
|
|
56
|
+
statement**, on its own line, adds one.
|
|
57
|
+
|
|
58
|
+
### Between two rooms: `door <a> <b>`
|
|
59
|
+
|
|
60
|
+
Two rooms can share a wall without either being the other's anchor. A
|
|
61
|
+
`door` statement connects them, following the same width and offset
|
|
62
|
+
syntax as [door declarations](#door-declarations):
|
|
63
|
+
|
|
64
|
+
```porta img/door-standalone.svg
|
|
65
|
+
room hall "Hall" 40x20 root
|
|
66
|
+
room east "East" 20x20 down-of hall
|
|
67
|
+
room west "West" 20x20 down-of hall shift=20
|
|
68
|
+
door=10@0 east west
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
<img alt="Two rooms below a hall, joined by a standalone door" src="img/door-standalone.svg" width="70%">
|
|
72
|
+
|
|
73
|
+
`door` statements can also be used to add additional doors to walls that
|
|
74
|
+
already have them, as long as the doors do not overlap:
|
|
75
|
+
|
|
76
|
+
```porta img/door-multi.svg
|
|
77
|
+
room r "Root" 20x20 root
|
|
78
|
+
room a "Room A" 20x20 right-of r door@5
|
|
79
|
+
door@15 r a
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
<img alt="A pair of rooms linked by two doors" src="img/door-multi.svg" width="70%">
|
|
83
|
+
|
|
84
|
+
### To the outside: `door <room> outside <side>`
|
|
85
|
+
|
|
86
|
+
An **external** door opens a room onto the outside, on a named side — `up`,
|
|
87
|
+
`down`, `left`, or `right`:
|
|
88
|
+
|
|
89
|
+
```porta img/door-outside.svg
|
|
90
|
+
room a "Room A" 20x20 root
|
|
91
|
+
room b "Room B" 20x20 right-of a
|
|
92
|
+
door a outside left
|
|
93
|
+
door b outside down
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
<img alt="Two rooms with external doors on their outer walls" src="img/door-outside.svg" width="70%">
|
|
97
|
+
|
|
98
|
+
## Invalid doors
|
|
99
|
+
|
|
100
|
+
`porta` rejects a door it can't place:
|
|
101
|
+
|
|
102
|
+
- An explicit `door` on a relation whose rooms share no wall (they meet only at
|
|
103
|
+
a corner).
|
|
104
|
+
- A door wider than its wall, or pushed past the wall's end by its offset.
|
|
105
|
+
- Two doors that overlap on the same wall.
|
|
106
|
+
- An external door on a side that isn't exterior — a room sits flush there.
|
|
107
|
+
|
|
108
|
+
## Putting it together
|
|
109
|
+
|
|
110
|
+
```porta img/door-capstone.svg
|
|
111
|
+
room hall "Hall" 20x40 root
|
|
112
|
+
room drawing "Drawing Room" 30x40 left-of hall door=20
|
|
113
|
+
room dining "Dining Room" 30x20 right-of hall door@10
|
|
114
|
+
room kitchen "Kitchen" 30x20 right-of hall align=end
|
|
115
|
+
room pantry "Pantry" 10x? right-of dining right-of kitchen
|
|
116
|
+
room porch "Porch" 20x10 down-of hall
|
|
117
|
+
room cloak "Cloakroom" 10x10 down-of drawing left-of porch
|
|
118
|
+
room scullery "Scullery" 15x10 down-of kitchen align=end shift=-5
|
|
119
|
+
room passage "Passage" ?x10 right-of porch left-of scullery
|
|
120
|
+
door=10@5 dining kitchen
|
|
121
|
+
door porch outside down
|
|
122
|
+
door dining outside up
|
|
123
|
+
door drawing outside left
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
<img alt="The manor ground floor with its doors controlled" src="img/door-capstone.svg" width="70%">
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="1268" viewBox="-50 -25 110 126.8">
|
|
2
|
+
<rect x="-50" y="-25" width="110" height="126.8" fill="#e0e0e0" />
|
|
3
|
+
<g stroke="#bbb" stroke-width="0.15">
|
|
4
|
+
<line x1="-15" y1="-15" x2="-15" y2="25" />
|
|
5
|
+
<line x1="-10" y1="-15" x2="-10" y2="25" />
|
|
6
|
+
<line x1="-5" y1="-15" x2="-5" y2="25" />
|
|
7
|
+
<line x1="0" y1="-15" x2="0" y2="25" />
|
|
8
|
+
<line x1="5" y1="-15" x2="5" y2="25" />
|
|
9
|
+
<line x1="10" y1="-15" x2="10" y2="25" />
|
|
10
|
+
<line x1="15" y1="-15" x2="15" y2="25" />
|
|
11
|
+
<line x1="20" y1="-15" x2="20" y2="25" />
|
|
12
|
+
<line x1="25" y1="-15" x2="25" y2="25" />
|
|
13
|
+
<line x1="-15" y1="-15" x2="25" y2="-15" />
|
|
14
|
+
<line x1="-15" y1="-10" x2="25" y2="-10" />
|
|
15
|
+
<line x1="-15" y1="-5" x2="25" y2="-5" />
|
|
16
|
+
<line x1="-15" y1="0" x2="25" y2="0" />
|
|
17
|
+
<line x1="-15" y1="5" x2="25" y2="5" />
|
|
18
|
+
<line x1="-15" y1="10" x2="25" y2="10" />
|
|
19
|
+
<line x1="-15" y1="15" x2="25" y2="15" />
|
|
20
|
+
<line x1="-15" y1="20" x2="25" y2="20" />
|
|
21
|
+
<line x1="-15" y1="25" x2="25" y2="25" />
|
|
22
|
+
</g>
|
|
23
|
+
<rect data-room="a" x="-15" y="0" width="15" height="10" fill="none" stroke="black" stroke-width="0.5" />
|
|
24
|
+
<text data-room="a" x="-7.5" y="5" text-anchor="middle" dominant-baseline="central" font-size="6">A</text>
|
|
25
|
+
<rect data-room="b" x="10" y="0" width="15" height="10" fill="none" stroke="black" stroke-width="0.5" />
|
|
26
|
+
<text data-room="b" x="17.5" y="5" text-anchor="middle" dominant-baseline="central" font-size="6">B</text>
|
|
27
|
+
<rect data-room="c" x="0" y="-15" width="10" height="15" fill="none" stroke="black" stroke-width="0.5" />
|
|
28
|
+
<text data-room="c" x="5" y="-7.5" text-anchor="middle" dominant-baseline="central" font-size="6">C</text>
|
|
29
|
+
<rect data-room="d" x="0" y="10" width="10" height="15" fill="none" stroke="black" stroke-width="0.5" />
|
|
30
|
+
<text data-room="d" x="5" y="17.5" text-anchor="middle" dominant-baseline="central" font-size="6">D</text>
|
|
31
|
+
<rect data-room="r" x="0" y="0" width="10" height="10" fill="none" stroke="black" stroke-width="0.5" />
|
|
32
|
+
<text data-room="r" x="5" y="5" text-anchor="middle" dominant-baseline="central" font-size="6">R</text>
|
|
33
|
+
<line class="door" x1="0" y1="0" x2="0" y2="5" stroke="black" stroke-width="1.5" />
|
|
34
|
+
<line class="door" x1="0" y1="0" x2="5" y2="0" stroke="black" stroke-width="1.5" />
|
|
35
|
+
<line class="door" x1="0" y1="10" x2="5" y2="10" stroke="black" stroke-width="1.5" />
|
|
36
|
+
<line class="door" x1="10" y1="0" x2="10" y2="5" stroke="black" stroke-width="1.5" />
|
|
37
|
+
<text class="scale" x="-40" y="44.2" font-size="6">1 square = 5 ft</text>
|
|
38
|
+
<text class="key" x="-40" y="53.8" font-size="6">A Left room (15x10 ft)</text>
|
|
39
|
+
<text class="key" x="-40" y="63.4" font-size="6">B Right room (15x10 ft)</text>
|
|
40
|
+
<text class="key" x="-40" y="73" font-size="6">C Up room (10x15 ft)</text>
|
|
41
|
+
<text class="key" x="-40" y="82.6" font-size="6">D Down room (10x15 ft)</text>
|
|
42
|
+
<text class="key" x="-40" y="92.2" font-size="6">R Root room (10x10 ft)</text>
|
|
43
|
+
</svg>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="876" viewBox="-45 -10 110 87.6">
|
|
2
|
+
<rect x="-45" y="-10" width="110" height="87.6" fill="#e0e0e0" />
|
|
3
|
+
<g stroke="#bbb" stroke-width="0.15">
|
|
4
|
+
<line x1="-10" y1="0" x2="-10" y2="20" />
|
|
5
|
+
<line x1="-5" y1="0" x2="-5" y2="20" />
|
|
6
|
+
<line x1="0" y1="0" x2="0" y2="20" />
|
|
7
|
+
<line x1="5" y1="0" x2="5" y2="20" />
|
|
8
|
+
<line x1="10" y1="0" x2="10" y2="20" />
|
|
9
|
+
<line x1="15" y1="0" x2="15" y2="20" />
|
|
10
|
+
<line x1="20" y1="0" x2="20" y2="20" />
|
|
11
|
+
<line x1="25" y1="0" x2="25" y2="20" />
|
|
12
|
+
<line x1="30" y1="0" x2="30" y2="20" />
|
|
13
|
+
<line x1="-10" y1="0" x2="30" y2="0" />
|
|
14
|
+
<line x1="-10" y1="5" x2="30" y2="5" />
|
|
15
|
+
<line x1="-10" y1="10" x2="30" y2="10" />
|
|
16
|
+
<line x1="-10" y1="15" x2="30" y2="15" />
|
|
17
|
+
<line x1="-10" y1="20" x2="30" y2="20" />
|
|
18
|
+
</g>
|
|
19
|
+
<rect data-room="a" x="-10" y="0" width="10" height="10" fill="none" stroke="black" stroke-width="0.5" />
|
|
20
|
+
<text data-room="a" x="-5" y="5" text-anchor="middle" dominant-baseline="central" font-size="6">A</text>
|
|
21
|
+
<rect data-room="b" x="20" y="0" width="10" height="10" fill="none" stroke="black" stroke-width="0.5" />
|
|
22
|
+
<text data-room="b" x="25" y="5" text-anchor="middle" dominant-baseline="central" font-size="6">B</text>
|
|
23
|
+
<rect data-room="r" x="0" y="0" width="20" height="20" fill="none" stroke="black" stroke-width="0.5" />
|
|
24
|
+
<text data-room="r" x="10" y="10" text-anchor="middle" dominant-baseline="central" font-size="12">R</text>
|
|
25
|
+
<line class="door" x1="0" y1="0" x2="0" y2="5" stroke="black" stroke-width="1.5" />
|
|
26
|
+
<line class="door" x1="20" y1="0" x2="20" y2="5" stroke="black" stroke-width="1.5" />
|
|
27
|
+
<text class="scale" x="-35" y="39.2" font-size="6">1 square = 5 ft</text>
|
|
28
|
+
<text class="key" x="-35" y="48.8" font-size="6">A Left room (10x10 ft)</text>
|
|
29
|
+
<text class="key" x="-35" y="58.4" font-size="6">B Right room (10x10 ft)</text>
|
|
30
|
+
<text class="key" x="-35" y="68" font-size="6">R Root room (20x20 ft)</text>
|
|
31
|
+
</svg>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="876" viewBox="-45 -10 110 87.6">
|
|
2
|
+
<rect x="-45" y="-10" width="110" height="87.6" fill="#e0e0e0" />
|
|
3
|
+
<g stroke="#bbb" stroke-width="0.15">
|
|
4
|
+
<line x1="-10" y1="0" x2="-10" y2="20" />
|
|
5
|
+
<line x1="-5" y1="0" x2="-5" y2="20" />
|
|
6
|
+
<line x1="0" y1="0" x2="0" y2="20" />
|
|
7
|
+
<line x1="5" y1="0" x2="5" y2="20" />
|
|
8
|
+
<line x1="10" y1="0" x2="10" y2="20" />
|
|
9
|
+
<line x1="15" y1="0" x2="15" y2="20" />
|
|
10
|
+
<line x1="20" y1="0" x2="20" y2="20" />
|
|
11
|
+
<line x1="25" y1="0" x2="25" y2="20" />
|
|
12
|
+
<line x1="30" y1="0" x2="30" y2="20" />
|
|
13
|
+
<line x1="-10" y1="0" x2="30" y2="0" />
|
|
14
|
+
<line x1="-10" y1="5" x2="30" y2="5" />
|
|
15
|
+
<line x1="-10" y1="10" x2="30" y2="10" />
|
|
16
|
+
<line x1="-10" y1="15" x2="30" y2="15" />
|
|
17
|
+
<line x1="-10" y1="20" x2="30" y2="20" />
|
|
18
|
+
</g>
|
|
19
|
+
<rect data-room="a" x="-10" y="10" width="10" height="10" fill="none" stroke="black" stroke-width="0.5" />
|
|
20
|
+
<text data-room="a" x="-5" y="15" text-anchor="middle" dominant-baseline="central" font-size="6">A</text>
|
|
21
|
+
<rect data-room="b" x="20" y="10" width="10" height="10" fill="none" stroke="black" stroke-width="0.5" />
|
|
22
|
+
<text data-room="b" x="25" y="15" text-anchor="middle" dominant-baseline="central" font-size="6">B</text>
|
|
23
|
+
<rect data-room="r" x="0" y="0" width="20" height="20" fill="none" stroke="black" stroke-width="0.5" />
|
|
24
|
+
<text data-room="r" x="10" y="10" text-anchor="middle" dominant-baseline="central" font-size="12">R</text>
|
|
25
|
+
<line class="door" x1="0" y1="10" x2="0" y2="15" stroke="black" stroke-width="1.5" />
|
|
26
|
+
<line class="door" x1="20" y1="10" x2="20" y2="15" stroke="black" stroke-width="1.5" />
|
|
27
|
+
<text class="scale" x="-35" y="39.2" font-size="6">1 square = 5 ft</text>
|
|
28
|
+
<text class="key" x="-35" y="48.8" font-size="6">A Left room (10x10 ft)</text>
|
|
29
|
+
<text class="key" x="-35" y="58.4" font-size="6">B Right room (10x10 ft)</text>
|
|
30
|
+
<text class="key" x="-35" y="68" font-size="6">R Root room (20x20 ft)</text>
|
|
31
|
+
</svg>
|