Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preflight checks hackathon 2024 #2454

Open
wants to merge 55 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
094b9cc
Create mysuperdupercheck-check.py
jmcouffin Sep 3, 2024
d364f37
Fix Model Checker Attribute Error
tay0thman Oct 9, 2024
6d3b4f5
Create radar_check.py
tay0thman Oct 11, 2024
67480d3
Merge branch 'Preflight-Checks_Hackathon_2024' into develop
jmcouffin Oct 15, 2024
c58a784
Merge pull request #2413 from tay0thman/develop
jmcouffin Oct 15, 2024
466aae9
Merge pull request #2414 from tay0thman/patch-4
jmcouffin Oct 16, 2024
de6ab85
Update radar_check.py - Rev1
tay0thman Oct 16, 2024
c0c2fcf
Merge pull request #2418 from tay0thman/patch-5
jmcouffin Oct 17, 2024
ac74b95
Create CADAUDIT_check.py
nasmovk Oct 18, 2024
13c98b8
Update radar_check.py
tay0thman Oct 18, 2024
f8a7011
Merge pull request #2425 from tay0thman/patch-6
jmcouffin Oct 21, 2024
d351df6
Create Naming_Convention_Check
andreasdraxl Oct 29, 2024
700534c
Update radar_check.py - Rounding Coordinates
tay0thman Oct 30, 2024
2f6f786
Create wall_list.json
andreasdraxl Oct 30, 2024
05b7052
Update and rename Naming_Convention to Naming_Convention_Check.py
andreasdraxl Oct 30, 2024
91ea604
Update Naming_Convention_Check.py
andreasdraxl Oct 30, 2024
3626f35
Update Naming_Convention_Check.py
andreasdraxl Oct 30, 2024
a24cb36
Update Naming_Convention_Check.py
andreasdraxl Oct 30, 2024
7661032
Merge pull request #2442 from tay0thman/patch-8
jmcouffin Nov 1, 2024
b70dccf
Update and rename wall_list.json to extensions/pyRevitTools.extension…
jmcouffin Nov 1, 2024
d6beb74
Rename Naming_Convention_Check.py to walltypes_naming_convention_chec…
jmcouffin Nov 4, 2024
6ef1bea
Rename wall_list.json to walltypes_list.json
jmcouffin Nov 4, 2024
da42ec6
Update walltypes_naming_convention_check.py
jmcouffin Nov 4, 2024
298c1d0
Merge branch 'Preflight-Checks_Hackathon_2024' into patch-2
jmcouffin Nov 4, 2024
a18c4bd
Merge pull request #2453 from jmcouffin/patch-2
jmcouffin Nov 4, 2024
f9d66d8
Rename CADAUDIT_check.py to cad_audit_check.py
jmcouffin Nov 4, 2024
06b79cd
Update cad_audit_check.py
jmcouffin Nov 4, 2024
9fd9c57
Update cad_audit_check.py
jmcouffin Nov 4, 2024
44c4ce4
Merge branch 'Preflight-Checks_Hackathon_2024' into patch-1
jmcouffin Nov 4, 2024
c771c27
Merge pull request #2423 from nasmovk/patch-1
jmcouffin Nov 4, 2024
b5003bd
small fixes to checks
jmcouffin Nov 6, 2024
74d902f
fixed some of the cadaudit check
jmcouffin Nov 6, 2024
e9ec311
Merge branch 'develop' into Preflight-Checks_Hackathon_2024
jmcouffin Nov 6, 2024
99587e5
Update walltypes_naming_convention_check.py
jmcouffin Nov 6, 2024
59bc9bf
Update cad_audit_check.py
jmcouffin Nov 6, 2024
e907472
Update extensions/pyRevitTools.extension/checks/mysuperdupercheck-che…
jmcouffin Nov 7, 2024
fabdad2
Update extensions/pyRevitTools.extension/checks/modelchecker_check.py
jmcouffin Nov 7, 2024
0b9ae71
Update extensions/pyRevitTools.extension/checks/modelchecker_check.py
jmcouffin Nov 7, 2024
192ff7e
Update extensions/pyRevitTools.extension/checks/mysuperdupercheck-che…
jmcouffin Nov 7, 2024
c960997
Update extensions/pyRevitTools.extension/checks/mysuperdupercheck-che…
jmcouffin Nov 7, 2024
32d60a7
Update extensions/pyRevitTools.extension/checks/mysuperdupercheck-che…
jmcouffin Nov 7, 2024
02af5b5
Update extensions/pyRevitTools.extension/checks/mysuperdupercheck-che…
jmcouffin Nov 7, 2024
0bfbfff
Update extensions/pyRevitTools.extension/checks/cad_audit_check.py
jmcouffin Nov 7, 2024
73c0c9e
Update extensions/pyRevitTools.extension/checks/cad_audit_check.py
jmcouffin Nov 7, 2024
b735d2d
Update cad_audit_check.py
jmcouffin Nov 7, 2024
4435761
Update extensions/pyRevitTools.extension/checks/cad_audit_check.py
jmcouffin Nov 7, 2024
9818045
Update extensions/pyRevitTools.extension/checks/cad_audit_check.py
jmcouffin Nov 7, 2024
9300840
Update extensions/pyRevitTools.extension/checks/cad_audit_check.py
jmcouffin Nov 7, 2024
91c8475
Apply suggestions from code review
jmcouffin Nov 7, 2024
26a2707
Apply suggestions from code review
jmcouffin Nov 7, 2024
2718ef9
Update walltypes_naming_convention_check.py
jmcouffin Nov 7, 2024
a537784
Update radar_check.py
tay0thman Nov 17, 2024
89db8fd
Merge pull request #2458 from tay0thman/patch-9
jmcouffin Nov 17, 2024
2f3f7ce
Update radar_check.py
tay0thman Nov 27, 2024
e4cb829
Merge pull request #2467 from tay0thman/patch-10
jmcouffin Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions extensions/pyRevitTools.extension/checks/cad_audit_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# -*- coding: utf-8 -*-

from pyrevit import script, revit, DB, DOCS
from pyrevit.forms import alert
from pyrevit.preflight import PreflightTestCase

from System.Windows import Window # Used for cancel button
from rpw.ui.forms import (FlexForm, Label, Separator, Button) # RPW
from rpw.ui.forms.flexform import RpwControlMixin, Controls # Used to create RadioButton

from pyrevit.coreutils import Timer # Used for timing the check
from datetime import timedelta # Used for timing the check

doc = DOCS.doc
ac_view = doc.ActiveView


def collect_cadinstances(active_view_only):
""" Collect ImportInstance class from whole model or from just active view """
if active_view_only:
cadinstances = DB.FilteredElementCollector(doc, ac_view.Id).OfClass(DB.ImportInstance).WhereElementIsNotElementType().ToElements()
else:
cadinstances = DB.FilteredElementCollector(doc).OfClass(DB.ImportInstance).WhereElementIsNotElementType().ToElements()
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved
if cadinstances:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless check, anyway if there is an empty list, it will not be enumerated.
The rules of good form say that it is better to return an empty list instead of None

return cadinstances
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else not needed since the if branch returns

alert("No CAD instances found in the {}.".format("active view" if active_view_only else "model"), exitscript=True)

# Manage Flexform cancel using .NET System.Windows RoutedEventArgs Class
class ButtonClass(Window): # to handle button event (https://stackoverflow.com/questions/54756424/using-the-on-click-option-for-a-revit-rpw-flexform-button)
@staticmethod
def cancel_clicked(sender, e):
window = Window.GetWindow(sender)
window.close()
script.exit()
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved

# Add radio button functionality to RPW Flexforms
class RadioButton(RpwControlMixin, Controls.RadioButton):
"""
Windows.Controls.RadioButton Wrapper

>>> RadioButton('Label')
"""
def __init__(self, name, radio_text, default=False, **kwargs):
"""
Args:
name (``str``): Name of control. Will be used to return value
radio_text (``str``): RadioButton label Text
default (``bool``): Sets IsChecked state [Default: False]
wpf_params (kwargs): Additional WPF attributes
"""
self.Name = name
self.Content = radio_text
self.IsChecked = default
self.set_attrs(top_offset=0, **kwargs)

@property
def value(self):
return self.IsChecked

def get_cad_site(cad_inst):
""" A CAD's location site cannot be got from the Shared Site parameter
cad_inst.Name returns the site name with a 'location' prefix (language-specific, eg 'emplacement' in French)"""
cad_site_name = cad_inst.Name
cad_site_name.replace("location", "-")
return cad_site_name
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved


def get_user_input():
""" create RPW input FlexForm for user choice of collection mode (coll_mode) whole model or just active view """
flexform_comp = [Label("Audit CAD instances:")]
flexform_comp.append(RadioButton("model", "in this project", True, GroupName="grp")) # GroupName implemented in class through kwargs
flexform_comp.append(RadioButton("active_view", "in only the active view", False, GroupName="grp"))
flexform_comp.append(Separator())
flexform_comp.append(Button("CANCEL", on_click=ButtonClass.cancel_clicked))
flexform_comp.append(Button("OK"))
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved

user_input = FlexForm("Audit of CAD instances in the current project", flexform_comp, Width=500, Height=200) # returns a FlexForm object
user_input.show()
user_input_dict = user_input.values # This is a dictionary
if not bool(user_input_dict): # check if dict not empty (a bool of non-empty dict is True)
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved
script.exit()

return user_input_dict

def get_load_stat(cad, is_link):
""" Loaded status from the import instance's CADLinkType """
cad_type = doc.GetElement(cad.GetTypeId()) # Retreive the type from the instance

if is_link:
exfs = cad_type.GetExternalFileReference()
status = exfs.GetLinkedFileStatus().ToString()
if status == "NotFound":
status = ":cross_mark: NotFound"
elif status == "Unloaded":
status = ":heavy_multiplication_x: Unloaded"
else:
status = ":warning: IMPORTED" # Not an external reference

return status
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved


def checkModel(doc, output):
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved
timer = Timer()
output = script.get_output()
output.close_others()
output.set_title("CAD audit of model '{}'".format(doc.Title))
output.set_width (1700)

coll_mode = get_user_input()["active_view"]

table_data = [] # store array for table formatted output
row_head = ["No", "Select/Zoom", "DWG instance", "Loaded status", "Workplane or single view", "Duplicate", "Workset", "Creator user", "Location site name"] # output table first and last row
cad_instances = collect_cadinstances(coll_mode)
for cad in cad_instances:
count = 0
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved
cad_id = cad.Id
cad_is_link = cad.IsLinked
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used only once, no need for a variable. also, since get_load_stat has cad as the first argument, couldn't just check cad.IsLinked by itself?

cad_name = cad.Parameter[DB.BuiltInParameter.IMPORT_SYMBOL_NAME].AsString()

table_row = []
table_row.append(count+1)
table_row.append(output.linkify(cad_id, title="{}".format("Select")))
table_row.append(cad_name)
table_row.append(get_load_stat(cad, cad_is_link)) # loaded status
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved

# if the instance has an owner view, it was placed on the active view only (bad, so give warning and show the view name)
# if the instance has no owner view, it should have a level or workplane (good)
cad_own_view_id = cad.OwnerViewId
if cad_own_view_id == DB.ElementId.InvalidElementId:
table_row.append(doc.GetElement(cad.LevelId).Name)
else:
cad_own_view_name = doc.GetElement(cad_own_view_id).Name
table_row.append(":warning: view '{}'".format(cad_own_view_name))
Comment on lines +122 to +127
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be extracted to a function that returns the level name or the warning, so that we can continue populating the list instead of use append

table_row.append(":warning:" if cad_name in [row[2] for row in table_data] else "-") # If the name is already in table_data, it is a duplicat (bad)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to manage a separate set with the names for quicker membership checks

seen_names = set()
# ... 
    table_row = [
        # ...
        ":warning:" if cad_name in seen_names else "-",
        # ...
    ]
    seen_names.add(cad_name)

table_row.append(revit.query.get_element_workset(cad).Name) # cad instance workset
table_row.append(DB.WorksharingUtils.GetWorksharingTooltipInfo(revit.doc, cad.Id).Creator) # ID of the user
table_row.append(get_cad_site(cad)) # Extract site name from location
Comment on lines +128 to +131
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those can be added to the list initialization after the refactor above.

also cad.Id -> cad_id so that variable starts to have some sense 😉

table_data.append(table_row)
table_data.append(row_head)
output.print_md("## Preflight audit of imported and linked CAD")
output.print_table(table_data=table_data,
title="",
columns=row_head,
formats=['', '', '', '', '', '', '', '', ''],
last_line_style='background-color:#233749;color:white;font-weight:bold')

# Summary output section:
link_to_view = output.linkify(ac_view.Id, title="Show the view")
print("{} CAD instances found.".format(len(cad_instances))
if coll_mode: # if active view only
summary_msg = "the active view ('{}') {}".format(ac_view.Name, link_to_view)
else:
summary_msg = "the whole model ({})".format(doc.Title)
print("The check was run on {}".format(summary_msg))
output.print_md("##Explanations for warnings :warning:")
print("Loaded status...........: alert if a CAD is imported, rather than linked")
print("Workplane or single view: alert if a CAD is placed on a single view with the option 'active view only' rather than on a model workplane")
print("Duplicate...............: alert if a CAD is placed more than once (whether linked or imported) in case it is unintentinal")

# Display check duration
endtime = timer.get_time()
endtime_hms = str(timedelta(seconds=endtime))
endtime_hms_claim = " \n\nCheck duration " + endtime_hms[0:7] # Remove seconods decimals from string
print(endtime_hms_claim)

class ModelChecker(PreflightTestCase):
"""
Preflight audit of imported and linked CAD
This QC tools returns the following data:
CAD link instance and type information

Quality control includes information and alerts for:
The status of links (Loaded, Not found, Unloaded...)
Whether a CAD is on a model workplane or just a single view
Whether a CAD is linked or imported
Whether CADs have been duplicated
The CAD's shared location name

Feature: The output allows navigating to the found CADs

"""

name = "CAD Audit"
author = "Kevin Salmon"


def startTest(self, doc, output):
checkModel(doc, output)
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 0 additions & 9 deletions extensions/pyRevitTools.extension/checks/grids_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,6 @@ class ModelChecker(PreflightTestCase):
name = "Grids Data Lister"
author = "Jean-Marc Couffin"

def setUp(self, doc, output):
pass

def startTest(self, doc, output):
checkModel(doc, output)


def tearDown(self, doc, output):
pass

def doCleanups(self, doc, output):
pass
9 changes: 0 additions & 9 deletions extensions/pyRevitTools.extension/checks/levels_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,6 @@ class ModelChecker(PreflightTestCase):
name = "Levels Data Lister"
author = "Jean-Marc Couffin"

def setUp(self, doc, output):
pass

def startTest(self, doc, output):
checkModel(doc, output)


def tearDown(self, doc, output):
pass

def doCleanups(self, doc, output):
pass
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: UTF-8 -*-
# pylint: disable=import-error,invalid-name,broad-except,superfluous-parens
import datetime

from pyrevit import coreutils
Expand Down Expand Up @@ -424,8 +423,6 @@ class ModelChecker(PreflightTestCase):
name = "RVTLinks warnings"
author = "Jean-Marc Couffin"

def setUp(self, doc, output):
pass

def startTest(self, doc, output):
timer = coreutils.Timer()
Expand All @@ -434,9 +431,3 @@ def startTest(self, doc, output):
endtime_hms = str(datetime.timedelta(seconds=endtime))
endtime_hms_claim = "Transaction took " + endtime_hms
print (endtime_hms_claim)

def tearDown(self, doc, output):
pass

def doCleanups(self, doc, output):
pass
75 changes: 33 additions & 42 deletions extensions/pyRevitTools.extension/checks/modelchecker_check.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: UTF-8 -*-
#pylint: disable=import-error,invalid-name,broad-except,superfluous-parens
import datetime

from pyrevit import coreutils
Expand Down Expand Up @@ -1177,40 +1176,40 @@ def DocPhases(doc, links = []):
if "Unassigned" not in worksetNames:
worksetNames.append("Unassigned")
graphWorksetsData.append("Unassigned")

# sorting results in chart legend
worksetNames.sort()

worksetsSet = []
for i in worksetNames:
count = graphWorksetsData.count(i)
worksetsSet.append(count)
worksetNames = [x.encode("utf8") for x in worksetNames]

# Worksets OUTPUT print chart only when file is workshared
if len(worksetNames) > 0:
chartWorksets = output.make_doughnut_chart()
chartWorksets.options.title = {
"display": True,
"text": "Element Count by Workset",
"fontSize": 25,
"fontColor": "#000",
"fontStyle": "bold",
}
chartWorksets.data.labels = worksetNames
set_a = chartWorksets.data.new_dataset("Not Standard")
set_a.data = worksetsSet

set_a.backgroundColor = COLORS

worksetsCount = len(worksetNames)
if worksetsCount < 15:
chartWorksets.set_height(100)
elif worksetsCount < 30:
chartWorksets.set_height(160)
else:
chartWorksets.set_height(200)

# print worksetNames
# sorting results in chart legend
worksetNames.sort()

worksetsSet = []
for i in worksetNames:
count = graphWorksetsData.count(i)
worksetsSet.append(count)
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved
worksetNames = [x.encode("utf8") for x in worksetNames]

# Worksets OUTPUT print chart only when file is workshared
if len(worksetNames) > 0:
jmcouffin marked this conversation as resolved.
Show resolved Hide resolved
chartWorksets = output.make_doughnut_chart()
chartWorksets.options.title = {
"display": True,
"text": "Element Count by Workset",
"fontSize": 25,
"fontColor": "#000",
"fontStyle": "bold",
}
chartWorksets.data.labels = worksetNames
set_a = chartWorksets.data.new_dataset("Not Standard")
set_a.data = worksetsSet

set_a.backgroundColor = COLORS

worksetsCount = len(worksetNames)
if worksetsCount < 15:
chartWorksets.set_height(100)
elif worksetsCount < 30:
chartWorksets.set_height(160)
else:
chartWorksets.set_height(200)
chartWorksets.draw()

class ModelChecker(PreflightTestCase):
Expand Down Expand Up @@ -1241,8 +1240,6 @@ class ModelChecker(PreflightTestCase):
name = "Model Checker"
author = "David Vadkerti, Jean-Marc Couffin"

def setUp(self, doc, output):
pass

def startTest(self, doc, output):
timer = coreutils.Timer()
Expand All @@ -1251,9 +1248,3 @@ def startTest(self, doc, output):
endtime_hms = str(datetime.timedelta(seconds=endtime))
endtime_hms_claim = "Transaction took " + endtime_hms
print(endtime_hms_claim)

def tearDown(self, doc, output):
pass

def doCleanups(self, doc, output):
pass
Loading