Walls shifts while create from model detection csv

Hi, i try to create pyrevit function to import my model detections into revit by creating walls by x/y min/max detection results. But i get walls shifts especially farther from the insertion point. I verified the calculations and all looks ok… Do you have any idea what could causing the variations?


120cm in draw is 142 pixels in image:

Here is the code:

## Imports
from Autodesk.Revit.UI.Selection import *
from Autodesk.Revit.DB import *
from pyrevit import forms
import csv

## Get revit model
doc = __revit__.ActiveUIDocument.Document

## def / class

### Change string to float in dict
def float_values(trips):
    for key, value in trips.items():
        try:
            if key in ["ymin", "ymax", "xmin", "xmax"]:
                trips[key] = float(value)
        except ValueError:
            continue


## Pick file with csv
try:
    csv_file_path = forms.pick_file(title="Pick file with exported csv")
except:
    forms.alert(title="Program Error", msg="You didn't pick csv file", exitscript=True)

## Ask for measured lenght in jpg image
lenght_real_centimeters = forms.ask_for_string(
    default="Set value",
    prompt="Write what is the real lenght of measure object in centimeters",
    title="Real lenght",
)

lenght_pixels = forms.ask_for_string(
    default="Set value",
    prompt="Write what is the lenght of measure object in pixels",
    title="Pixels lenght",
)

## Calculate scale
try:
    scale = float(lenght_real_centimeters) / float(lenght_pixels)
except ValueError:
    forms.alert(
        title="Program Error",
        msg="You wrote wrong lenght in centimeters or pixels (maybe you used letters?)",
        exitscript=True,
    )
except:
    forms.alert(
        title="Program Error",
        msg="You wrote wrong lenght value in centimeters or pixels",
        exitscript=True,
    )

## Create walls

### Import csv
data_file = []
with open(csv_file_path) as csvfile:
    data = csv.DictReader(csvfile, delimiter=",")
    for row in data:
        data_file.append(row)

### Change strings to float
for dict in data_file:
    float_values(dict)

print(data_file)

## Create walls

### Collect levels and walls types
levels = (
    FilteredElementCollector(doc)
    .OfCategory(BuiltInCategory.OST_Levels)
    .WhereElementIsNotElementType()
    .ToElements()
)
walls = (
    FilteredElementCollector(doc)
    .OfCategory(BuiltInCategory.OST_Walls)
    .WhereElementIsElementType()
    .ToElements()
)

### Ask which wall(s) use
try:
    walls_dict = {Element.Name.GetValue(x): x for x in walls}
    selected_walls = forms.SelectFromList.show(
        walls_dict.keys(),
        title="Select walls to use",
        multiselect=True,
        button_name="Select walls to use",
    )
    if len(selected_walls) == 0:
        raise Exception
except:
    forms.alert(
        title="Program Error",
        msg="You canceled wall choosing or didn't pick anything",
        exitscript=True,
    )

walls_list = []
for wall_name in selected_walls:
    walls_list.append((walls_dict[wall_name], walls_dict[wall_name].Width))

### Ask for level
try:
    levels_dict = {x.Name: x for x in levels}
    selected_level = forms.SelectFromList.show(
        levels_dict.keys(),
        title="Select level",
        multiselect=False,
        button_name="Select level",
    )
    if len(selected_level) == 0:
        raise Exception
except:
    forms.alert(
        title="Program Error",
        msg="You canceled level choosing or didn't pick anything",
        exitscript=True,
    )

### Ask for hight of import walls
height_of_walls = forms.ask_for_string(
    default="Set value",
    prompt="Write height of walls in centimeters",
    title="Walls height",
)
try:
    height_of_walls = float(height_of_walls) / 30.48
except:
    forms.alert(
        title="Program Error",
        msg="You wrote wrong height in centimeters (maybe you used letters?)",
        exitscript=True,
    )

### Collect walls curves
curves_list = []
for dict in data_file:
    a = dict["xmax"] - dict["xmin"]
    b = abs(dict["ymax"] - dict["ymin"])
    if (
        a >= b
    ):  # measure which side is longer this tell us which dimension is lenght and width
        x1 = dict["xmin"]
        x2 = dict["xmax"]
        y1 = dict["ymin"] + (dict["ymax"] - dict["ymin"]) / 2
        y2 = dict["ymin"] + (dict["ymax"] - dict["ymin"]) / 2
        wall_thickness = b * scale
    else:
        x1 = dict["xmin"] + (dict["xmax"] - dict["xmin"]) / 2
        x2 = dict["xmin"] + (dict["xmax"] - dict["xmin"]) / 2
        y1 = dict["ymin"]
        y2 = dict["ymax"]
        wall_thickness = a * scale

    # We must division by 30.48 because we need to translate centimeters to inches
    point_1 = XYZ(
        (x1 * scale) / 30.48,
        (y1 * scale) / 30.48,
        levels_dict[selected_level].Elevation,
    )
    point_2 = XYZ(
        (x2 * scale) / 30.48,
        (y2 * scale) / 30.48,
        levels_dict[selected_level].Elevation,
    )
    # Create and collect revit curves with detected walls thickness
    wall_line = Line.CreateBound(point_1, point_2)
    print("p1 {} p2 {}".format(point_1.ToString, point_2.ToString))
    curves_list.append((wall_line, wall_thickness / 30.48))

### Unzip picked walls with their thickness
walls, walls_thickness = map(list, zip(*walls_list))

### Start placing walls
t = Transaction(doc, "Walls from AECVision - PYLAB")
t.Start()
try:
    #### Iterate throught lines
    for line, thickness in curves_list:
        #### Get closest wall thickness
        wall_index = min(
            range(len(walls_thickness)),
            key=lambda i: abs(walls_thickness[i] - thickness),
        )
        #### Create wall
        Wall.Create(
            doc,
            line,
            walls[wall_index].Id,
            levels_dict[selected_level].Id,
            height_of_walls,
            0,
            False,
            True,
        )
except Exception as e:
    forms.alert(title="Program Error", msg=e, exitscript=True)
t.Commit()

I upload the rvt model with scaled image and csv predictions.WeTransfer - Send Large Files & Share Photos Online - Up to 2GB Free
Thank you for help and suggestions!

Sorry I will write a little bit about my calculation method. So:

  1. Detection bounding boxes on image with xmin xmax ymin ymax coordinations in pixels and save to csv
  2. Upload csv by pyrevit
  3. Measure by inkspace the leght of known dimension on image and calculate “scale”
try:
    scale = float(lenght_real_centimeters) / float(lenght_pixels)
except ValueError:
    forms.alert(
        title="Program Error",
        msg="You wrote wrong lenght in centimeters or pixels (maybe you used letters?)",
        exitscript=True,
    )
except:
    forms.alert(
        title="Program Error",
        msg="You wrote wrong lenght value in centimeters or pixels",
        exitscript=True,
    )
  1. Define wall orientation by comparing “a” and “b” side of bounding box
  2. Create curve from two points calculated in this place
curves_list = []
for dict in data_file:
    a = dict["xmax"] - dict["xmin"]
    b = abs(dict["ymax"] - dict["ymin"])
    if (
        a >= b
    ):  # measure which side is longer this tell us which dimension is lenght and width
        x1 = dict["xmin"]
        x2 = dict["xmax"]
        y1 = dict["ymin"] + (dict["ymax"] - dict["ymin"]) / 2
        y2 = dict["ymin"] + (dict["ymax"] - dict["ymin"]) / 2
        wall_thickness = b * scale
    else:
        x1 = dict["xmin"] + (dict["xmax"] - dict["xmin"]) / 2
        x2 = dict["xmin"] + (dict["xmax"] - dict["xmin"]) / 2
        y1 = dict["ymin"]
        y2 = dict["ymax"]
        wall_thickness = a * scale

    # We must division by 30.48 because we need to translate centimeters to inches
    point_1 = XYZ(
        (x1 * scale) / 30.48,
        (y1 * scale) / 30.48,
        levels_dict[selected_level].Elevation,
    )
    point_2 = XYZ(
        (x2 * scale) / 30.48,
        (y2 * scale) / 30.48,
        levels_dict[selected_level].Elevation,
    )
    # Create and collect revit curves with detected walls thickness
    wall_line = Line.CreateBound(point_1, point_2)
    print("p1 {} p2 {}".format(point_1.ToString, point_2.ToString))
    curves_list.append((wall_line, wall_thickness / 30.48))

I multiply X and Y by scale and divide by 30.48 (because revit works in feet… ). From X1,X2, Y1, Y2 i create curve and place walls. But I still have problem with walls shifts…

Hi @PanPawel_1, great work here!

I didn’t test your code, put I see you’re printing point_1 and point_2; are their coordinates the expected ones? You can manually calculate them using some excel formula and compare the results.
If they are, then there’s something wrong in the wall_line or the Wall.Create method.

That being said, I can suggest some modifications in the code:

  • pyrevit.forms function don’t raise exception on no selection, they simply return an empty string, so you should check the value of the variable instead of use try/except;
  • you could read the csv and transform the numbers in one go: data_file = [float_values(row) for row in data]
  • don’t use dict as variable name, is a reserved word for the dictionary class
  • SelectFromList can accept a list of object and get the text to display with the name_attr argument, so you could use selected_walls = forms.SelectFromList.show(walls, name_attr="Name", ...) and directly get the list of selected walls (again, don’t catch exceptions but simply check if the list is empty with if not selected_walls:). The same applies to levels;
  • Instead of creating the walls_list list of tuples and then separate it, just create wall_thickness = [w.Width for w in selected_walls]. Or you can even drop that list and directly get the matching wall with min(selected_walls, key=lambda w: abs(w.Width - thickness));
  • It’s always best to catch a specific exception to avoid swallowing unpredictable errors; after cleaning up the selections, there’s only the float() function that can error, and it can throw ValueError or TypeError.

The following are my preferences:

  • the DB namespace/module can also be accessed by from pyrevit.revit import DB (pyrevit already takes care of loading Revit assemblies);
  • the doc variable can also be accessed by pyrevit.revit.doc;
  • the Transaction object you get by from pyrevit.revit import Transaction takes care of handling the commit/rollback via a context manager: use with Transaction(): and indent all the operations that needs to be done inside it. it also shows a message in case of errors, so no need to try/except;
  • instead of keep calling dict[key] I would use temporary variables to store xmin, xmax, ymin and ymax, and also use other temporary variables to store the x_delta and y_delta instead of repeating the calculation.
1 Like

Thank you for your suggestion, a lot of great ideas. Ok i will start my investigation :male_detective:

I checked all stages and:

  • detection results in pixels are ok
  • change pixels to XYZ points with multiply by “scale” and divide by 30.48 (cm in one foot) is ok

In conclusion this problem with shifts can be cause by inaccurate measurement of real dimension in pixels…

1 Like