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 30 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 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
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
199 changes: 199 additions & 0 deletions extensions/pyRevitTools.extension/checks/cad_audit_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-

# Import Reference Modules
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


# Definitions

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()

if len(cadinstances) >0:
return cadinstances, len(cadinstances)
else:
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()

# 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


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"))

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)
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

timer = Timer()
def checkModel(doc, output):
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

for inc, cad in enumerate(collect_cadinstances(coll_mode)[0]):
cad_id = cad.Id
cad_is_link = cad.IsLinked
cad_name = cad.Parameter[DB.BuiltInParameter.IMPORT_SYMBOL_NAME].AsString()

table_row = []
table_row.append(inc+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

# 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))
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)
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
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(collect_cadinstances(coll_mode)[1]))
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 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
79 changes: 42 additions & 37 deletions extensions/pyRevitTools.extension/checks/modelchecker_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,44 +1167,49 @@ def DocPhases(doc, links = []):
worksetKind = str(worksetTable.GetWorkset(worksetId).Kind)
if worksetKind == "UserWorkset":
worksetName = worksetTable.GetWorkset(worksetId).Name
if element.Name not in ('DefaultLocation', '', None) or element.Category.Name not in ('', None):
# Removes the location objects from the list as well as empty elements or proxies
if worksetName not in worksetNames:
worksetNames.append(worksetName)
graphWorksetsData.append(worksetName)
# 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)
if hasattr(element, "Name") and hasattr(element, "Category") and hasattr(element.Category, "Name"):
if element.Name not in ('DefaultLocation', '', None) or element.Category.Name not in ('', None):
# Removes the location objects from the list as well as empty elements or proxies
if worksetName not in worksetNames:
worksetNames.append(worksetName)
graphWorksetsData.append(worksetName)
else:
if "Unassigned" not in worksetNames:
worksetNames.append("Unassigned")
graphWorksetsData.append("Unassigned")
# print worksetNames
# 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)
chartWorksets.draw()

class ModelChecker(PreflightTestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# -*- coding: UTF-8 -*-
from pyrevit import script, revit, DB, DOCS
from pyrevit.preflight import PreflightTestCase

doc = DOCS.doc


def grids_collector(document):
grids = DB.FilteredElementCollector(document).OfCategory(DB.BuiltInCategory.OST_Grids).WhereElementIsNotElementType()
return grids


def grids_count(document=doc):
grids = grids_collector(document)
count = grids.GetElementCount()
return count


def grids_names(document=doc):
grids = grids_collector(document)
grids_names = []
for grid in grids:
grids_names.append(grid.Name)
return grids_names


def grids_types(document=doc):
grids = grids_collector(document)
grids_types = []
for grid in grids:
grid_type = document.GetElement(grid.GetTypeId())
# grid_type = grid.get_Parameter(DB.BuiltInParameter.ELEM_TYPE_PARAM).AsElement()
grids_types.append(grid_type.get_Parameter(DB.BuiltInParameter.SYMBOL_NAME_PARAM).AsString())
return grids_types


def grids_pinned(document=doc):
grids = grids_collector(document)
pinned_grids = []
for grid in grids:
pinned_grids.append(grid.Pinned)
return pinned_grids


def grids_scoped(document=doc):
grids = grids_collector(document)
scoped_grids = []
for grid in grids:
scope = grid.get_Parameter(DB.BuiltInParameter.DATUM_VOLUME_OF_INTEREST).AsElementId()
scope = document.GetElement(scope)
if scope:
scoped_grids.append(scope.Name)
else:
scoped_grids.append("None")
return scoped_grids


def checkModel(doc, output):
output = script.get_output()
output.close_others()
output.print_md("# Grids Data Lister")
count = grids_count()
output.print_md("## Number of grids: {0}".format(count))
names = grids_names() # [1,2,3,4]
types = grids_types() # [bubble, bubble, bubble, bubble]
pinned = grids_pinned() # [True, False, True, False]
scoper = grids_scoped() # [Name of scope, Name of scope, Name of scope, Name of scope]
output.print_table(table_data=zip(names, types, pinned, scoper), title="Grids", columns=["Name", "Type", "Pinned", "Scope Box"])



class ModelChecker(PreflightTestCase):
"""
List grids, if they are pinned, scoped boxed, or named
This QC tools returns you with the following data:
Grids count, name, type, pinned status
"""

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
Loading