Background
FloorPlan DSL
The FloorPlan DSL is a language to specify models of indoor environments. It is modelled using textX (a Python-based Xtext-inspired meta-language). It also includes a Variation DSL to generate variations of a base floor plan by sampling a variety of distributions.
A floor plan is composed of the following concepts:
- Spaces are used to model enclosed rooms
- Features such as columns or dividers
- Openings such as windows or entryways
Each of them has a shape. The locations of spaces and features are specified with respect to one another or to a wall. The locations of openings are specified with respect to the wall they are in. A tutorial on how to model a floor plan describes how to model each of these concepts step by step.
Composable model representation
In parallel to the design of this DSL, a modelling approach to specify composable models using JSON-LD was being developed. A more thorough introduction to the composable models can be found here, but for the purposes of this document a brief overview of its relevant aspects follows. The main idea is to use linked data to represent models in JSON. All elements in a model have unique IDs that other elements can reference in their properties. This allows us to model different domains or aspects separately, and later compose those separate specifications into a graph of linked objects (where edges are the references between unique IDs). With this graph representation, it is possible to query and make inferences about the model and its elements, using, for example, Resource Description Framework (RDF) tools (e.g, rdflib ).
However, modelling in JSON-LD is cumbersome, so the FloorPlan DSL became the “front-end” DSL for users, since it is more human-friendly. After a model is specified using the DSLs, a JSON-LD representation (that conforms to the JSON-LD metamodels) can be generated. Note that due to their composability, there is no one true/single schema to check for well-formedness of a particular type of composable model. This makes the generation of the JSON-LD representation challenging, since the JSON-LD metamodels do not describe the structural constraints (the schema to conform to) but only the available types.
The json-ld generator outputs the following files:
.
├── coordinate.json # Coordinates of the positions and poses in spatial_relations.json
├── floorplan.json # Composition of the floorplan and its elements
├── shape.json # Polygons for spaces and walls
├── skeleton.json # Points and frames for all elements
└── spatial_relations.json # Descriptions of positions and poses
scenery_builder
The scenery builder uses the JSON-LD representation of the floorplan and composes it with JSON-LD models for door descriptions, door states and door instances:
- door objects (their shape and kinematic chains)
- states of objects
- door instances (with poses and states attached)
The composed model is used to generate the necessary artefacts for simulation-based testing, e.g. Gazebo world models, door models, ROS launch files, etc. Additionally, it provides three Gazebo plugins to control the initial state of doors at launch time and their dynamic behaviour at run-time.
textX (and differences to Xtext)
Two main differences to Xtext influence the design decisions in the FloorPlan DSLs:
- Static semantics: no constraint language like OCL, but has object and metamodel processors to apply static semantics (using python code for validation/checks).
- No transformation language available. Generators can be written in Python to transform to other representations, or use the Jinja template engine for model-to-text transformations.
For a more detailed comparison, see the comparison to other tools in the textX website.
Jinja
The templating engine has some useful utilities to minimize duplication of code:
- inheritance: templates can extend other templates.
- macros: functions to abstract and reuse pieces of code.
- includes: rendering a template at the location where this tag is invoked.
Motivation for the refactoring of the FloorPlan DSL
- Migrating the generation of the 3D mesh to the scenery_builder required that 3D data would be exported to the JSON-LD representations (see issues below).
- Modelling some floor plan elements for their use with the scenery_builder is currently done directly in JSON-LD. These elements have available JSON-LD models and metamodels (see scenery_builder).
Identified issues with the floorplan-v1
(formerly exsce-floorplan
) DSL
- No 3D-data was included in the generated JSON-LD models (e.g. no height was exported for walls, dividers or columns).
- Inconsistencies in the specification of locations for different elements.
- Syntax is different between different types of elements.
- Some location concepts are defined outside the location model.
- Abstraction levels: Features are defined inside the space model, but openings are defined outside of them, despite the relations between them, e.g. Dividers and Entryways.
- Model transformations (from textX models to JSON-LD models) used a hard-coded “transformation engine” written in Python. This was error-prone and resulted in the following issues:
- No validation of model correctness (JSON-LD metamodels are not used in the transformation).
- Metamodel names in textX do not match JSON-LD metamodels for the same concepts.
- Missing elements in the JSON-LD generation (in addition to 3D data):
- Doors are modelled as “Dividers” but no JSON-LD is generated for them. Features were missing from some JSON-LD exports.
- Entryways and Windows were also not exported.
- The custom classes used to parse the model do not respect separation of concerns. Out of convenience, they mix semantics of the language with the interpretation required for some of the exports.
- They use additional classes which are not part of the textx grammar and models, but have corresponding JSON-LD metamodels and are generated “out of thin air” part of the json-ld models (since they do not exist in the textx model AST).
- Units are part of the syntax but are ultimately ignored (i.e. no checks if angles are specified in radians or degrees).
- Abstraction level was inconsistent for some elements (e.g. AngleInDegrees or MeterVariable).
- Duplicated/repeated models due to lack of modularity (e.g. in
models/examples/
three models ofbrsu_building_c
add or remove features and openings but share the same spaces). - Code principles
- Custom classes also do not have consistent levels of abstraction to match their domain concepts. They perform functions that do not match their level of abstraction or are part of the semantics. As an example,
Frames
have translation and rotation matrices and perform transformation of positions and poses. - Generally, the methods in custom classes don’t follow the principle of single responsibility, and have duplicated code.
- Custom classes also do not have consistent levels of abstraction to match their domain concepts. They perform functions that do not match their level of abstraction or are part of the semantics. As an example,
Goals of the refactoring
- Refactoring the textX metamodels.
- Making the level of abstraction top-down.
- Renaming concepts to align with the JSON-LD ones where applicable.
- Expand language semantics to facilitate the generation of JSON-LD models.
- Custom classes that match the semantics of the concepts in the model and have appropriate levels of abstraction.
- Model-to-text transformations that follow the JSON-LD metamodels. Challenge: There is no transformation language available. textX has ways to register generators but this relies on the custom-written code of the transformation engine. JSON-LD metamodels only include types but no pre-defined schema.
- Systematically generate JSON-LD models to include all textx models.
- Add 3D data to JSON-LD generation.
- Validation of models (e.g. unit consistency).
- Creating new textX metamodels for the new doors.
- Modularity of the models to enable their reuse.
Implementation
The structure of the repository was completely reorganized for v2. With the exception of some code optimization/deduplication, most of the original version of the floorplan is still in this repository to enable the v1-v2 conversion and to validate that v2 can produce the same json-ld representation as v1. Once the latter has been successfully completed, most likely only the v1 grammar will be kept (to allow for v1-v2 conversion), and eventually removed once v1 can be fully deprecated. The repository is currently organized as follows:
.
├── models
│ └── examples # Mainly contains models from v1
│ ├── ...
│ ├── hbrs.floorplan
│ ├── hbrs.fpm2 # Example of converted model from v1 to v2
│ ├── hbrs.variation # Updated for v2
│ ├── hospital.floorplan
│ ├── hospital.fpm2 # Manually specified to demonstrate model optimization
│ ├── hospital.variation # Updated for v2
│ ├── kitchen.floorplan
│ ├── kitchen.fpm2 # Example of converted model from v1 to v2
│ ├── kitchen.variation # Updated for v2
│ └── ...
├── src
│ └── floorplan_dsl
│ ├── classes # Custom classes used for parsing into Python AST
│ │ ├── fpm1
│ │ │ ├── __init__.py
│ │ │ ├── floor_feature.py
│ │ │ ├── geometry.py # Frame (not explicitly modelled)
│ │ │ ├── helpers.py
│ │ │ ├── polytope.py
│ │ │ ├── position.py
│ │ │ ├── space.py
│ │ │ ├── wall.py # Not explicitly modelled
│ │ │ └── wall_opening.py
│ │ ├── fpm2
│ │ │ ├── __init__.py
│ │ │ ├── floorplan.py
│ │ │ ├── geometry.py
│ │ │ ├── qudt.py
│ │ │ └── variables.py
│ │ ├── variation
│ │ │ ├── __init__.py
│ │ │ └── distribution.py
│ │ └── __init__.py
│ ├── generators # Code to read templates and pass the model to jinja
│ │ ├── __init__.py
│ │ ├── fpm.py # v1-v2 conversion, json-ld
│ │ └── variations.py # variations
│ ├── grammar
│ │ ├── fpm1 # The original grammar of v1
│ │ │ ├── floorplan.tx
│ │ │ ├── geometry.tx
│ │ │ └── polytope.tx
│ │ ├── fpm2 # The new grammar added in v2
│ │ │ ├── floorplan.tx
│ │ │ ├── geometry.tx
│ │ │ ├── units.tx
│ │ │ ├── variables.tx
│ │ │ └── walls.tx
│ │ └── variation # Minor updates from v1 to v2
│ │ ├── distributions.tx
│ │ └── floorplan_variation.tx
│ ├── processors
│ │ ├── semantics # Static semantics in v2: model refinement
│ │ │ ├── __init__.py
│ │ │ └── fpm2.py
│ │ ├── validation # Validation of constraints
│ │ │ ├── __init__.py
│ │ │ ├── fpm1.py
│ │ │ ├── fpm2.py
│ │ │ └── variation.py
│ │ └── __init__.py
│ ├── scoping # Reference resolution
│ │ ├── __init__.py
│ │ └── fpm2.py
│ ├── templates # Jinja2 templates
│ │ ├── fpm2 # For floorplan v2 models (v1-v2 conversion and variations)
│ │ └── json-ld # For the generation of JSON-LD representations
│ │ ├── floorplan
│ │ ├── elements # floor plan concepts matching json-ld metamodels
│ │ │ └── structural-entities # Templates extending base concepts in the geometry domain for the floor plan concepts
│ │ └── geometry # Base concepts in json-ld metamodels
│ │ ├── coordinate
│ │ ├── spatial-relations
│ │ └── structural-entities
│ └── registration.py # Registration of the languages and generators
└── test.sh # Tests the parsing, conversion from v1 to v2, and generation of variations and json-ld models
Refactoring of the textx metamodels
[!NOTE] Status: Completed
The re-design of the grammar considered the requirements in the concrete syntax (what users can specify) separately from the abstract syntax that is needed to instantiate the floorplan (e.g. walls), and incorporated this as static semantics. The v1
version focused mainly on the syntax of the and interpreted the model before the generation of the various artefacts. We decided to include the semantic elements as optional in the syntax rules in v2
to allow for the full specification the model if desired, while treatng the required syntax as syntactic sugar (i.e., as a short hand to make it easier to specify and read floor plan models).
The differences in syntax between v1
and v2
can be observed by comparing hospital.floorplan with hospital.fpm2.
- Model optimization: reduced model size by making unnecessary statements in the concrete syntax optional (e.g. translations and rotations of zero).
-translation: x:1.0 m, y:0.0 m +translation: x: 1.0 m -rotation: 0.0 deg
- Made the specification of locations of elements consistent. This required a small change in syntax. ```diff Space hallway: … location:
- from: reception.walls[0]
- to: this.walls[2]
- pose:
- translation: x:2.0 m, y:0.0 m
- rotation: 0.0 deg
- wrt: reception.walls[0]
- of: this.walls[2]
- translation: x: 2.0 m spaced features: Column wall_column_1: shape: Rectangle width=0.5 m, length=0.3 m height: 2.5 m
- from: this.walls[3]
- pose:
- translation: x:-2.5 m, y:0.0 m
- rotation: 0.0 deg
- location:
- wrt: this.walls[3]
- translation: x: -2.5 m Entryway hallway_roomA:
- in: room_A.walls[1] and hallway.walls[3] shape: Rectangle width=1.2 m, height=2.0 m
- pose:
- translation: x: 1.0 m, y: 0.0 m, z: 0.0 m
- rotation: 0.0 deg
- location:
- in: room_A.walls[1] and hallway.walls[3]
- translation: x: 1.0 m ```
- Added metamodels in the grammar to better support the semantics of the geometry domain. This required considerable changes to the static semantics to better-align the textx grammar to the JSON-LD metamodels. This change also facilitates the generation of JSON-LD models, as most concepts are now present in the AST after parsing. Examples of this include rules for: Frames, Points, Position and PoseCoordinates.
- Although Frames were part of the interpretation of the models in
v1
, they had nothing to do with the JSON-LD representation. Inv2
we added rules for them:Point: 'point:' name=ID ; Frame: 'frame:' name=ID origin=Point? ;
Which are then converted in the model-to-text transformation (discussed later).
- Although Frames were part of the interpretation of the models in
- Used textX’s custom classes to do model refinement. The addition of the rules necessary for the JSON-LD representation gives us a lower-level model from a more abstract specification. We expand the syntactic sugar in two places: the custom classes and the processors. Examples include: using the space’s shape to instantiate its walls without having to model them explicitly, adding a shape to each wall, adding frames and points to all elements, applying/propagating default values like wall height to model properties, and updating the location from space and wall references to the frame that is meant. As an example, the latter was achieved by including the
ReferenceFrame
rule in all locations’of
andwrt
rules:SpaceOfReference: WallFrame|SpaceFrame|ReferenceFrame ;
and using an object processor to translate
room_1.walls[0]
to the frame of that wall:if textx_isinstance(self.location.of, mm["WallFrame"]): self.location.of = self.location.of.space.walls[self.location.of.wall_idx].frame elif textx_isinstance(self.location.of, mm["SpaceFrame"]): self.location.of = self.location.of.space.frame else: raise TextXSemanticError("Can't find 'of' frame for space {}".format(self.name))
-
Updated the variation DSL and its generator to work with the new syntax.
-
In the generator implementation, the object processors that expand the semantics are disabled. This is required because rather than the precise meaning of the model, the variations are applied directly on the syntax elements, for example:
Defaults: walls: thickness: 0.23 m height: 2.5 m Space room_1: shape: Rectangle width = 1.0m length= 0.8m location: ...
The
room_1.wall.thickness
is not specified. The semantics point the room’s wall thickness to that of the globalDefaults.walls.thickness
. If we want to vary the room’s thickness, we don’t want to modify the (shared) global defaults, but rather the values specified in the model.
-
- Converted units internally to meters and radians for consistency.
Modularization of the grammar is discussed below.
Adding concepts for the 3D-representation
- Add new concepts to grammar
- Expand semantics to generate faces for walls, features and openings
Model-to-text transformation
[!NOTE] Status: Paused
The only remaining point is a last-minute addition to modularize the json-ld representations
- Using jinja2, we create templates for each (relevant) element type in the JSON-LD metamodels, in an attempt to use them as transformation rules (that call or extend the other more atomic element-level rules).
- The transformation engine takes the textX model and applies the pre-defined Jinja2 templates. These templates take advantage of the Jinja facilities discussed in Jinja. We defined templates for each concept in the JSON-LD metamodels. The structure and inheritance of the json-ld templates is shown below:
- Base templates: The
base.json
file has the empty structure of a JSON-LD document. Thefloorplan.json
defines the loops and reusable blocks for transforming the floorplan model AST. - Templates used by jinja: These are the templates that are loaded by the generator. The jinja generator keeps the file name and changes the extension to
.fpm2
. Each templateincludes
the relevant templates for the concepts for each of the five JSON-LD models (see composable model representation). -
Base JSON-LD representations: Each concept has a template (that other templates can extend to minimize repetition). For example, a frame template matches what we specified in the grammar rule that we discussed the in refactoring:
{ "@id": "floorplan:{% block frame_id %}{{ frame.name }}{% endblock %}", "@type": "Frame", "origin": "floorplan:{% block origin %}{{ frame.origin.name }}{% endblock %}" }
In
skeleton.fpm2.jinja
, we just define the frame of the element we’re currently converting, e.g. to define the origin point and the frame of a space:{% set frame = space.frame %} {% include "./floorplan/structural-entities/origin.json" with context %}, {% include "./geometry/structural-entities/frame.json" with context %},
- Base templates: The
- The transformation engine takes the textX model and applies the pre-defined Jinja2 templates. These templates take advantage of the Jinja facilities discussed in Jinja. We defined templates for each concept in the JSON-LD metamodels. The structure and inheritance of the json-ld templates is shown below:
-
Model-to-model transformation from
v1
tov2
was also implemented with jinja2. How the jinja templates are organized is shown below:
__name__.fpm2.jinja
is used by thev1->v2
converter and is based on the templates for the variation generation ofv1
. Jinja will replace__name__
with thename
variable passed in its context, which is the filename in our case. For example:# Generates a hospital.fpm2 file (converting from v1 to v2) textx generate models/examples/hospital.floorplan --target floorplan-v2
__name_____seed__.fpm2.jinja
is used by the variation generator and was rewritten forv2
.__seed__
is replaced by the random seed used for the sampling.
- Add 3D representations to the JSON-LD generation.
- Update JSON-LD templates with new points, CoordinatePositions.
- Add new concept: Polyhedron
- Add options in generation to convert units
- rad->deg
- m->cm
- Modularize the JSON-LD models to maximize their reusability
Validation
[!NOTE] Status: Paused
Only the two constraints still need to be implemented, but they require the interpretation of the location wrt to the world frame.
-
Verify that right-hand side (RHS) variable assignments match the units of the left-hand side (LHS) (validate type matches).
- This was implemented with a processor in order to provide a more human-friendly message for the error. In the example below we try to assign an AngleVariable to a Rectangle that expects values in meters.
var doorway_width = 1.0 deg ... Divider door_doorway_01: shape: Rectangle width=0.1 m, length=doorway_width
When the constraints were implemented as part of the syntax, an AngleVariable rule would be matched instead of a LengthVariable, and the error would be a failure to resolve the reference of
doorway_width
to a LengthVariable:Error: /Users/argen/100 Projects/floorplan/dsl/models/examples/brsu_building_c_with_doors.fpm2:93:54: Unknown class doorway_width of class LengthVariable
Implementing the validation of constraints in the processor, we can check the metamodel for the expected units and what the variable’s units are:
Error: /Users/argen/100 Projects/floorplan/dsl/models/examples/brsu_building_c_with_doors.fpm2:93:54: Expected an assignment in m. doorway_width has deg units
- Verify the constraints in the directions that each element is allowed to be rotated and translated. The following constraints were implemented with a processor, as that allowed the reuse of the PointCoordinate/PositionVector and EulerAngle rules. It also allows us to check if one of the forbidden directions is specified but has a value of zero.
- Only rotation in
y
for openings - Only translation in
x
andz
for openings - Only rotation in
z
for spaces, walls and features - Only translation in
x
andy
for spaces, walls and features
- Only rotation in
- Verify that the shape definitions match the allowed keywords as described in the concepts, e.g. Rectangles should have
height
and notlength
for Openings. Similarly to the point above, the implementation was done through a processor to allow for the reuse of rules/models. We also validate that rectangles have non-zero positive values. - Verify that openings have max. 2 wall frames as reference.
- Max. 2 walls and min. 1: This is implemented through the syntax. Only the first two walls will be matched by the rule, and one wall is required.
- For contiguous walls: min 2 wall frames are required. This most likely requires additional semantics and refinement of the models to recognize contiguous walls.
- Verify there are no opening shapes that go below the floor. This requires the implementation of the
not aligned
andspaced
semantics, and the pose transformations wrt to the world frame. - If a space is located wrt the world frame, its location must not have the
spaced
flag - When the
spaced
flag is present, there should be no translation iny
- When the
aligned
flag is present (True
by default), there should be no rotation inz
Adding door models
[!NOTE] Status: Planned
This part of the refactoring has not started yet
- Add new way to specify doors with kinematic chains (see Industry Foundation Classes (IFC) models used to represent the as reference).
- Specificying the location of doors should also be possible wrt to a doorway. Currently dividers are specified wrt to spaces.
Modularity and model reuse
[!NOTE] Status: Paused
Initially each floor plan element had its own grammar, but this made the syntax cumbersome (see points below). Most elements were moved back into a single file (
floorplan.tx
) until other parts of the refactoring are finished.
Two options (not necessarily mutually exclusive):
- Modularize the grammars to allow the reuse of model specifications (i.e., allow models to import other models).
- Challenges: Limitations on what textx supports for importing grammars (circular references in space and feature locations).
- Separate the specification of the element (space, feature, opening) separately from its location.