diff --git a/models/examples/hospital.fpm2 b/models/examples/hospital.fpm2 new file mode 100644 index 0000000..9170915 --- /dev/null +++ b/models/examples/hospital.fpm2 @@ -0,0 +1,159 @@ +// This is a comment +Floor plan: hospital + + // variables + var doorway_width = 2.0 m + var wall_height = 2.5 m + + Space reception: + shape: Polygon points:[ + (-7.0 m, 6.0 m), + (7.0 m, 6.0 m), + (7.0 m, -3.0 m), + (4.0 m, -6.0 m), + (-4.0 m, -6.0 m), + (-7.0 m, -3.0 m) + ] + location: + translation: x: 10.0 m, y: 5.0 m + rotation: 45.0 deg + wrt: world + of: this + walls: + thickness: 0.40 m + height: 3.0 m + features: + Column central_left: + shape: Rectangle width=0.5 m, length=0.5 m + height: 3.0 m + location: + wrt: this + translation: x: -2.5 m + rotation: -35.0 deg + + Column central_right: + shape: Rectangle width=0.5 m, length=0.5 m + height: 3.0 m + location: + wrt: this + translation: x: 2.5 m + rotation: 35.0 deg + + Divider divider_central: + shape: Rectangle width=doorway_width, length=0.2 m + height: 1.0 m + location: + wrt: this + + Divider divider_left: + shape: Rectangle width=2.0 m, length=0.2 m + height: 1.0 m + location: + wrt: this + translation: x: -2.5 m, y: 1.0 m + rotation: 90.0 deg + + Divider divider_right: + shape: Rectangle width=2.0 m, length=0.2 m + height: 1.0 m + location: + wrt: this + translation: x: 2.5 m, y: 1.0 m + rotation: 90.0 deg + + Space hallway: + shape: Rectangle width=5.0 m, length=14.0 m + location: + wrt: reception.walls[0] + of: this.walls[2] + translation: x: 2.0 m + spaced + walls: + thickness: 0.20 m + height: 2.5 m + features: + Column wall_column_1: + shape: Rectangle width=0.5 m, length=0.3 m + height: 2.5 m + location: + wrt: this.walls[3] + translation: x: -2.5 m + Column wall_column_2: + shape: Rectangle width=0.5 m, length=0.3 m + height: 2.5 m + location: + wrt: this.walls[3] + translation: x: 1.0 m + Column wall_column_3: + shape: Rectangle width=0.5 m, length=0.3 m + height: 2.5 m + location: + wrt: this.walls[3] + translation: x: 5.0 m + Column wall_column_4: + shape: Rectangle width=0.5 m, length=0.3 m + height: 2.5 m + location: + wrt: this.walls[3] + translation: x: 7.0 m + + Space room_A: + shape: Rectangle width=3.5 m, length=4.2 m + location: + wrt: hallway.walls[3] + of: this.walls[1] + translation: x: -3.0 m + spaced + + Space room_B: + shape: Rectangle width=3.5 m, length=4.2 m + location: + wrt: hallway.walls[3] + of: this.walls[0] + translation: x: 2.0 m + spaced + + Entryway reception_main: + shape: Rectangle width=2.5 m, height=2.0 m + location: + in: reception.walls[3] + + Entryway reception_hallway: + shape: Rectangle width=4.0 m, height=2.0 m + location: + in: hallway.walls[2] and reception.walls[0] + + Entryway hallway_roomA: + shape: Rectangle width=1.2 m, height=2.0 m + location: + in: room_A.walls[1] and hallway.walls[3] + translation: x: 1.0 m + + Entryway hallway_roomB: + shape: Rectangle width=1.0 m, height=1.8 m + location: + in: room_B.walls[0] and hallway.walls[3] + translation: x: -1.0 m + + Window hallway_window_1: + shape: Rectangle width=3.0 m, height=1.5 m + location: + in: hallway.walls[1] + translation: x: 3.0 m, z: 0.8 m + + Window hallway_window_2: + shape: Rectangle width=3.0 m, height=1.5 m + location: + in: hallway.walls[1] + translation: x: -1.0 m, z: 0.8 m + + Window hallway_window_3: + shape: Circle radius=1.0 m + location: + in: hallway.walls[1] + translation: x: -1.0 m, z: 0.8 m + + Defaults: + walls: + thickness: 0.23 m + height: 2.5 m \ No newline at end of file diff --git a/models/examples/hospital.variation b/models/examples/hospital.variation index ca5e3d8..f682d83 100644 --- a/models/examples/hospital.variation +++ b/models/examples/hospital.variation @@ -1,8 +1,8 @@ -import "hospital.floorplan" +import "hospital.fpm2" hallway: { - location.pose.translation.x : normal(mean=0.0, std=5.0) - wall_thickness : discrete([ + location.transformation.translation.x : normal(mean=0.0, std=5.0) // TODO this doesn't match the format in fpm (location.translation) + defaults.wall.thickness : discrete([ (0.2, 0.14), (0.4, 0.35), (0.4, 0.51) @@ -11,14 +11,17 @@ hallway: { reception_hallway : { shape.width : uniform([2.0, 3.0, 4.0]) } -reception.divider_central: { +reception.divider_left: { // TODO Temporarily changed divider_center until we add semantics for default values height : normal(mean=1.0, std=0.2) - location.pose.rotation : discrete([ + location.transformation.rotation.z : discrete([ (0.8, 0.0), (0.2, 0.20) ]) } room_B: { - location.pose.translation.x : uniform([2.0, 3.0, 4.0, 5.0]) + location.transformation.translation.x : uniform([2.0, 3.0, 4.0, 5.0]) shape.length : uniform([4.2, 5.0, 6.0, 3.0]) -} \ No newline at end of file +} + +doorway_width : normal(mean=1.0, std=0.2) +wall_height : normal(mean=1.0, std=0.2) \ No newline at end of file diff --git a/src/exsce_floorplan/registration.py b/src/exsce_floorplan/registration.py index 91553c0..beaf407 100644 --- a/src/exsce_floorplan/registration.py +++ b/src/exsce_floorplan/registration.py @@ -127,7 +127,7 @@ def exsce_variation_metamodel(): variation_floorplan_gen = GeneratorDesc( language="floorplan-variation", - target="floorplan-v1", + target="floorplan-v2", description="Generate variations of indoor environments from .floorplan models", generator=variation_floorplan_generator, ) diff --git a/src/exsce_floorplan/variation/exsce_variations.py b/src/exsce_floorplan/variation/exsce_variations.py index f7c87a1..6d0552e 100644 --- a/src/exsce_floorplan/variation/exsce_variations.py +++ b/src/exsce_floorplan/variation/exsce_variations.py @@ -1,23 +1,21 @@ import sys, os -from textx import TextXSemanticError, get_location +from operator import attrgetter +import numpy.random as random -dir_path = os.path.dirname(os.path.realpath(__file__)) -sys.path.append(dir_path) +from textx import TextXSemanticError, get_children_of_type, metamodel_for_language +from textx.scoping.tools import get_unique_named_object -import jinja2 -from textx import metamodel_for_language +from textxjinja import textx_jinja_generator -import numpy.random as random +dir_path = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(dir_path) def get_variable_from_fqn(obj, fqn): """Recursive function to get to the object of interest when using a FQN""" - return ( - getattr(obj, fqn[0]) - if len(fqn) == 1 - else get_variable_from_fqn(getattr(obj, fqn[0]), fqn[1:]) - ) + f = attrgetter(fqn) + return f(obj) def new_sample(fp_model, var_model): @@ -25,38 +23,26 @@ def new_sample(fp_model, var_model): # For each variation set for var in var_model.variations: + print("---") + print(var.ref.name) - # If a variable is the target of a distribution, then create a new list of - # attributes with only the variable. Otherwise select the list of attributes - attributes = var.attributes if hasattr(var, "attributes") else [var] + # If it's a variable, sample a new value for it + if var.__class__.__name__ == "VariableRef": + var_obj = get_unique_named_object(fp_model, var.ref.name) + var_obj.value = att.distribution.sample() + continue + + # Otherwise select the of attributes + attributes = get_children_of_type("Attribute", var) - # For each attribute, select the value to set and sample the distribution for att in attributes: - name = var.ref.name - class_name = var.ref.__class__.__name__ - obj = fp_model[ - "{class_name}.{name}".format(class_name=class_name, name=name) - ] - - if hasattr(att, "fqn"): - fqn = att.fqn.split(".") - obj = get_variable_from_fqn(obj, fqn) - - if not hasattr(obj.value, "value"): - raise TextXSemanticError( - "Semantic Error: This attribute is originally set by a variable.", - **get_location(att), - ) - elif ( - obj is fp_model["Default.WallThickness"] - or obj is fp_model["Default.WallHeight"] - ): - raise TextXSemanticError( - "Semantic Error: This attribute must set in the original model.", - **get_location(att), - ) - - obj.value.value = att.distribution.sample() + print("\t", att.fqn) + fp_obj = get_unique_named_object(fp_model, var.ref.name) + var_obj = get_variable_from_fqn(fp_obj, att.fqn) + if var_obj.__class__.__name__ in ["LengthValue", "AngleValue"]: + var_obj.value.value = att.distribution.sample() + elif var_obj.__class__.__name__ in ["Length", "Angle"]: + var_obj.value = att.distribution.sample() def variation_floorplan_generator( @@ -66,44 +52,25 @@ def variation_floorplan_generator( model_folder_path = os.path.dirname(var_model._tx_parser.file_name) fp_model_path = var_model.import_uri.importURI - fp_mm = metamodel_for_language("floorplan-v1") + fp_mm = metamodel_for_language("floorplan-v2") fp_model = fp_mm.model_from_file(os.path.join(model_folder_path, fp_model_path)) - full_path = os.path.realpath(__file__) - _path, filename = os.path.split(full_path) - - path = os.path.join(_path, "templates") - jinja_env = jinja2.Environment( - loader=jinja2.FileSystemLoader(path), trim_blocks=True, lstrip_blocks=True + this_folder = os.path.dirname(__file__) + template_folder = os.path.join( + this_folder, "../templates/__name____seed__.fpm2.jinja" ) - template = jinja_env.get_template("__floorplan_name_____seed__.floorplan.jinja") variations = custom_args["variations"] - output = custom_args["output"] - - fp_model_hashtable = {} - for space in fp_model.spaces: - fp_model_hashtable["Space.{}".format(space.name)] = space - for feature in space.floor_features: - fp_model_hashtable["FloorFeature.{}".format(feature.name)] = feature - for variable in fp_model.variables: - fp_model_hashtable[ - "{type}.{name}".format(type=variable.__class__.__name__, name=variable.name) - ] = variable - for wall_opening in fp_model.wall_openings: - fp_model_hashtable["WallOpening.{}".format(wall_opening.name)] = wall_opening - fp_model_hashtable["Default.WallThickness"] = fp_model.default.wall_thickness - fp_model_hashtable["Default.WallHeight"] = fp_model.default.wall_height for i in range(int(variations)): seed = random.randint(1000, 9999) random.seed(seed) fp_model.seed = seed - new_sample(fp_model_hashtable, var_model) - with open( - os.path.join( - output, "{name}_{seed}.floorplan".format(name=fp_model.name, seed=seed) - ), - "w", - ) as f: - f.write(template.render(fp=fp_model)) + new_sample(fp_model, var_model) + context = dict(trim_blocks=True, lstrip_blocks=True) + context["model"] = fp_model + context["seed"] = seed + context["name"] = fp_model.name + textx_jinja_generator( + template_folder, output_path, context, overwrite=overwrite + ) diff --git a/src/exsce_floorplan/variation/grammar/floorplan_variation.tx b/src/exsce_floorplan/variation/grammar/floorplan_variation.tx index f53b0d7..a27cd7a 100644 --- a/src/exsce_floorplan/variation/grammar/floorplan_variation.tx +++ b/src/exsce_floorplan/variation/grammar/floorplan_variation.tx @@ -1,4 +1,4 @@ -reference floorplan-v1 as f +reference floorplan-v2 as f import distributions Model: