Merge branch 'planner'

This commit is contained in:
Nick Beckley 2026-06-22 19:20:17 +01:00
commit 882a96fd01
49 changed files with 1071 additions and 69 deletions

View File

@ -0,0 +1,50 @@
---
title: Floor Plan Best Practices
category: Floor Plans
tags: floor plans, best practices, writing, continuity, planning spaces
contextKey: floorPlans.bestPractices
micro: Keep Floor Plans focused on story usefulness, not architectural perfection.
quickTitle: Floor Plan best practices
quickSummary: Start rough, model narrative spaces, use hidden routes when they matter, and focus on what the story needs.
screenshot:
---
# Overview
A useful Floor Plan is clear enough to support the story. It does not need to be a perfect building survey.
# Start Rough
Place the important rooms first. Add corridors, gardens, cellars and stairs when they matter. You can refine shapes and transitions later.
# Model Narrative Spaces
Use blocks to represent spaces that matter to the story: where people stand, what they can hear, where an asset is hidden, or how someone gets from one room to another.
Do not over-model every architectural detail. A fireplace, alcove or cupboard only needs its own Location if the story treats it as a meaningful place.
# Use Multiple Blocks For Awkward Shapes
If a room is L-shaped or irregular, use several blocks linked to the same Location. PlotDirector will visually merge the shared internal borders, so the room still reads as one space.
# Corridors Matter
Use corridors, landings and halls as Locations too. They often control movement, sightlines, overheard conversations and who could plausibly arrive in time.
# Secret Routes
Use hidden doors, secret passages and blocked routes when they carry story meaning. They are ideal for mystery houses, backstage areas, old buildings, hidden compartments and reveals.
# Check Vertical Logic
Use the reference floor overlay before adding stairs. It is much easier to line up a staircase early than to fix a broken vertical route after several scenes depend on it.
# Focus On Story Questions
Let the plan answer practical questions:
- who can see or hear whom?
- where are important assets?
- how does a character move through the space?
- what route is hidden from the reader or from other characters?
- does the scene still make sense when viewed from above?

View File

@ -0,0 +1,70 @@
---
title: Creating Floors and Rooms
category: Floor Plans
tags: floor plans, floors, rooms, blocks, grid, coordinates
contextKey: floorPlans.creatingFloorsAndRooms
micro: Add floors and room blocks, then shape them into useful story spaces.
quickTitle: Creating floors and rooms
quickSummary: Build a plan by adding floors, placing location blocks, resizing them and using grid coordinates to keep the layout clear.
screenshot:
---
# Overview
Start a Floor Plan by creating the broad container: a house, office, school or other story location. Then add floors and place blocks for the rooms, corridors, gardens and other spaces that matter.
The plan does not need to be perfect on the first pass. Start rough, make the important rooms clear, and refine the layout as the story asks more of it.
# Floors And Levels
Each floor has a Level value. Level controls vertical order:
- Deep Basement can be `-2`
- Basement can be `-1`
- Ground Floor is usually `0`
- First Floor is usually `1`
- Second Floor is usually `2`
Stairs use Level to find the floor above or below. Negative levels are allowed.
# Grid Size
Grid width controls how many columns the floor has. Grid height controls how many rows it has. Increase them when you need more drawing space.
The visible grid uses friendly coordinates: columns are labelled A, B, C and so on, while rows are labelled 1, 2, 3 and so on. PlotDirector stores coordinates internally in its own way, but you do not need to worry about that.
# Adding Blocks
Use Add block to place a Location on the active floor. You can use an existing child Location or create a new room/location as you add the block.
Choose a block type to give it the right visual role:
- Room for normal rooms
- Corridor for halls and passages
- Open Space for courtyards or large open areas
- Exterior for gardens, drives or outdoor spaces
- Stairs for staircase spaces
- Other for anything that does not fit neatly
Size presets give you a useful starting point. You can resize manually afterwards.
# Moving And Resizing
Drag a block to move it. Use the resize handle to change its width and height. You can also edit the selected block's X, Y, width and height values directly.
X uses the grid column letters. Y uses the grid row numbers. Width and height tell PlotDirector how many grid squares the block occupies.
# Complex Room Shapes
If your room is not a neat rectangle, do not fight the grid. Add another block, link it to the same Location, and place it beside the first one. PlotDirector treats the two blocks as the same room and hides the internal join where they touch.
This is useful for L-shaped drawing rooms, long corridors, irregular lounges, split halls or any room that needs more than one rectangle to read properly.
# Deleting
Deleting a block removes it from the plan. It does not delete the linked Location. Deleting a floor removes the blocks on that floor, so check before confirming.
# Related Guides
- [Reference Floors, Backgrounds and Grid Guides](/Help/Article/floorPlans.referenceFloorsBackgroundsAndGrid)
- [Floor Plan Best Practices](/Help/Article/floorPlans.bestPractices)

View File

@ -0,0 +1,14 @@
---
title: Add Block
category: Floor Plans
tags: floor plans, blocks, locations, fields
contextKey: floorPlans.addBlock
micro: Add a room, corridor, exterior space or other Location block.
quickTitle: Add block
quickSummary: Add blocks to place story Locations on the active floor.
screenshot:
---
# Overview
Use Add block to place a Location on the current floor. A block can represent a room, corridor, garden, stairwell or any other space that matters to the story.

View File

@ -0,0 +1,14 @@
---
title: Add Floor
category: Floor Plans
tags: floor plans, floors, fields
contextKey: floorPlans.addFloor
micro: Add another level to the current Floor Plan.
quickTitle: Add floor
quickSummary: Add floors for basements, ground floors, upper floors, lofts or any other story level that needs mapping.
screenshot:
---
# Overview
Add a floor when the story needs another level of the same building or location. Give it a clear name, a Level value and enough grid space to place the rooms that matter.

View File

@ -0,0 +1,14 @@
---
title: Background Controls
category: Floor Plans
tags: floor plans, background image, opacity, scale, offset, fields
contextKey: floorPlans.fields.backgroundControls
micro: Adjust a background image so it lines up with the grid.
quickTitle: Background controls
quickSummary: Use opacity, scale, offset and lock controls to trace a background image comfortably.
screenshot:
---
# Overview
Opacity makes the image fainter or stronger. Scale changes its size. Offset moves it around the grid. Lock keeps the guide in place once it is aligned.

View File

@ -0,0 +1,14 @@
---
title: Background Image
category: Floor Plans
tags: floor plans, background image, fields
contextKey: floorPlans.fields.backgroundImage
micro: Upload a sketch or scanned plan to trace over.
quickTitle: Background image
quickSummary: Use a sketch, scanned plan or reference image as a guide while drawing blocks.
screenshot:
---
# Overview
Upload a sketch, scanned plan or reference image and use it as a guide while drawing. The image sits behind the grid and blocks.

View File

@ -0,0 +1,14 @@
---
title: Block Type
category: Floor Plans
tags: floor plans, blocks, fields
contextKey: floorPlans.fields.blockType
micro: Choose the visual style for a placed block.
quickTitle: Block type
quickSummary: Use Room for normal rooms, Corridor for halls, Open Space for courtyards and Exterior for outside areas.
screenshot:
---
# Overview
Block type changes the visual style of the block. Use Room for normal rooms, Corridor for halls/passages, Open Space for courtyards, and Exterior for gardens or outside areas.

View File

@ -0,0 +1,14 @@
---
title: Create Transition
category: Floor Plans
tags: floor plans, transitions, fields
contextKey: floorPlans.createTransition
micro: Add a connection between two Locations.
quickTitle: Create transition
quickSummary: Create a transition when two Locations connect by door, window, passage, stairs, ladder or another route.
screenshot:
---
# Overview
Create a transition when two Locations connect. PlotDirector uses the transition type to decide how to filter destination choices and how to draw the marker.

View File

@ -0,0 +1,14 @@
---
title: Delete Block
category: Floor Plans
tags: floor plans, blocks, delete, fields
contextKey: floorPlans.deleteBlock
micro: Remove a placed block without deleting its Location.
quickTitle: Delete block
quickSummary: Deleting a block removes it from the plan, but the linked Location remains in your project.
screenshot:
---
# Overview
Delete block removes the block from the Floor Plan. It does not delete the linked Location from your project.

View File

@ -0,0 +1,14 @@
---
title: Block Width And Height
category: Floor Plans
tags: floor plans, blocks, dimensions, fields
contextKey: floorPlans.fields.dimensions
micro: Set how many grid squares the block occupies.
quickTitle: Width and height
quickSummary: Width and height control how many grid squares a block covers.
screenshot:
---
# Overview
Width and height control how many grid squares the block occupies. Use them for precise adjustments after dragging or resizing.

View File

@ -0,0 +1,14 @@
---
title: Existing Transitions
category: Floor Plans
tags: floor plans, transitions, fields
contextKey: floorPlans.existingTransitions
micro: Review, edit or delete transitions already on this floor.
quickTitle: Existing transitions
quickSummary: Existing transitions are the doors, windows, stairs and passages already created for the active floor.
screenshot:
---
# Overview
Use Existing transitions to review, edit or delete the connections already created on the active floor.

View File

@ -0,0 +1,14 @@
---
title: Floor Name
category: Floor Plans
tags: floor plans, floors, fields
contextKey: floorPlans.fields.floorName
micro: Name the floor in plain story terms.
quickTitle: Floor name
quickSummary: Use names such as Ground Floor, Basement, First Floor or Loft so the floor is easy to recognise.
screenshot:
---
# Overview
Use the name your story team would naturally use. Clear floor names make tabs, selectors and occupancy review easier to follow.

View File

@ -0,0 +1,14 @@
---
title: From Location
category: Floor Plans
tags: floor plans, transitions, locations, fields
contextKey: floorPlans.fields.fromLocation
micro: Choose where the transition begins.
quickTitle: From location
quickSummary: The From Location is the room or space where the transition begins.
screenshot:
---
# Overview
The From Location is the Location where the transition begins.

View File

@ -0,0 +1,14 @@
---
title: Grid Coordinates
category: Floor Plans
tags: floor plans, grid, coordinates, fields
contextKey: floorPlans.fields.gridCoordinates
micro: Show column letters and row numbers on the grid.
quickTitle: Grid coordinates
quickSummary: Coordinates make it easier to line up rooms and edit selected block positions.
screenshot:
---
# Overview
Show grid coordinates when you want visible guides. Columns use letters such as A, B and C. Rows use numbers such as 1, 2 and 3.

View File

@ -0,0 +1,14 @@
---
title: Grid Height
category: Floor Plans
tags: floor plans, grid, fields
contextKey: floorPlans.fields.gridHeight
micro: Set how many rows this floor uses.
quickTitle: Grid height
quickSummary: Taller plans give you more drawing space from top to bottom.
screenshot:
---
# Overview
Grid height is how many grid rows this floor uses. Increase it when the plan needs more room vertically.

View File

@ -0,0 +1,14 @@
---
title: Grid Width
category: Floor Plans
tags: floor plans, grid, fields
contextKey: floorPlans.fields.gridWidth
micro: Set how many columns this floor uses.
quickTitle: Grid width
quickSummary: Wider plans give you more drawing space from left to right.
screenshot:
---
# Overview
Grid width is how many grid columns this floor uses. Increase it when the plan needs more room horizontally.

View File

@ -0,0 +1,14 @@
---
title: Hidden Door
category: Floor Plans
tags: floor plans, hidden door, transitions, fields
contextKey: floorPlans.transitions.hiddenDoor
micro: Mark a door that is not visibly obvious in the story space.
quickTitle: Hidden Door
quickSummary: Hidden doors are useful for concealed access and do not count as normal visible entrances.
screenshot:
---
# Overview
Use Hidden Door for a doorway that exists but is not plainly visible. It helps mystery and reveal logic without treating the room as openly accessible.

View File

@ -0,0 +1,14 @@
---
title: Label Override
category: Floor Plans
tags: floor plans, labels, fields
contextKey: floorPlans.fields.labelOverride
micro: Show a shorter or clearer label on the plan.
quickTitle: Label override
quickSummary: Use a label override when the Location name is too long or you want the block to read differently on the map.
screenshot:
---
# Overview
Use a label override when the Location name is too long or when the map needs a shorter display name. This changes the visible label, not the Location itself.

View File

@ -0,0 +1,14 @@
---
title: Ladder
category: Floor Plans
tags: floor plans, ladder, transitions, fields
contextKey: floorPlans.transitions.ladder
micro: Connect upward locations with a ladder or hatch route.
quickTitle: Ladder
quickSummary: Ladders are vertical movement routes, useful for lofts, roof spaces, shafts and hatches.
screenshot:
---
# Overview
Use Ladder for vertical access such as loft hatches, shafts, roof spaces or maintenance routes. Ladders can connect to higher floors.

View File

@ -0,0 +1,14 @@
---
title: Floor Level
category: Floor Plans
tags: floor plans, floors, level, fields
contextKey: floorPlans.fields.level
micro: Level controls which floors sit above or below each other.
quickTitle: Level
quickSummary: Use 0 for Ground Floor, 1 for First Floor, -1 for Basement and so on. Stairs use this value to find nearby floors.
screenshot:
---
# Overview
Level controls the vertical order of floors. Use `0` for Ground Floor, `1` for First Floor, `-1` for Basement, and so on. Stairs use this value to find the floor above or below.

View File

@ -0,0 +1,14 @@
---
title: Block Location
category: Floor Plans
tags: floor plans, blocks, locations, fields
contextKey: floorPlans.fields.location
micro: Link each block to a story Location.
quickTitle: Location
quickSummary: If you use the same Location for more than one block, PlotDirector treats those blocks as part of the same room.
screenshot:
---
# Overview
Each block is linked to a Location. If you use the same Location for more than one block, PlotDirector treats those blocks as part of the same room.

View File

@ -0,0 +1,14 @@
---
title: Existing Or New Location
category: Floor Plans
tags: floor plans, blocks, locations, fields
contextKey: floorPlans.fields.locationMode
micro: Choose whether this block uses an existing Location or creates a new one.
quickTitle: Existing or new Location
quickSummary: Use an existing Location for a room you already track, or create a new child Location as you add the block.
screenshot:
---
# Overview
Choose an existing Location when the room already exists in your Location list. Create a new room/location when you are sketching the plan and want PlotDirector to add it for you.

View File

@ -0,0 +1,14 @@
---
title: Floor Plan Notes
category: Floor Plans
tags: floor plans, notes, fields
contextKey: floorPlans.fields.notes
micro: Add private planning notes for a block or transition.
quickTitle: Notes
quickSummary: Use notes for reminders about access, reveals, sightlines or story use.
screenshot:
---
# Overview
Use notes for planning details that help revision: locked access, sightlines, important assets, secrets, reveals or why a route matters.

View File

@ -0,0 +1,14 @@
---
title: Previous And Next Scene
category: Floor Plans
tags: floor plans, occupancy, scenes, fields
contextKey: floorPlans.occupancy.navigation
micro: Move through scenes in story order while reviewing occupancy.
quickTitle: Previous and Next Scene
quickSummary: Use these controls to step through the story and watch occupancy change from scene to scene.
screenshot:
---
# Overview
Previous Scene and Next Scene move through the story order. They are useful when checking how characters and assets move over time.

View File

@ -0,0 +1,14 @@
---
title: Occupancy Scene Selector
category: Floor Plans
tags: floor plans, occupancy, scenes, fields
contextKey: floorPlans.occupancy.sceneSelector
micro: Choose which scene to review on the plan.
quickTitle: Scene selector
quickSummary: Choose a scene to see its character and asset placement on the Floor Plan.
screenshot:
---
# Overview
Choose a scene to review who and what is placed on the plan at that point in the story.

View File

@ -0,0 +1,14 @@
---
title: Occupancy Tokens
category: Floor Plans
tags: floor plans, occupancy, characters, assets, fields
contextKey: floorPlans.occupancy.tokens
micro: Tokens show characters and assets placed in the selected scene.
quickTitle: Occupancy tokens
quickSummary: Character and asset tokens show who and what is in each placed Location for the selected scene.
screenshot:
---
# Overview
Tokens show characters and assets placed in the selected scene. Images are used when available; initials appear when no image exists.

View File

@ -0,0 +1,14 @@
---
title: Occupancy Warnings
category: Floor Plans
tags: floor plans, occupancy, warnings, fields
contextKey: floorPlans.occupancy.warnings
micro: Warnings highlight missing or unusual scene placement.
quickTitle: Occupancy warnings
quickSummary: Warnings highlight missing or unusual placement, such as a scene character who has not been assigned to a room.
screenshot:
---
# Overview
Warnings highlight missing or unusual placement, such as a character in the scene who has not been assigned to a room.

View File

@ -0,0 +1,14 @@
---
title: Occupants On Other Floors
category: Floor Plans
tags: floor plans, occupancy, floors, fields
contextKey: floorPlans.occupancy.otherFloors
micro: Some scene occupants may be placed on a different floor.
quickTitle: Occupants on other floors
quickSummary: This note means the selected scene has placed occupants, but some are on another floor.
screenshot:
---
# Overview
This note means the selected scene has occupants placed on another floor. Switch floors to inspect them.

View File

@ -0,0 +1,14 @@
---
title: Position Within Location
category: Floor Plans
tags: floor plans, transitions, stairs, ladders, fields
contextKey: floorPlans.fields.positionWithinLocation
micro: Place stairs or ladders inside a room.
quickTitle: Position within location
quickSummary: Used for stairs and ladders, which sit inside a room rather than on a wall.
screenshot:
---
# Overview
Position Within Location is used for stairs and ladders, which sit inside a room rather than on a wall.

View File

@ -0,0 +1,14 @@
---
title: Reference Floor
category: Floor Plans
tags: floor plans, reference floor, fields
contextKey: floorPlans.fields.referenceFloor
micro: Show the floor above or below as a faint guide.
quickTitle: Reference floor
quickSummary: Use the reference floor to align staircases, corridors and rooms across levels.
screenshot:
---
# Overview
Show the floor above or below as a faint guide. This helps align staircases, corridors and rooms across floors.

View File

@ -0,0 +1,14 @@
---
title: Root Location
category: Floor Plans
tags: floor plans, locations, fields
contextKey: floorPlans.fields.rootLocation
micro: Link this plan or floor to the Location it represents.
quickTitle: Root Location
quickSummary: The root Location is the story place this plan or floor belongs to, such as Calendar House or Ground Floor.
screenshot:
---
# Overview
Root Location connects the plan or floor to your Location list. Blocks added beneath it can become child Locations, keeping the map and story records aligned.

View File

@ -0,0 +1,14 @@
---
title: Secret Passage
category: Floor Plans
tags: floor plans, secret passage, transitions, fields
contextKey: floorPlans.transitions.secretPassage
micro: Connect locations by a concealed or story-reveal route.
quickTitle: Secret Passage
quickSummary: Secret passages can connect locations more widely and do not count as ordinary visible entrances.
screenshot:
---
# Overview
Use Secret Passage for a concealed route between story spaces. It is useful for mystery houses, old buildings, hidden service routes and late reveals.

View File

@ -0,0 +1,14 @@
---
title: Size Preset
category: Floor Plans
tags: floor plans, blocks, size, fields
contextKey: floorPlans.fields.sizePreset
micro: Choose a starting size for a new block.
quickTitle: Size preset
quickSummary: Pick a starting size, then resize the block manually if the room needs a different shape.
screenshot:
---
# Overview
Choose a starting size for the block. You can resize it manually afterwards.

View File

@ -0,0 +1,14 @@
---
title: Stairs Up And Down
category: Floor Plans
tags: floor plans, stairs, transitions, fields
contextKey: floorPlans.transitions.stairs
micro: Connect adjacent floor levels with a staircase.
quickTitle: Stairs Up and Down
quickSummary: Stairs use floor Level values to connect the floor above or below, and markers sit inside a Location.
screenshot:
---
# Overview
Stairs Up and Stairs Down connect adjacent floor levels. PlotDirector uses floor Level values to find the floor above or below.

View File

@ -0,0 +1,14 @@
---
title: Structure Mode
category: Floor Plans
tags: floor plans, structure, fields
contextKey: floorPlans.structureMode
micro: Structure mode is where you edit floors, blocks, transitions and guides.
quickTitle: Structure mode
quickSummary: Use Structure mode to build the plan: add rooms, shape blocks, connect locations and manage floor guides.
screenshot:
---
# Overview
Structure mode is for editing the map itself. Use it when you want to add floors, place rooms, resize blocks, create transitions, upload background sketches or adjust grid settings.

View File

@ -0,0 +1,14 @@
---
title: To Location
category: Floor Plans
tags: floor plans, transitions, locations, fields
contextKey: floorPlans.fields.toLocation
micro: Choose the Location this transition connects to.
quickTitle: To location
quickSummary: PlotDirector filters this list depending on the transition type.
screenshot:
---
# Overview
The To Location is the Location the transition connects to. PlotDirector filters this list depending on the transition type.

View File

@ -0,0 +1,14 @@
---
title: Transition Type
category: Floor Plans
tags: floor plans, transitions, fields
contextKey: floorPlans.fields.transitionType
micro: Choose what kind of connection links the two Locations.
quickTitle: Transition type
quickSummary: Transitions describe how two Locations connect, such as a door, window, staircase or secret passage.
screenshot:
---
# Overview
Transitions describe how two Locations connect, such as a door, window, staircase or secret passage.

View File

@ -0,0 +1,14 @@
---
title: Transitions Toolbox
category: Floor Plans
tags: floor plans, transitions, fields
contextKey: floorPlans.transitions
micro: Create and edit doors, stairs, windows, passages and other connections.
quickTitle: Transitions toolbox
quickSummary: Transitions describe how two Locations connect, such as a door, window, staircase or secret passage.
screenshot:
---
# Overview
Use the Transitions toolbox to create and edit connections between Locations. These connections help the plan show movement, access and hidden routes.

View File

@ -0,0 +1,14 @@
---
title: X Coordinate
category: Floor Plans
tags: floor plans, coordinates, fields
contextKey: floorPlans.fields.xCoordinate
micro: Choose the grid column where the block starts.
quickTitle: X coordinate
quickSummary: The X coordinate matches the column letters shown on the grid.
screenshot:
---
# Overview
The X coordinate is the column where the block starts. These match the letters shown on the grid.

View File

@ -0,0 +1,14 @@
---
title: Y Coordinate
category: Floor Plans
tags: floor plans, coordinates, fields
contextKey: floorPlans.fields.yCoordinate
micro: Choose the grid row where the block starts.
quickTitle: Y coordinate
quickSummary: The Y coordinate matches the row numbers shown on the grid.
screenshot:
---
# Overview
The Y coordinate is the row where the block starts. These match the numbers shown on the grid.

View File

@ -0,0 +1,50 @@
---
title: Occupancy Mode
category: Floor Plans
tags: floor plans, occupancy, scenes, characters, assets, continuity
contextKey: floorPlans.occupancyMode
micro: See where characters and assets are during a selected scene.
quickTitle: Occupancy mode
quickSummary: Occupancy mode shows scene occupants on the plan so you can check who and what is where at a specific story moment.
screenshot:
---
# Overview
Occupancy mode shows where characters and assets are during a selected scene. It turns the Floor Plan into a continuity check: who is in the hall, what is in the study, and which important objects are elsewhere?
Structure mode edits the plan. Occupancy mode views story placement.
# Scene Selection
Choose a scene from the dropdown. Previous Scene and Next Scene move through the story order. When a scene has an initial floor, the editor can switch to that floor to help you start in the right place.
# Tokens
Characters and assets appear as small tokens inside placed Locations. If an image thumbnail exists, PlotDirector uses it. If not, it falls back to initials.
When several occupants are in the same space, tokens stack together. If there are more than can fit clearly, a `+N` indicator shows the extra occupants.
# Other Floors
If the selected scene has occupants on another floor, PlotDirector can show a compact note near the plan. Switch floors to inspect those occupants.
# Occupancy Warnings
Warnings highlight missing or unusual placement, such as:
- a character appears in the scene but has no floor-plan location
- an asset appears in the scene but has no floor-plan location
- an occupant is assigned to a location not placed on this Floor Plan
- an occupied room has no visible entrance
These warnings are there to support revision. They do not block writing.
# Why This Helps
Occupancy mode helps you catch continuity problems before readers do. It is especially useful for ensemble scenes, investigations, locked-room puzzles, hidden assets and stories where movement through a building matters.
# Related Guides
- [Floor Plans Overview](/Help/Article/floorPlans.overview)
- [Transitions, Doors and Entrances](/Help/Article/floorPlans.transitionsAndEntrances)

View File

@ -0,0 +1,50 @@
---
title: Floor Plans Overview
category: Floor Plans
tags: floor plans, locations, continuity, occupancy, planning spaces
contextKey: floorPlans.overview
micro: Use Floor Plans as story-space maps, not technical architectural drawings.
quickTitle: Floor Plans overview
quickSummary: Floor Plans help you map rooms, entrances, stairs, secret routes, characters and assets so spatial continuity stays clear.
screenshot:
---
# Overview
Floor Plans are story-space maps. They help you understand where important rooms, corridors, gardens, staircases, characters, assets and hidden routes sit in relation to each other.
They are not CAD drawings. You do not need exact architectural scale, perfect wall thickness or every cupboard in the building. The aim is to answer practical story questions: Who is where? What can they reach? Which room is above this one? Could someone move from the hall to the garden without being seen?
# When To Use Floor Plans
Use a Floor Plan when a location's layout affects scenes or continuity. A mystery house, school, office, hospital ward, hotel, spaceship, theatre, castle or apartment block may all benefit from a simple map.
Floor Plans are especially useful when:
- characters move between rooms during a scene
- secrets depend on doors, windows, stairs or hidden passages
- assets are hidden, moved or discovered
- different floors need to line up
- you want to check where everyone and everything is at a specific point in the story
# Structure Mode And Occupancy Mode
Structure mode is where you build and edit the plan. You add floors, place room blocks, resize them, connect locations with doors or passages, and use grid guides or background images.
Occupancy mode is where you review a scene. It shows character and asset tokens on the plan so you can see who is in which location at that moment in the story.
# How Floor Plans Connect To Locations
Every block on a Floor Plan links to a Location. That means your map is connected to the same story places used by scenes, characters and assets.
A location can appear as one block or several blocks. If a room is L-shaped, long and thin, or otherwise awkward, you can use multiple blocks linked to the same Location. PlotDirector treats them as one room and hides internal joins where those blocks touch.
# Continuity Value
A good Floor Plan lets you ask, "Where is everyone and everything at this point in the story?" It gives you a visual check before a scene creates an impossible movement, a forgotten character, or a secret route that does not quite make sense.
# Related Guides
- [Creating Floors and Rooms](/Help/Article/floorPlans.creatingFloorsAndRooms)
- [Transitions, Doors and Entrances](/Help/Article/floorPlans.transitionsAndEntrances)
- [Occupancy Mode](/Help/Article/floorPlans.occupancyMode)

View File

@ -0,0 +1,44 @@
---
title: Reference Floors, Backgrounds and Grid Guides
category: Floor Plans
tags: floor plans, reference floor, background image, grid, coordinates
contextKey: floorPlans.referenceFloorsBackgroundsAndGrid
micro: Use grid labels, reference floors and background images to align the plan.
quickTitle: Guides and backgrounds
quickSummary: Grid coordinates, reference overlays and background images help you trace, align and refine your floor plan without overworking it.
screenshot:
---
# Overview
Floor Plan guides help you draw with confidence. They are there to support the story map, not to force architectural precision.
# Grid Coordinates
Grid coordinates label columns A, B, C and rows 1, 2, 3. They make it easier to talk about where a room starts, line up blocks, and move selected blocks without counting squares manually.
# Reference Floor Overlay
The reference floor overlay shows the floor above or below as a faint guide. Use it when you need to check whether rooms, corridors, stairs or shafts line up across floors.
This is especially useful before adding stairs. If the landing above is too far away from the hall below, the plan will make that obvious before the manuscript does.
# Background Image Overlay
You can upload a sketch, scanned plan or reference image and use it as a guide while drawing. The image sits behind the grid and blocks.
Depending on the current floor, you may be able to adjust opacity, scale, offset and lock settings. Lower opacity makes the sketch easier to trace over. Scale and offset help line the image up with the grid. Lock keeps it from getting in the way once it is positioned.
# Suggested Workflow
1. Sketch the rough layout on paper.
2. Scan or photograph it.
3. Upload it as a background.
4. Trace rooms using blocks.
5. Add transitions for doors, stairs and hidden routes.
6. Lock or remove the background when the plan is clear enough.
# Related Guides
- [Creating Floors and Rooms](/Help/Article/floorPlans.creatingFloorsAndRooms)
- [Stairs, Ladders and Floor Levels](/Help/Article/floorPlans.stairsLaddersAndLevels)

View File

@ -0,0 +1,52 @@
---
title: Stairs, Ladders and Floor Levels
category: Floor Plans
tags: floor plans, stairs, ladders, levels, vertical movement
contextKey: floorPlans.stairsLaddersAndLevels
micro: Use Levels, stairs and ladders to make vertical movement believable.
quickTitle: Stairs, ladders and levels
quickSummary: Floor Levels control ordering, stairs link adjacent floors, and ladders can connect upward to higher locations.
screenshot:
---
# Overview
Levels tell PlotDirector which floor is above or below another. Ground Floor is usually `0`, First Floor is `1`, and Basement is `-1`.
This matters for vertical movement. Stairs and ladders are how the plan says that characters can move from one level to another.
# Stairs Up And Stairs Down
Stairs Up connect the current floor to the floor one level above. Stairs Down connect the current floor to the floor one level below.
When possible, PlotDirector treats stairs as one staircase. Adding stairs up creates the matching stairs down on the other floor, and adding stairs down can create the matching stairs up.
Stair markers sit inside the From Location rather than on a wall. This is because a staircase usually belongs to a room, hall or landing rather than being a wall opening.
# Position Within Location
Use Position Within Location to place stairs or ladders inside the room:
- Centre
- North
- South
- East
- West
- Corners
This is especially helpful when a room has more than one vertical route or when you are aligning stairs with the floor above.
# Ladders
Ladders are vertical movement too, but they are less formal than stairs. Use them for loft hatches, shafts, roof spaces, hidden access, maintenance routes or attic entries.
A ladder can connect to a location on any higher level. It does not need same-floor adjacency like a normal door.
# Reference Floor Overlay
Before adding stairs, turn on the reference floor overlay. Showing the floor above or below as a faint guide helps align landings, corridors and stair positions.
# Related Guides
- [Reference Floors, Backgrounds and Grid Guides](/Help/Article/floorPlans.referenceFloorsBackgroundsAndGrid)
- [Transitions, Doors and Entrances](/Help/Article/floorPlans.transitionsAndEntrances)

View File

@ -0,0 +1,64 @@
---
title: Transitions, Doors and Entrances
category: Floor Plans
tags: floor plans, transitions, doors, windows, entrances, hidden rooms
contextKey: floorPlans.transitionsAndEntrances
micro: Connect locations with doors, windows, stairs, passages and secret routes.
quickTitle: Transitions and entrances
quickSummary: Transitions describe how two Locations connect and help PlotDirector understand visible entrances, hidden routes and movement.
screenshot:
---
# Overview
Transitions describe how two Locations connect. They are not separate drawing objects or furniture. A transition says, "these two story spaces are connected in this way."
Examples include doors, windows, archways, stairs, ladders, open passages and secret passages. PlotDirector renders small symbols on the plan so the connection is easy to read.
# Transition Types
Common transition types include:
- Door
- Double Door
- French Door
- Sliding Door
- Window
- Archway
- Exterior Door
- Hidden Door
- Blocked Door
- Open Passage
- Hidden Passage
- Secret Passage
- Stairs Up
- Stairs Down
- Ladder
Unknown or unusual transition types still fall back to a simple marker.
# Smart Filtering
PlotDirector filters destination Locations to keep the list useful:
- normal doors and windows connect adjacent same-floor locations
- open, hidden and secret passages can connect more widely
- Stairs Up and Stairs Down connect neighbouring floor levels
- ladders connect upward locations on higher floors
This keeps common door work quick while still allowing secret-house logic when the story needs it.
# Multiple Transitions
You can have more than one transition between the same two Locations. For example, the Drawing Room and Garden might have a door and two windows. PlotDirector spaces the markers along the shared edge so they do not sit on top of each other.
# Hidden Rooms And Visible Entrances
Rooms with no visible entrance can be highlighted as hidden. This is useful for secret rooms, concealed chambers and mystery-house layouts.
Windows do not count as visible entrances. Hidden doors and secret passages also do not count as visible entrances, because they are not obvious to characters or readers until revealed.
# Related Guides
- [Stairs, Ladders and Floor Levels](/Help/Article/floorPlans.stairsLaddersAndLevels)
- [Floor Plan Best Practices](/Help/Article/floorPlans.bestPractices)

View File

@ -7880,6 +7880,7 @@ public sealed class FloorPlanService(
var warnings = new List<FloorPlanOccupancyWarningViewModel>();
var blocksByLocation = blocks.GroupBy(x => x.LocationID).ToDictionary(x => x.Key, x => x.ToList());
var floors = blocks.Select(x => x.FloorPlanFloorID).Distinct().ToList();
var occupancyRowsByScene = occupancyRows.GroupBy(x => x.SceneID).ToDictionary(x => x.Key, x => x.ToList());
var visibleEntranceLocationIds = transitions
.Where(x => IsVisibleEntranceTransition(x.TransitionType))
.SelectMany(x => new[] { x.FromLocationID, x.ToLocationID })
@ -7888,7 +7889,7 @@ public sealed class FloorPlanService(
foreach (var sceneGroup in participants.GroupBy(x => x.SceneID))
{
var sceneId = sceneGroup.Key;
var sceneRows = occupancyRows.Where(x => x.SceneID == sceneId).ToList();
var sceneRows = occupancyRowsByScene.GetValueOrDefault(sceneId) ?? [];
var occupiedCharacterIds = sceneRows.Where(x => x.CharacterID.HasValue).Select(x => x.CharacterID!.Value).ToHashSet();
var occupiedAssetIds = sceneRows.Where(x => x.StoryAssetID.HasValue).Select(x => x.StoryAssetID!.Value).ToHashSet();

View File

@ -0,0 +1,36 @@
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Scenes_FloorPlanID' AND object_id = OBJECT_ID(N'dbo.Scenes'))
CREATE INDEX IX_Scenes_FloorPlanID ON dbo.Scenes(FloorPlanID, IsArchived, ChapterID, SceneNumber, SortOrder)
INCLUDE (SceneTitle, InitialFloorPlanFloorID);
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Scenes_InitialFloorPlanFloorID' AND object_id = OBJECT_ID(N'dbo.Scenes'))
CREATE INDEX IX_Scenes_InitialFloorPlanFloorID ON dbo.Scenes(InitialFloorPlanFloorID)
WHERE InitialFloorPlanFloorID IS NOT NULL;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneFloorPlanOccupancy_FloorPlanID' AND object_id = OBJECT_ID(N'dbo.SceneFloorPlanOccupancy'))
CREATE INDEX IX_SceneFloorPlanOccupancy_FloorPlanID ON dbo.SceneFloorPlanOccupancy(FloorPlanID, SceneID, LocationID)
INCLUDE (FloorPlanFloorID, CharacterID, StoryAssetID);
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneFloorPlanOccupancy_LocationID' AND object_id = OBJECT_ID(N'dbo.SceneFloorPlanOccupancy'))
CREATE INDEX IX_SceneFloorPlanOccupancy_LocationID ON dbo.SceneFloorPlanOccupancy(LocationID)
INCLUDE (SceneID, FloorPlanID, FloorPlanFloorID, CharacterID, StoryAssetID);
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneFloorPlanOccupancy_CharacterID' AND object_id = OBJECT_ID(N'dbo.SceneFloorPlanOccupancy'))
CREATE INDEX IX_SceneFloorPlanOccupancy_CharacterID ON dbo.SceneFloorPlanOccupancy(CharacterID)
INCLUDE (SceneID, FloorPlanID, LocationID)
WHERE CharacterID IS NOT NULL;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneFloorPlanOccupancy_StoryAssetID' AND object_id = OBJECT_ID(N'dbo.SceneFloorPlanOccupancy'))
CREATE INDEX IX_SceneFloorPlanOccupancy_StoryAssetID ON dbo.SceneFloorPlanOccupancy(StoryAssetID)
INCLUDE (SceneID, FloorPlanID, LocationID)
WHERE StoryAssetID IS NOT NULL;
GO

View File

@ -89,7 +89,7 @@
<div class="page-heading compact">
<div>
<p class="eyebrow">Location floor plan</p>
<h1>@Model.FloorPlan.Name</h1>
<h1>@Model.FloorPlan.Name <help-icon key="floorPlans.overview" /></h1>
</div>
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Back to floor plans</a>
</div>
@ -110,23 +110,23 @@
<h2>No floors yet</h2>
<p>Add a floor to start arranging linked locations.</p>
<div class="floor-plan-tool-card mt-3">
<h3>Add floor</h3>
<h3>Add floor <help-icon key="floorPlans.addFloor" mode="inline" /></h3>
<input asp-for="NewFloor.FloorPlanID" type="hidden" name="FloorPlanID" form="floor-plan-add-floor-form" />
<div class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label">New floor</label>
<label class="form-label">New floor <help-icon key="floorPlans.fields.floorName" mode="inline" /></label>
<input asp-for="NewFloor.Name" class="form-control form-control-sm" name="Name" form="floor-plan-add-floor-form" />
</div>
<div class="col-md-2">
<label class="form-label">Level</label>
<label class="form-label">Level <help-icon key="floorPlans.fields.level" mode="inline" /></label>
<input asp-for="NewFloor.SortOrder" class="form-control form-control-sm" name="SortOrder" min="-10" max="50" step="1" form="floor-plan-add-floor-form" />
</div>
<div class="col-md-2">
<label class="form-label">Width</label>
<label class="form-label">Width <help-icon key="floorPlans.fields.gridWidth" mode="inline" /></label>
<input asp-for="NewFloor.GridWidth" class="form-control form-control-sm" name="GridWidth" min="8" max="80" form="floor-plan-add-floor-form" />
</div>
<div class="col-md-2">
<label class="form-label">Height</label>
<label class="form-label">Height <help-icon key="floorPlans.fields.gridHeight" mode="inline" /></label>
<input asp-for="NewFloor.GridHeight" class="form-control form-control-sm" name="GridHeight" min="8" max="80" form="floor-plan-add-floor-form" />
</div>
<div class="col-md-2">
@ -167,10 +167,10 @@
<div class="floor-plan-toolbar-control-group">
<label class="floor-plan-toolbar-check">
<input type="checkbox" data-grid-coordinates-toggle />
Show grid coordinates
Show grid coordinates <help-icon key="floorPlans.fields.gridCoordinates" mode="inline" />
</label>
<label class="floor-plan-reference-control">
<span class="muted">Reference floor</span>
<span class="muted">Reference floor <help-icon key="floorPlans.fields.referenceFloor" mode="inline" /></span>
<select class="form-select form-select-sm" data-reference-floor-select>
<option value="none">None</option>
<option value="below">Floor below</option>
@ -180,17 +180,18 @@
</div>
<div class="floor-plan-toolbar-action-group">
<span class="floor-plan-save-status" data-save-status>Saved</span>
<help-icon key="floorPlans.structureMode" mode="inline" />
<div class="btn-group btn-group-sm" role="group" aria-label="Floor plan mode">
<button class="btn btn-outline-secondary active" type="button" data-floor-plan-mode="structure">Structure</button>
<button class="btn btn-outline-secondary" type="button" data-floor-plan-mode="occupancy">Occupancy</button>
</div>
<a class="btn btn-outline-secondary btn-sm" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Back to floor plans</a>
<help-icon key="floorPlans.occupancyMode" mode="inline" />
<button class="btn btn-primary btn-sm" type="submit" data-save-layout-button>Save layout</button>
</div>
</div>
<div class="floor-plan-occupancy-scene-row" data-occupancy-controls hidden>
<label class="floor-plan-reference-control">
<span class="muted">Scene</span>
<span class="muted">Scene <help-icon key="floorPlans.occupancy.sceneSelector" mode="inline" /></span>
<select class="form-select form-select-sm" data-occupancy-scene-select>
@foreach (var scene in Model.OccupancyScenes)
{
@ -200,8 +201,10 @@
</label>
<button class="btn btn-outline-secondary btn-sm" type="button" data-occupancy-prev>Previous Scene</button>
<button class="btn btn-outline-secondary btn-sm" type="button" data-occupancy-next>Next Scene</button>
<help-icon key="floorPlans.occupancy.navigation" mode="inline" />
<help-icon key="floorPlans.occupancy.tokens" mode="inline" />
<div class="floor-plan-occupancy-warning-panel" data-occupancy-warning-panel hidden>
<strong>Occupancy Warnings</strong>
<strong>Occupancy Warnings <help-icon key="floorPlans.occupancy.warnings" mode="inline" /></strong>
<ul>
@foreach (var warning in Model.OccupancyWarnings)
{
@ -298,6 +301,7 @@
{
<span class="floor-plan-occupancy-more">+@(tokens.Count - 3)</span>
}
<span class="visually-hidden">Occupancy tokens help: characters and assets placed in this scene.</span>
</div>
}
}
@ -317,7 +321,7 @@
}
else if (floorTokens < totalTokens)
{
<div class="floor-plan-occupancy-message" data-occupancy-message data-occupancy-scene="@scene.SceneID" data-occupancy-floor="@floor.FloorPlanFloorID" hidden>Some occupants are on other floors.</div>
<div class="floor-plan-occupancy-message" data-occupancy-message data-occupancy-scene="@scene.SceneID" data-occupancy-floor="@floor.FloorPlanFloorID" hidden>Some occupants are on other floors. <help-icon key="floorPlans.occupancy.otherFloors" mode="inline" /></div>
}
}
@foreach (var transition in floorTransitions)
@ -353,7 +357,7 @@
</div>
<div class="floor-plan-toolbox-body">
<section class="floor-plan-tool-card active" id="floor-plan-tool-add" role="tabpanel" aria-labelledby="floor-plan-tool-tab-add" data-toolbox-panel="add">
<h2>Add block</h2>
<h2>Add block <help-icon key="floorPlans.addBlock" /></h2>
<p class="muted">Adds a block to <span data-current-floor-name>@activeFloor?.Name</span>. Use a child Location or create a new room under this floor.</p>
<input type="hidden" name="FloorPlanBlockID" value="0" form="floor-plan-add-block-form" />
<input type="hidden" name="FloorPlanFloorID" value="@activeFloor?.FloorPlanFloorID" data-add-block-floor-id form="floor-plan-add-block-form" />
@ -362,11 +366,11 @@
<input type="hidden" name="WidthCells" value="1" data-add-block-width form="floor-plan-add-block-form" />
<input type="hidden" name="HeightCells" value="1" data-add-block-height form="floor-plan-add-block-form" />
<div class="floor-plan-choice-row" role="group" aria-label="Add block location mode">
<label><input type="radio" name="AddBlockLocationMode" value="Existing" checked form="floor-plan-add-block-form" data-add-block-mode /> Use existing location</label>
<label><input type="radio" name="AddBlockLocationMode" value="Create" form="floor-plan-add-block-form" data-add-block-mode /> Create new room/location</label>
<label><input type="radio" name="AddBlockLocationMode" value="Existing" checked form="floor-plan-add-block-form" data-add-block-mode /> Use existing location <help-icon key="floorPlans.fields.locationMode" mode="inline" /></label>
<label><input type="radio" name="AddBlockLocationMode" value="Create" form="floor-plan-add-block-form" data-add-block-mode /> Create new room/location <help-icon key="floorPlans.fields.locationMode" mode="inline" /></label>
</div>
<div class="mb-2" data-existing-location-panel>
<label class="form-label" for="add-block-location">Existing child Location</label>
<label class="form-label" for="add-block-location">Existing child Location <help-icon key="floorPlans.fields.location" mode="inline" /></label>
<select id="add-block-location" class="form-select form-select-sm" name="LocationID" form="floor-plan-add-block-form" data-add-block-location>
<option value="">Choose a child Location</option>
@foreach (var location in Model.Locations)
@ -376,15 +380,15 @@
</select>
</div>
<div class="mb-2 d-none" data-new-location-panel>
<label class="form-label" for="add-block-new-location">New room/location name</label>
<label class="form-label" for="add-block-new-location">New room/location name <help-icon key="floorPlans.fields.location" mode="inline" /></label>
<input id="add-block-new-location" class="form-control form-control-sm" name="NewLocationName" form="floor-plan-add-block-form" />
</div>
<div class="mb-2">
<label class="form-label" for="add-block-type">Block type</label>
<label class="form-label" for="add-block-type">Block type <help-icon key="floorPlans.fields.blockType" mode="inline" /></label>
<select id="add-block-type" class="form-select form-select-sm" name="BlockType" asp-items="Model.BlockTypeOptions" form="floor-plan-add-block-form"></select>
</div>
<div class="mb-2">
<label class="form-label" for="add-block-size-preset">Size preset</label>
<label class="form-label" for="add-block-size-preset">Size preset <help-icon key="floorPlans.fields.sizePreset" mode="inline" /></label>
<select id="add-block-size-preset" class="form-select form-select-sm" data-size-preset>
<option value="1x1">Small Room 1 x 1</option>
<option value="2x1" selected>Medium Room 2 x 1</option>
@ -394,14 +398,14 @@
</select>
</div>
<div class="mb-3">
<label class="form-label" for="add-block-label">Optional label override</label>
<label class="form-label" for="add-block-label">Optional label override <help-icon key="floorPlans.fields.labelOverride" mode="inline" /></label>
<input id="add-block-label" class="form-control form-control-sm" name="LabelOverride" form="floor-plan-add-block-form" />
</div>
<button class="btn btn-primary btn-sm w-100" type="submit" form="floor-plan-add-block-form">Add block</button>
</section>
<section class="floor-plan-tool-card" id="floor-plan-tool-selected" role="tabpanel" aria-labelledby="floor-plan-tool-tab-selected" data-toolbox-panel="selected" hidden>
<h2>Selected block</h2>
<h2>Selected block <help-icon key="floorPlans.addBlock" /></h2>
<p class="muted" data-no-selection>Select a block on the grid to edit its details.</p>
<div class="floor-plan-overlap-warning d-none" data-overlap-warning>Some blocks overlap on this floor.</div>
@ -417,29 +421,39 @@
<select asp-for="Blocks[i].LocationID" asp-items="Model.LocationOptions" class="form-select form-select-sm" data-block-field="location"></select>
</div>
<div class="mb-2">
<label class="form-label">Type</label>
<label class="form-label">Type <help-icon key="floorPlans.fields.blockType" mode="inline" /></label>
<select asp-for="Blocks[i].BlockType" asp-items="Model.BlockTypeOptions" class="form-select form-select-sm" data-block-field="type"></select>
</div>
<div class="row g-2">
<div class="col-6">
<label class="form-label">X</label>
<input asp-for="Blocks[i].X" class="form-control form-control-sm" min="0" max="79" data-block-field="x" />
<label class="form-label">X <help-icon key="floorPlans.fields.xCoordinate" mode="inline" /></label>
<select class="form-select form-select-sm"
id="Blocks_@(i)__X"
name="Blocks[@i].X"
data-block-field="x"
data-coordinate-axis="x"
data-coordinate-value="@Model.Blocks[i].X"></select>
</div>
<div class="col-6">
<label class="form-label">Y</label>
<input asp-for="Blocks[i].Y" class="form-control form-control-sm" min="0" max="79" data-block-field="y" />
<label class="form-label">Y <help-icon key="floorPlans.fields.yCoordinate" mode="inline" /></label>
<select class="form-select form-select-sm"
id="Blocks_@(i)__Y"
name="Blocks[@i].Y"
data-block-field="y"
data-coordinate-axis="y"
data-coordinate-value="@Model.Blocks[i].Y"></select>
</div>
<div class="col-6">
<label class="form-label">Width</label>
<label class="form-label">Width <help-icon key="floorPlans.fields.dimensions" mode="inline" /></label>
<input asp-for="Blocks[i].WidthCells" class="form-control form-control-sm" min="1" max="80" data-block-field="w" />
</div>
<div class="col-6">
<label class="form-label">Height</label>
<label class="form-label">Height <help-icon key="floorPlans.fields.dimensions" mode="inline" /></label>
<input asp-for="Blocks[i].HeightCells" class="form-control form-control-sm" min="1" max="80" data-block-field="h" />
</div>
</div>
<div class="mt-2">
<label class="form-label">Label override</label>
<label class="form-label">Label override <help-icon key="floorPlans.fields.labelOverride" mode="inline" /></label>
<input asp-for="Blocks[i].LabelOverride" class="form-control form-control-sm" />
</div>
<div class="mt-2">
@ -457,7 +471,7 @@
</section>
<section class="floor-plan-tool-card" id="floor-plan-tool-background" role="tabpanel" aria-labelledby="floor-plan-tool-tab-background" data-toolbox-panel="background" hidden>
<h2>Background</h2>
<h2>Background <help-icon key="floorPlans.referenceFloorsBackgroundsAndGrid" /></h2>
<p class="muted">Settings affect only <span data-current-floor-name>@activeFloor?.Name</span>.</p>
@for (var i = 0; i < Model.Floors.Count; i++)
{
@ -468,7 +482,7 @@
{
<p class="muted">Upload a map, sketch, or floor-plan image to trace over. The image sits behind the grid and blocks.</p>
<div class="mb-3">
<label class="form-label" for="background-image-@floor.FloorPlanFloorID">Background image</label>
<label class="form-label" for="background-image-@floor.FloorPlanFloorID">Background image <help-icon key="floorPlans.fields.backgroundImage" mode="inline" /></label>
<input id="background-image-@floor.FloorPlanFloorID"
class="form-control form-control-sm"
type="file"
@ -482,7 +496,7 @@
{
<p class="floor-plan-background-file">Background: <strong>@floor.BackgroundImageOriginalFileName</strong></p>
<div class="mb-2">
<label class="form-label" for="background-opacity-@floor.FloorPlanFloorID">Opacity</label>
<label class="form-label" for="background-opacity-@floor.FloorPlanFloorID">Opacity <help-icon key="floorPlans.fields.backgroundControls" mode="inline" /></label>
<div class="floor-plan-background-range">
<input id="background-opacity-@floor.FloorPlanFloorID"
class="form-range"
@ -500,7 +514,7 @@
</div>
<div class="row g-2">
<div class="col-6">
<label class="form-label" for="background-scale-@floor.FloorPlanFloorID">Scale</label>
<label class="form-label" for="background-scale-@floor.FloorPlanFloorID">Scale <help-icon key="floorPlans.fields.backgroundControls" mode="inline" /></label>
<input id="background-scale-@floor.FloorPlanFloorID"
class="form-control form-control-sm"
type="number"
@ -514,7 +528,7 @@
form="floor-plan-background-settings-@floor.FloorPlanFloorID" />
</div>
<div class="col-6">
<label class="form-label" for="background-offset-x-@floor.FloorPlanFloorID">Offset X</label>
<label class="form-label" for="background-offset-x-@floor.FloorPlanFloorID">Offset X <help-icon key="floorPlans.fields.backgroundControls" mode="inline" /></label>
<input id="background-offset-x-@floor.FloorPlanFloorID"
class="form-control form-control-sm"
type="number"
@ -528,7 +542,7 @@
form="floor-plan-background-settings-@floor.FloorPlanFloorID" />
</div>
<div class="col-6">
<label class="form-label" for="background-offset-y-@floor.FloorPlanFloorID">Offset Y</label>
<label class="form-label" for="background-offset-y-@floor.FloorPlanFloorID">Offset Y <help-icon key="floorPlans.fields.backgroundControls" mode="inline" /></label>
<input id="background-offset-y-@floor.FloorPlanFloorID"
class="form-control form-control-sm"
type="number"
@ -550,7 +564,7 @@
data-background-field="locked"
data-background-floor="@floor.FloorPlanFloorID"
form="floor-plan-background-settings-@floor.FloorPlanFloorID" />
Locked
Locked <help-icon key="floorPlans.fields.backgroundControls" mode="inline" />
</label>
</div>
</div>
@ -564,7 +578,7 @@
</section>
<section class="floor-plan-tool-card" id="floor-plan-tool-transitions" role="tabpanel" aria-labelledby="floor-plan-tool-tab-transitions" data-toolbox-panel="transitions" hidden>
<h2>Transitions</h2>
<h2>Transitions <help-icon key="floorPlans.transitions" /></h2>
<p class="muted">Create doors, archways, stairs, and other movement paths for <span data-current-floor-name>@activeFloor?.Name</span>.</p>
@foreach (var floor in Model.Floors)
{
@ -584,6 +598,7 @@
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#transition-create-@floor.FloorPlanFloorID" aria-expanded="true" aria-controls="transition-create-@floor.FloorPlanFloorID">
Create transition
</button>
<help-icon key="floorPlans.createTransition" mode="inline" />
</h3>
<div id="transition-create-@floor.FloorPlanFloorID" class="accordion-collapse collapse show" aria-labelledby="transition-create-heading-@floor.FloorPlanFloorID" data-bs-parent="#transition-accordion-@floor.FloorPlanFloorID">
<div class="accordion-body">
@ -596,11 +611,17 @@
<input type="hidden" name="FloorPlanTransitionID" value="0" form="floor-plan-transition-add-@floor.FloorPlanFloorID" />
<input type="hidden" name="FloorPlanFloorID" value="@floor.FloorPlanFloorID" form="floor-plan-transition-add-@floor.FloorPlanFloorID" />
<div class="mb-2">
<label class="form-label" for="transition-type-@floor.FloorPlanFloorID">Transition Type</label>
<label class="form-label" for="transition-type-@floor.FloorPlanFloorID">Transition Type <help-icon key="floorPlans.fields.transitionType" mode="inline" /></label>
<select id="transition-type-@floor.FloorPlanFloorID" class="form-select form-select-sm" name="TransitionType" asp-items="Model.TransitionTypeOptions" form="floor-plan-transition-add-@floor.FloorPlanFloorID" data-transition-type-select="@floor.FloorPlanFloorID"></select>
<div class="form-text">
Hidden doors <help-icon key="floorPlans.transitions.hiddenDoor" mode="inline" />
Secret passages <help-icon key="floorPlans.transitions.secretPassage" mode="inline" />
Stairs <help-icon key="floorPlans.transitions.stairs" mode="inline" />
Ladders <help-icon key="floorPlans.transitions.ladder" mode="inline" />
</div>
</div>
<div class="mb-2">
<label class="form-label" for="transition-from-@floor.FloorPlanFloorID">From Location</label>
<label class="form-label" for="transition-from-@floor.FloorPlanFloorID">From Location <help-icon key="floorPlans.fields.fromLocation" mode="inline" /></label>
<select id="transition-from-@floor.FloorPlanFloorID" class="form-select form-select-sm" name="FromLocationID" form="floor-plan-transition-add-@floor.FloorPlanFloorID" data-transition-from-select="@floor.FloorPlanFloorID">
<option value="">Choose a Location</option>
@foreach (var location in floorLocationOptions)
@ -610,7 +631,7 @@
</select>
</div>
<div class="mb-2">
<label class="form-label" for="transition-to-@floor.FloorPlanFloorID">To Location</label>
<label class="form-label" for="transition-to-@floor.FloorPlanFloorID">To Location <help-icon key="floorPlans.fields.toLocation" mode="inline" /></label>
<select id="transition-to-@floor.FloorPlanFloorID" class="form-select form-select-sm" name="ToLocationID" form="floor-plan-transition-add-@floor.FloorPlanFloorID" data-transition-to-select="@floor.FloorPlanFloorID">
<option value="">Choose a From Location first</option>
</select>
@ -618,15 +639,15 @@
<button class="btn btn-link btn-sm px-0 d-none" type="button" data-transition-stair-fallback="@floor.FloorPlanFloorID">Show all locations on adjacent floor</button>
</div>
<div class="mb-2 d-none" data-transition-position-panel="@floor.FloorPlanFloorID">
<label class="form-label" for="transition-position-@floor.FloorPlanFloorID">Position Within Location</label>
<label class="form-label" for="transition-position-@floor.FloorPlanFloorID">Position Within Location <help-icon key="floorPlans.fields.positionWithinLocation" mode="inline" /></label>
<select id="transition-position-@floor.FloorPlanFloorID" class="form-select form-select-sm" name="PositionWithinLocation" asp-items="transitionPositions" form="floor-plan-transition-add-@floor.FloorPlanFloorID"></select>
</div>
<div class="mb-2">
<label class="form-label" for="transition-label-@floor.FloorPlanFloorID">Display Label</label>
<label class="form-label" for="transition-label-@floor.FloorPlanFloorID">Display Label <help-icon key="floorPlans.fields.labelOverride" mode="inline" /></label>
<input id="transition-label-@floor.FloorPlanFloorID" class="form-control form-control-sm" name="DisplayLabel" form="floor-plan-transition-add-@floor.FloorPlanFloorID" />
</div>
<div class="mb-2">
<label class="form-label" for="transition-notes-@floor.FloorPlanFloorID">Notes</label>
<label class="form-label" for="transition-notes-@floor.FloorPlanFloorID">Notes <help-icon key="floorPlans.fields.notes" mode="inline" /></label>
<textarea id="transition-notes-@floor.FloorPlanFloorID" class="form-control form-control-sm" name="Notes" rows="2" form="floor-plan-transition-add-@floor.FloorPlanFloorID"></textarea>
</div>
<label class="floor-plan-checkbox">
@ -643,6 +664,7 @@
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#transition-existing-@floor.FloorPlanFloorID" aria-expanded="false" aria-controls="transition-existing-@floor.FloorPlanFloorID">
Existing transitions
</button>
<help-icon key="floorPlans.existingTransitions" mode="inline" />
</h3>
<div id="transition-existing-@floor.FloorPlanFloorID" class="accordion-collapse collapse" aria-labelledby="transition-existing-heading-@floor.FloorPlanFloorID" data-bs-parent="#transition-accordion-@floor.FloorPlanFloorID">
<div class="accordion-body">
@ -681,33 +703,33 @@
</section>
<section class="floor-plan-tool-card" id="floor-plan-tool-floors" role="tabpanel" aria-labelledby="floor-plan-tool-tab-floors" data-toolbox-panel="floors" hidden>
<h2>Floors</h2>
<h2>Floors <help-icon key="floorPlans.creatingFloorsAndRooms" /></h2>
<div class="floor-plan-tool-subsection">
<h3>Add floor</h3>
<h3>Add floor <help-icon key="floorPlans.addFloor" mode="inline" /></h3>
<input asp-for="NewFloor.FloorPlanID" type="hidden" name="FloorPlanID" form="floor-plan-add-floor-form" />
<div class="mb-2">
<label class="form-label">New floor</label>
<label class="form-label">New floor <help-icon key="floorPlans.fields.floorName" mode="inline" /></label>
<input asp-for="NewFloor.Name" class="form-control form-control-sm" name="Name" form="floor-plan-add-floor-form" />
</div>
<div class="mb-2">
<label class="form-label">Level</label>
<label class="form-label">Level <help-icon key="floorPlans.fields.level" mode="inline" /></label>
<input asp-for="NewFloor.SortOrder" class="form-control form-control-sm" name="SortOrder" min="-10" max="50" step="1" form="floor-plan-add-floor-form" />
<div class="form-text">Used to order floors and later to connect stairs up/down.</div>
</div>
<div class="row g-2">
<div class="col-6">
<label class="form-label">Width</label>
<label class="form-label">Width <help-icon key="floorPlans.fields.gridWidth" mode="inline" /></label>
<input asp-for="NewFloor.GridWidth" class="form-control form-control-sm" name="GridWidth" min="8" max="80" form="floor-plan-add-floor-form" />
</div>
<div class="col-6">
<label class="form-label">Height</label>
<label class="form-label">Height <help-icon key="floorPlans.fields.gridHeight" mode="inline" /></label>
<input asp-for="NewFloor.GridHeight" class="form-control form-control-sm" name="GridHeight" min="8" max="80" form="floor-plan-add-floor-form" />
</div>
</div>
<button class="btn btn-outline-primary btn-sm w-100 mt-2" type="submit" form="floor-plan-add-floor-form">Add floor</button>
</div>
<div class="floor-plan-tool-subsection">
<h3>Current floor</h3>
<h3>Current floor <help-icon key="floorPlans.creatingFloorsAndRooms" mode="inline" /></h3>
@for (var i = 0; i < Model.Floors.Count; i++)
{
var floor = Model.Floors[i];
@ -716,7 +738,7 @@
<input asp-for="Floors[i].FloorPlanFloorID" type="hidden" />
<input asp-for="Floors[i].FloorPlanID" type="hidden" />
<div class="mb-2">
<label class="form-label">Floor name</label>
<label class="form-label">Floor name <help-icon key="floorPlans.fields.floorName" mode="inline" /></label>
<input asp-for="Floors[i].Name" class="form-control form-control-sm" />
</div>
<div class="mb-2">
@ -724,22 +746,22 @@
<textarea asp-for="Floors[i].Description" class="form-control form-control-sm" rows="2"></textarea>
</div>
<div class="mb-2">
<label class="form-label">Root Location</label>
<label class="form-label">Root Location <help-icon key="floorPlans.fields.rootLocation" mode="inline" /></label>
<select asp-for="Floors[i].LocationID" asp-items="Model.LocationOptions" class="form-select form-select-sm" data-floor-location-select="@floor.FloorPlanFloorID"></select>
</div>
<div class="mb-2">
<label class="form-label">Level</label>
<label class="form-label">Level <help-icon key="floorPlans.fields.level" mode="inline" /></label>
<input asp-for="Floors[i].SortOrder" class="form-control form-control-sm" min="-10" max="50" step="1" data-floor-level-input="@floor.FloorPlanFloorID" />
<div class="form-text">Used to order floors and later to connect stairs up/down.</div>
</div>
<p class="muted">Location: @(string.IsNullOrWhiteSpace(floor.LocationPath) ? "A child Location will be created or linked on save." : floor.LocationPath)</p>
<div class="row g-2">
<div class="col-6">
<label class="form-label">Grid width</label>
<label class="form-label">Grid width <help-icon key="floorPlans.fields.gridWidth" mode="inline" /></label>
<input asp-for="Floors[i].GridWidth" class="form-control form-control-sm" min="8" max="80" data-floor-width="@floor.FloorPlanFloorID" />
</div>
<div class="col-6">
<label class="form-label">Grid height</label>
<label class="form-label">Grid height <help-icon key="floorPlans.fields.gridHeight" mode="inline" /></label>
<input asp-for="Floors[i].GridHeight" class="form-control form-control-sm" min="8" max="80" data-floor-height="@floor.FloorPlanFloorID" />
</div>
</div>
@ -1167,6 +1189,23 @@
return label;
}
function populateCoordinateSelect(select, count, labelForValue, selectedValue) {
if (!select) return;
const selected = String(Math.max(0, Math.min(Number(selectedValue || 0), Math.max(count - 1, 0))));
const existingValues = [...select.options].map(option => option.value).join("|");
const nextValues = Array.from({ length: Math.max(count, 1) }, (_, index) => String(index)).join("|");
if (existingValues !== nextValues) {
select.replaceChildren(...Array.from({ length: Math.max(count, 1) }, (_, index) => {
const option = document.createElement("option");
option.value = String(index);
option.textContent = labelForValue(index);
return option;
}));
}
select.value = selected;
select.dataset.coordinateValue = selected;
}
function refreshGridCoordinateLabels() {
const show = gridCoordinatesToggle?.checked === true;
panels.forEach(panel => {
@ -1665,11 +1704,16 @@
const wrap = document.createElement("div");
wrap.className = "col-6";
wrap.innerHTML = `<label class="form-label">${label}</label>`;
const input = document.createElement("input");
input.className = "form-control form-control-sm";
input.type = "number";
input.min = field === "x" || field === "y" ? "0" : "1";
input.max = field === "x" || field === "y" ? "79" : "80";
const input = field === "x" || field === "y" ? document.createElement("select") : document.createElement("input");
input.className = field === "x" || field === "y" ? "form-select form-select-sm" : "form-control form-control-sm";
if (input instanceof HTMLInputElement) {
input.type = "number";
input.min = "1";
input.max = "80";
} else {
input.dataset.coordinateAxis = field;
input.dataset.coordinateValue = value;
}
input.name = `Blocks[${index}].${property}`;
input.value = value;
input.dataset.blockField = field;
@ -1738,6 +1782,7 @@
blocks.push(button);
attachBlockEvents(button);
createBlockEditor(block);
writeBlock(button, readBlock(button));
checkOverlaps();
return button;
}
@ -1818,8 +1863,8 @@
function readBlock(block) {
const fields = fieldsFor(block);
return {
x: Number(fields.x?.value || block.style.getPropertyValue("--x") || 0),
y: Number(fields.y?.value || block.style.getPropertyValue("--y") || 0),
x: Number(fields.x?.value || fields.x?.dataset.coordinateValue || block.style.getPropertyValue("--x") || 0),
y: Number(fields.y?.value || fields.y?.dataset.coordinateValue || block.style.getPropertyValue("--y") || 0),
w: Number(fields.w?.value || block.style.getPropertyValue("--w") || 1),
h: Number(fields.h?.value || block.style.getPropertyValue("--h") || 1)
};
@ -1827,13 +1872,18 @@
function writeBlock(block, values) {
const next = clampBlock(block, values);
const grid = gridFor(block);
const gridWidth = Number(grid?.dataset.gridWidth || 24);
const gridHeight = Number(grid?.dataset.gridHeight || 16);
const maxXOptions = Math.max(gridWidth - next.w + 1, 1);
const maxYOptions = Math.max(gridHeight - next.h + 1, 1);
block.style.setProperty("--x", next.x);
block.style.setProperty("--y", next.y);
block.style.setProperty("--w", next.w);
block.style.setProperty("--h", next.h);
const fields = fieldsFor(block);
if (fields.x) fields.x.value = next.x;
if (fields.y) fields.y.value = next.y;
populateCoordinateSelect(fields.x, maxXOptions, gridColumnLabel, next.x);
populateCoordinateSelect(fields.y, maxYOptions, value => String(value + 1), next.y);
if (fields.w) fields.w.value = next.w;
if (fields.h) fields.h.value = next.h;
scheduleGeometryRefresh();
@ -2467,7 +2517,10 @@
saveLayoutAsync();
});
blocks.forEach(attachBlockEvents);
blocks.forEach(block => {
attachBlockEvents(block);
writeBlock(block, readBlock(block));
});
editor.addEventListener("pointermove", event => {
if (!pointerState) return;

View File

@ -15,7 +15,7 @@
<div class="page-heading compact">
<div>
<p class="eyebrow">Location plans</p>
<h1>@Model.Project.ProjectName</h1>
<h1>@Model.Project.ProjectName <help-icon key="floorPlans.overview" /></h1>
</div>
</div>
@ -25,7 +25,7 @@
}
<section class="edit-panel">
<h2>Create floor plan</h2>
<h2>Create floor plan <help-icon key="floorPlans.creatingFloorsAndRooms" /></h2>
<form asp-action="Create" method="post" class="row g-3">
<input asp-for="NewFloorPlan.ProjectID" type="hidden" name="ProjectID" />
<div class="col-md-4">
@ -37,7 +37,7 @@
<input asp-for="NewFloorPlan.Description" class="form-control" name="Description" />
</div>
<div class="col-md-2">
<label class="form-label">Root Location</label>
<label class="form-label">Root Location <help-icon key="floorPlans.fields.rootLocation" mode="inline" /></label>
<select asp-for="NewFloorPlan.LocationID" asp-items="Model.LocationOptions" class="form-select" name="LocationID"></select>
</div>
<div class="col-md-2 d-flex align-items-end">