From 094b9cc07f26cd0b6aeed7460d02269b735c37d1 Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Tue, 3 Sep 2024 09:56:51 +0200 Subject: [PATCH 01/20] Create mysuperdupercheck-check.py --- .../checks/mysuperdupercheck-check.py | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 extensions/pyRevitTools.extension/checks/mysuperdupercheck-check.py diff --git a/extensions/pyRevitTools.extension/checks/mysuperdupercheck-check.py b/extensions/pyRevitTools.extension/checks/mysuperdupercheck-check.py new file mode 100644 index 000000000..accf4e7c2 --- /dev/null +++ b/extensions/pyRevitTools.extension/checks/mysuperdupercheck-check.py @@ -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 From d364f374db46013e6b5e73e0c098edd0f08652b7 Mon Sep 17 00:00:00 2001 From: "tay.0" Date: Tue, 8 Oct 2024 22:52:46 -0700 Subject: [PATCH 02/20] Fix Model Checker Attribute Error This Error has been lingering for a while, I decided to give it a shot. Causes: 1- Document is not workshared, OR 2- The script is trying to get the .Name and .Category of document objects that doesn't own these attribute objects, this is typical for dependent elements such as sketchplanes, graphicOverrides, roomboundaries. Solution: Filter out element that doesn't have a name or a category and sort them under a seperate list called "Unassigned" Give it a try. --- .../checks/modelchecker_check.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/extensions/pyRevitTools.extension/checks/modelchecker_check.py b/extensions/pyRevitTools.extension/checks/modelchecker_check.py index 721b85e67..7d9641874 100644 --- a/extensions/pyRevitTools.extension/checks/modelchecker_check.py +++ b/extensions/pyRevitTools.extension/checks/modelchecker_check.py @@ -1158,17 +1158,23 @@ def DocPhases(doc, links = []): .WhereElementIsNotElementType() .ToElements() ) - worksetTable = doc.GetWorksetTable() - for element in elcollector: - worksetId = element.WorksetId - 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) + if doc.IsWorkshared: + worksetTable = doc.GetWorksetTable() + for element in elcollector: + worksetId = element.WorksetId + worksetKind = str(worksetTable.GetWorkset(worksetId).Kind) + if worksetKind == "UserWorkset": + worksetName = worksetTable.GetWorkset(worksetId).Name + 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() From 6d3b4f55e5e068bd3a2d84a51e8888dbbfa184c7 Mon Sep 17 00:00:00 2001 From: "tay.0" Date: Thu, 10 Oct 2024 22:57:46 -0700 Subject: [PATCH 03/20] Create radar_check.py This Model Checker swiftly verifies the extents of the Revit model. Placing model extents more than 10 miles from the project's internal origin can lead to issues with accuracy, tolerance, performance, and viewport display. This check ensures that the model remains within a 10-mile radius of the internal origin. --- .../checks/radar_check.py | 332 ++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 extensions/pyRevitTools.extension/checks/radar_check.py diff --git a/extensions/pyRevitTools.extension/checks/radar_check.py b/extensions/pyRevitTools.extension/checks/radar_check.py new file mode 100644 index 000000000..38bd83dc5 --- /dev/null +++ b/extensions/pyRevitTools.extension/checks/radar_check.py @@ -0,0 +1,332 @@ +# -*- coding: utf-8 -*- + +# Read Me, you can download a test model from the following link: +# https://drive.google.com/file/d/1xt7IVmCh2_5MIVXrXWPPFJ50glG1RAEK/view?usp=sharing + + +#______________________________________________Imports + +#General Imports +import sys +import math + +#.NET Imports +import clr +clr.AddReference('System') +from System.Collections.Generic import List +clr.AddReference('RevitAPI') +from Autodesk.Revit.DB import * + +#pyRevit +from pyrevit import revit, DB, DOCS +from pyrevit import script +from pyrevit import forms +from pyrevit.preflight import PreflightTestCase + +#______________________________________________Global Variables +doc = DOCS.doc #type:Document +extentdistance = 52800 #Linear Feet (10 miles) +intOrig = (0,0,0) + +#______________________________________________3D to Bounding Box Analysis +class Get3DViewBoundingBox(): + def get_tempbbox(self, ToggleCAD, ToggleRVT, ToggleIFC, ToggleAll): + violatingCAD = [] + violatingRVT = [] + badelements = [] + # Create a 3D view + t = Transaction(doc, "Create 3D View") + t.Start() + view3Dtypes = DB.FilteredElementCollector(doc).OfClass(DB.ViewFamilyType).WhereElementIsElementType().ToElements() + view3dtype = [v for v in view3Dtypes if v.ViewFamily == DB.ViewFamily.ThreeDimensional][0] + view = DB.View3D.CreateIsometric(doc, view3dtype.Id) + worksets = FilteredWorksetCollector(doc).OfKind(WorksetKind.UserWorkset).ToWorksets() + for ws in worksets: + view.SetWorksetVisibility(ws.Id, DB.WorksetVisibility.Visible) + view.IsSectionBoxActive = True + bb = view.GetSectionBox() + if ToggleCAD: + cads = FilteredElementCollector(doc).OfClass(ImportInstance).ToElements() + if len(cads) == 0: + print("No CAD Links in the model") + else: + for cad in cads: + cadbox = cad.get_BoundingBox(None) + cadmin = (cadbox.Min.X, cadbox.Min.Y, cadbox.Min.Z) + cadmax = (cadbox.Max.X, cadbox.Max.Y, cadbox.Max.Z) + if calculate_distance(cadmin, intOrig) > extentdistance or calculate_distance(cadmax, intOrig) > extentdistance: + violatingCAD.append(cad) + if ToggleRVT: + rvtlinks = FilteredElementCollector(doc).OfClass(RevitLinkInstance).ToElements() + if len(rvtlinks) == 0: + print("No RVT Links in the model") + else: + for rvt in rvtlinks: + rvtbox = rvt.get_BoundingBox(view) + rvtmin = (rvtbox.Min.X, rvtbox.Min.Y, rvtbox.Min.Z) + rvtmax = (rvtbox.Max.X, rvtbox.Max.Y, rvtbox.Max.Z) + if calculate_distance(rvtmin, intOrig) > extentdistance or calculate_distance(rvtmax, intOrig) > extentdistance: + violatingRVT.append(rvt) + if ToggleIFC: + pass + allrvt = FilteredElementCollector(doc).OfClass(RevitLinkType).ToElements() + allcad = FilteredElementCollector(doc).OfClass(ImportInstance).ToElements() + if len(allrvt) > 0: + view.HideElements(FilteredElementCollector(doc).OfClass(RevitLinkType).ToElementIds()) + if len(allcad) > 0: + view.HideElements(FilteredElementCollector(doc).OfClass(ImportInstance).ToElementIds()) + view.IsSectionBoxActive = False + view.IsSectionBoxActive = True + bbh = view.GetSectionBox() + if ToggleAll: + elcollector = FilteredElementCollector(doc,view.Id).WhereElementIsNotElementType().ToElements() + for el in elcollector: + if el.get_BoundingBox(view) is not None and hasattr(el, 'Name') and hasattr(el, 'Category'): + bbox = el.get_BoundingBox(view) + if analyzebbox(bbox, intOrig, 52800) == 0: + badelements.append(el) + + t.Dispose() + return bb, violatingCAD, violatingRVT, bbh, badelements + +#____________________________________________ Calculate Distance +def calculate_distance(point1, point2): + # Unpack the tuples + x1, y1, z1 = point1 + x2, y2, z2 = point2 + + # Calculate the distance using the Euclidean distance formula + distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2 + (z2 - z1)**2) + + return distance + +#____________________________________________ Calculate Horizontal Distance +def calculate_Horizontal_Distance(point1, point2): + # Unpack the tuples + x1, y1, z1 = point1 + x2, y2, z2 = point2 + + # Calculate the distance using the Euclidean distance formula + distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2 ) + deltaX = (x2 - x1) + deltaY = (y2 - y1) + + return distance, deltaX, deltaY + +#____________________________________________ Get Bounding Box +def get_bounding_box(view): + bb = view.CropBox + min = bb.Min + max = bb.Max + return min, max + +#____________________________________________ Analyze Bounding Box +def analyzebbox(bbox, intOrig, extentdistance): + min = (bbox.Min.X, bbox.Min.Y, bbox.Min.Z) + max = (bbox.Max.X, bbox.Max.Y, bbox.Max.Z) + if calculate_distance(min, intOrig) > extentdistance or calculate_distance(max, intOrig) > extentdistance: + Status = 0 + else: + Status = 1 + return Status + +#____________________________________________ Get ProjectBase and Survey Points +def get_project_base_and_survey_points(doc): + basept = DB.BasePoint.GetProjectBasePoint(doc).Position + basept_tuple = (basept.X, basept.Y, basept.Z) + survpt = DB.BasePoint.GetSurveyPoint(doc).Position + survpt_tuple = (survpt.X, survpt.Y, survpt.Z) + intOrig = (0,0,0) + return basept_tuple, survpt_tuple, intOrig + +#____________________________________________ Get Design Options & Objects +def getalldesignoptionobjects(doc): + dbobjs = [] + design_options = FilteredElementCollector(doc).OfClass(DesignOption).ToElements() + for do in design_options: + do_filter = ElementDesignOptionFilter(do.Id) + x = FilteredElementCollector(doc).WherePasses(do_filter).ToElements() + dbobjs.append(x) + + return design_options, dbobjs + +#__________________________________________________________ +#____________________________________________ MAIN FUNCTION +#__________________________________________________________ + +def check_model_extents(doc, output): + #______________________________________________HTML Styles + output = script.get_output() + output.add_style('bad {color:red; font-weight:bold;}') + output.add_style('warn {color:orange; font-weight:bold;}') + output.add_style('good {color:green; font-weight:bold;}') + output.add_style('cover {color:black; font-size:24pt; font-weight:bold;}') + output.add_style('header {color:black; font-size:15pt;}') + stringseperator = "_____________________________________________________________________________________________" + TestScore = 0 + extentdistance = 52800 #Linear Feet + #__________________________________________check the distnaces of base and survey points + output.print_html('________:satellite_antenna:__10-Mile Radar___________') + print(stringseperator) + print("") + output.print_html('
Checking model placement and coordinates
') + print(stringseperator) + intOrig = (0,0,0) + basept, survpt, intOrig = get_project_base_and_survey_points(doc) + surveydistance = calculate_distance(survpt, intOrig) + if surveydistance > extentdistance: + output.print_html('!!............Survey Point is more than 10 miles away from the Internal Origin.') + else: + output.print_html('OK............Survey Point is less than 10 miles away from the Internal Origin.') + baseptdistance = calculate_distance(basept, intOrig) + if baseptdistance > extentdistance: + output.print_html('!!............Project Base Point is more than 10 miles away from the Internal Origin') + + else: + output.print_html('OK............Project Base Point is less than 10 miles away from the Internal Origin.') + + # Print Distances + print (stringseperator) + print ("Internal Origin Coordinates = " + str(intOrig)) + print ("Project Base Point Coordinates = " + str(basept)) + print ("Survey Point Coordinates = " + str(survpt)) + print("") + print (stringseperator) + print("") + print("") + print ("Project Base Point Distance from Internal Origin = " + str(baseptdistance)) + print ("Survey Point Distance from Internal Origin = " + str(surveydistance)) + print ("Project Base Point to Survey Delta X = " + str(calculate_Horizontal_Distance(basept, survpt)[1])) + print ("Project Base Point to Survey Delta Y = " + str(calculate_Horizontal_Distance(basept, survpt)[2])) + print ("Horizontal Distance between Project Base Point and Survey Point = " + str(calculate_distance(basept, survpt))) + ProjectElevation = survpt[2] - basept[2] + print ("Project Elevation = " + str(ProjectElevation)) + print (stringseperator) + print("") + print("") + + #__________________________________________Get the bounding box of the 3D view + output.print_html("
Checking the document's boundingbox extents
") + bbox_instance = Get3DViewBoundingBox() + bbox = bbox_instance.get_tempbbox(0,0,0,0)[0] + min = (bbox.Min.X, bbox.Min.Y, bbox.Min.Z) + max = (bbox.Max.X, bbox.Max.Y, bbox.Max.Z) + print("") + print(stringseperator) + print("") + if calculate_distance(min, intOrig) > extentdistance or calculate_distance(max, intOrig) > extentdistance: + output.print_html('!!............3D View Bounding Box extends more than 10 miles away from the Internal Origin') + else: + output.print_html('OK............3D View Bounding Box is located less than 10 miles away from the Internal Origin.') + TestScore += 1 + + #__________________________________________Get Objects in Design Options + print("") + print(stringseperator) + output.print_html("
Checking the design options objects
") + print(stringseperator) + design_option_objects = getalldesignoptionobjects(doc) + violating_design_option_objects = [] + violating_options = [] + for x in design_option_objects[1]: + for y in x: + dbbox = y.get_BoundingBox(None) + dbmin = (dbbox.Min.X, dbbox.Min.Y, dbbox.Min.Z) + dbmax = (dbbox.Max.X, dbbox.Max.Y, dbbox.Max.Z) + if calculate_distance(dbmin, intOrig) > extentdistance or calculate_distance(dbmax, intOrig) > extentdistance: + violating_design_option_objects.append(x) + if y.DesignOption.Name not in violating_options: + violating_options.append(y.DesignOption.Name) + if len(violating_design_option_objects) > 0: + output.print_html('!!............Design Option Objects are located more than 10 miles away from the Internal Origin') + for x in violating_design_option_objects: + for y in x: + print(output.linkify(y.Id)+ str(y.Name)+ " - Is part of design option - "+ str(y.DesignOption.Name) ) + else: + output.print_html('OK............No object in any design option is located more than 10 miles away from the Internal Origin.') + TestScore += 1 + #__________________________________________Check Test Score + if TestScore >= 2: + output.print_html('OK............All Tests Passed.') + sys.exit() + else: + output.print_html('!!............Distant objects detected, Proceeding with additional analysis') + + #__________________________________________Check CAD and RVT Links + print(stringseperator) + output.print_html('
Checking CAD and RVT Links
') + print(stringseperator) + bboxLink = bbox_instance.get_tempbbox(1,1,1,0) + badcads = bboxLink[1] + badrvts = bboxLink[2] + cleanbbox = bboxLink[3] + # print (bboxLink[1], bboxLink[2]) + # print(bbox.Min, cleanbbox.Min) + if len(badcads) > 0 or len(badrvts) > 0: + for x in badcads: + print(output.linkify(x.Id)+"__" + str(x.Name) + ' ' + str(x.Category.Name)) + for x in badrvts: + print(output.linkify(x.Id)+"__" + str(x.Name) + ' ' + str(x.Category.Name)) + else: + output.print_html('OK............All CAD and RVT Links are located less than 10 miles away from the Internal Origin.') + TestScore += 1 + print(stringseperator) + if analyzebbox(cleanbbox, intOrig, 5) == 0: + output.print_html('!!............Distant objects are still being detected!') + output.print_html('!!............Further Analysis Required.') + else: + output.print_html('OK............All Objects are located less than 10 miles away from the Internal Origin.') + sys.exit() + print(stringseperator) + output.print_html('
Checking everything, It is going to take a while.
') + output.print_html('
please be patient.
') + #__________________________________________Check Bounding Box of Every Element in the Model + print(stringseperator) + getbadelements = bbox_instance.get_tempbbox(0,0,0,1) + badelements = getbadelements[4] + if len(badelements) > 0: + output.print_html('!!............Elements below are located more than 10 miles away from the Internal Origin') + for x in badelements: + print(output.linkify(x.Id)+ ' ' + str(x.Name) + ' ' + str(x.Category.Name)) + else: + output.print_html('.........All Objects are located less than 10 miles away from the Internal Origin.') + TestScore += 1 + +#______________________________________________Model Checker Class + +class ModelChecker(PreflightTestCase): + """ + Checks the extents of all elements in the model. +This Model Checker swiftly verifies the extents of the Revit model. +Placing model extents more than 10 miles from the project's +internal origin can lead to issues with accuracy, tolerance, +performance, and viewport display. This check ensures that the +model remains within a 10-mile radius of the internal origin. + +The test case examines the following, reporting extents +concerning the project's internal origin. The script prioritizes +based on the assumption that most model extent issues are +related to the following: + + - The distance between project basepoint and internal origin + - The distance between survey point and internal origin + - The bounding box of the 3D view + - The bounding box of the design option objects + - The bounding box of the CAD and RVT links + - The bounding box of all elements in the model + """ + + name = "10 Mile Radar" + author = "Tay Othman" + + def setUp(self, doc, output): + pass + + def startTest(self, doc, output): + check_model_extents(doc, output) + + def tearDown(self, doc, output): + pass + + def doCleanups(self, doc, output): + pass From de6ab8560b355da5d37a1cb3fa536cb1c7ce782d Mon Sep 17 00:00:00 2001 From: "tay.0" Date: Wed, 16 Oct 2024 14:41:06 -0700 Subject: [PATCH 04/20] Update radar_check.py - Rev1 Made the changes below per Jean-Marc's feedback. 1- you may want to present digits results in a tabular form for readability and ease in comparison. Response: Added output.print_table 2- the project base point cannot be placed at more than 10 miles in more recent version of Revit Response: Good to know! I removed the report for the location of the project basept. 3- you may want to use the convert unit util to display the distances in the same units as in the UI Response: I used the unitutils to convert the internal distances to the model distances, however in metric I am having hard time to show the suffix (mm, M, km)....etc 4- you may want to limit the amount of objects output in the output windows using linkify as it may greatly slow the tool Response: Set the limit of linkify to 10 prints per category, if there are more violating objects, then it will show a message "Manual investigation is required" 5- I tend to prefer print_md over print_html as it is more efficient and improves readability Response: I switched most of them to use print_md. also I used pyrevit emojis instead of html colors. please take a look and let me know if this makes sense. --- .../checks/radar_check.py | 162 +++++++++++------- 1 file changed, 101 insertions(+), 61 deletions(-) diff --git a/extensions/pyRevitTools.extension/checks/radar_check.py b/extensions/pyRevitTools.extension/checks/radar_check.py index 38bd83dc5..68705dc4a 100644 --- a/extensions/pyRevitTools.extension/checks/radar_check.py +++ b/extensions/pyRevitTools.extension/checks/radar_check.py @@ -18,15 +18,14 @@ from Autodesk.Revit.DB import * #pyRevit -from pyrevit import revit, DB, DOCS +from pyrevit import DB, DOCS, HOST_APP from pyrevit import script -from pyrevit import forms from pyrevit.preflight import PreflightTestCase #______________________________________________Global Variables doc = DOCS.doc #type:Document -extentdistance = 52800 #Linear Feet (10 miles) intOrig = (0,0,0) +extentdistance = 52800 #Linear Feet #______________________________________________3D to Bounding Box Analysis class Get3DViewBoundingBox(): @@ -100,6 +99,26 @@ def calculate_distance(point1, point2): return distance +def convert_units(doc, distance): + + + if HOST_APP.is_newer_than(2021): + UIunits = DB.UnitFormatUtils.Format(units=doc.GetUnits(), + specTypeId=DB.SpecTypeId.Length, + value=distance, + forEditing=False) + + + else: + UIunits = DB.UnitFormatUtils(units=doc.GetUnits(), + unitType=DB.UnitType.UT_Length, + value=distance, + maxAccuracy=False, + forEditing=False) + + return UIunits + + #____________________________________________ Calculate Horizontal Distance def calculate_Horizontal_Distance(point1, point2): # Unpack the tuples @@ -157,56 +176,48 @@ def getalldesignoptionobjects(doc): def check_model_extents(doc, output): #______________________________________________HTML Styles output = script.get_output() - output.add_style('bad {color:red; font-weight:bold;}') - output.add_style('warn {color:orange; font-weight:bold;}') - output.add_style('good {color:green; font-weight:bold;}') output.add_style('cover {color:black; font-size:24pt; font-weight:bold;}') output.add_style('header {color:black; font-size:15pt;}') - stringseperator = "_____________________________________________________________________________________________" + stringseperator = "_________________________________________________________________________________________________" TestScore = 0 - extentdistance = 52800 #Linear Feet #__________________________________________check the distnaces of base and survey points - output.print_html('________:satellite_antenna:__10-Mile Radar___________') + output.print_html('__________:satellite_antenna:__10-Mile Radar___________') print(stringseperator) print("") - output.print_html('
Checking model placement and coordinates
') + + output.print_md('# Checking model placement and coordinates') print(stringseperator) intOrig = (0,0,0) basept, survpt, intOrig = get_project_base_and_survey_points(doc) - surveydistance = calculate_distance(survpt, intOrig) + baseptdistance = abs(calculate_distance(basept, intOrig)) + surveydistance = abs(calculate_distance(survpt, intOrig)) + if baseptdistance > extentdistance: + output.print_md('### :thumbs_down_medium_skin_tone: ............Project Base Point is more than 10 miles (16KM) away from the Internal Origin.***') if surveydistance > extentdistance: - output.print_html('!!............Survey Point is more than 10 miles away from the Internal Origin.') + output.print_md('### :thumbs_down_medium_skin_tone: ............Survey Point is more than 10 miles (16KM) away from the Internal Origin.***') else: - output.print_html('OK............Survey Point is less than 10 miles away from the Internal Origin.') + output.print_md('### :OK_hand_medium_skin_tone: ............Survey Point is less than 10 miles (16KM) away from the Internal Origin.***') baseptdistance = calculate_distance(basept, intOrig) - if baseptdistance > extentdistance: - output.print_html('!!............Project Base Point is more than 10 miles away from the Internal Origin') - else: - output.print_html('OK............Project Base Point is less than 10 miles away from the Internal Origin.') - - # Print Distances - print (stringseperator) - print ("Internal Origin Coordinates = " + str(intOrig)) - print ("Project Base Point Coordinates = " + str(basept)) - print ("Survey Point Coordinates = " + str(survpt)) - print("") - print (stringseperator) - print("") - print("") - print ("Project Base Point Distance from Internal Origin = " + str(baseptdistance)) - print ("Survey Point Distance from Internal Origin = " + str(surveydistance)) - print ("Project Base Point to Survey Delta X = " + str(calculate_Horizontal_Distance(basept, survpt)[1])) - print ("Project Base Point to Survey Delta Y = " + str(calculate_Horizontal_Distance(basept, survpt)[2])) - print ("Horizontal Distance between Project Base Point and Survey Point = " + str(calculate_distance(basept, survpt))) - ProjectElevation = survpt[2] - basept[2] - print ("Project Elevation = " + str(ProjectElevation)) - print (stringseperator) - print("") - print("") + tabledata = [['InternaL Origin Coordinates', str(intOrig)], + ['Project Base Point Coordinates', str(basept)], + ['Survey Point Coordinates', str(survpt)], + ['Project Base Point Distance from Internal Origin', str(convert_units(doc, baseptdistance))], + ['Survey Point Distance from Internal Origin', str(convert_units(doc, surveydistance))], + ['Project Base Point to Survey Delta X', str(convert_units(doc, calculate_Horizontal_Distance(basept, survpt)[1]))], + ['Project Base Point to Survey Delta Y', str(convert_units(doc, calculate_Horizontal_Distance(basept, survpt)[2]))], + ['Horizontal Distance between Project Base Point and Survey Point', str(convert_units(doc, calculate_Horizontal_Distance(basept, survpt)[0]))], + ['Project Elevation', str(convert_units(doc, (survpt[2] - basept[2])))]] + + # Print Table + output.print_table(table_data=tabledata, + title='Project Coordinates and Distances', + columns=['Coordinates', 'Values'], + formats=['', '']) #__________________________________________Get the bounding box of the 3D view - output.print_html("
Checking the document's boundingbox extents
") + print("") + output.print_md('# Checking the extents of the 3D view bounding box') bbox_instance = Get3DViewBoundingBox() bbox = bbox_instance.get_tempbbox(0,0,0,0)[0] min = (bbox.Min.X, bbox.Min.Y, bbox.Min.Z) @@ -215,15 +226,15 @@ def check_model_extents(doc, output): print(stringseperator) print("") if calculate_distance(min, intOrig) > extentdistance or calculate_distance(max, intOrig) > extentdistance: - output.print_html('!!............3D View Bounding Box extends more than 10 miles away from the Internal Origin') + output.print_md('### :thumbs_down_medium_skin_tone: ............3D View Bounding Box extends more than 10 miles (16KM) away from the Internal Origin.***') else: - output.print_html('OK............3D View Bounding Box is located less than 10 miles away from the Internal Origin.') + output.print_md('### :OK_hand_medium_skin_tone: ............3D View Bounding Box is located less than 10 miles (16KM) away from the Internal Origin.***') TestScore += 1 #__________________________________________Get Objects in Design Options print("") print(stringseperator) - output.print_html("
Checking the design options objects
") + output.print_md('# Checking the extents of the design option objects') print(stringseperator) design_option_objects = getalldesignoptionobjects(doc) violating_design_option_objects = [] @@ -231,30 +242,42 @@ def check_model_extents(doc, output): for x in design_option_objects[1]: for y in x: dbbox = y.get_BoundingBox(None) - dbmin = (dbbox.Min.X, dbbox.Min.Y, dbbox.Min.Z) - dbmax = (dbbox.Max.X, dbbox.Max.Y, dbbox.Max.Z) - if calculate_distance(dbmin, intOrig) > extentdistance or calculate_distance(dbmax, intOrig) > extentdistance: - violating_design_option_objects.append(x) - if y.DesignOption.Name not in violating_options: - violating_options.append(y.DesignOption.Name) + if dbbox is None: + continue + else: + dbmin = (dbbox.Min.X, dbbox.Min.Y, dbbox.Min.Z) + dbmax = (dbbox.Max.X, dbbox.Max.Y, dbbox.Max.Z) + if calculate_distance(dbmin, intOrig) > extentdistance or calculate_distance(dbmax, intOrig) > extentdistance: + violating_design_option_objects.append(x) + if y.DesignOption.Name not in violating_options: + violating_options.append(y.DesignOption.Name) if len(violating_design_option_objects) > 0: - output.print_html('!!............Design Option Objects are located more than 10 miles away from the Internal Origin') + output.print_md('### :thumbs_down_medium_skin_tone: ............Design Option Objects are located more than 10 miles (16KM) away from the Internal Origin.***') + if len(violating_design_option_objects) > 10: + output.print_md('### :warning: ............Showing the first 10 objects***') + output.print_md('### :warning: ............Manual investigation is required***') + counter = 0 + limit = 10 for x in violating_design_option_objects: + for y in x: + if counter == limit: + break print(output.linkify(y.Id)+ str(y.Name)+ " - Is part of design option - "+ str(y.DesignOption.Name) ) + counter += 1 else: - output.print_html('OK............No object in any design option is located more than 10 miles away from the Internal Origin.') + output.print_md('### :OK_hand_medium_skin_tone: ............No object in any design option is located more than 10 miles (16KM) away from the Internal Origin.***') TestScore += 1 #__________________________________________Check Test Score if TestScore >= 2: - output.print_html('OK............All Tests Passed.') + output.print_md('### :OK_hand_medium_skin_tone: ............All Tests Passed.***') sys.exit() else: - output.print_html('!!............Distant objects detected, Proceeding with additional analysis') + output.print_md('### :thumbs_down_medium_skin_tone: ............Distant objects detected, Proceeding with additional analysis') #__________________________________________Check CAD and RVT Links print(stringseperator) - output.print_html('
Checking CAD and RVT Links
') + output.print_md('# Checking the extents of the CAD and RVT links') print(stringseperator) bboxLink = bbox_instance.get_tempbbox(1,1,1,0) badcads = bboxLink[1] @@ -262,34 +285,51 @@ def check_model_extents(doc, output): cleanbbox = bboxLink[3] # print (bboxLink[1], bboxLink[2]) # print(bbox.Min, cleanbbox.Min) + counter = 0 + limit = 5 if len(badcads) > 0 or len(badrvts) > 0: for x in badcads: print(output.linkify(x.Id)+"__" + str(x.Name) + ' ' + str(x.Category.Name)) + if counter == limit: + break + counter += 1 + counter = 0 for x in badrvts: print(output.linkify(x.Id)+"__" + str(x.Name) + ' ' + str(x.Category.Name)) + if counter == limit: + break + counter += 1 else: - output.print_html('OK............All CAD and RVT Links are located less than 10 miles away from the Internal Origin.') + output.print_md('### :OK_hand_medium_skin_tone: ............All CAD and RVT Links are located less than 10 miles (16KM) away from the Internal Origin.***') TestScore += 1 print(stringseperator) if analyzebbox(cleanbbox, intOrig, 5) == 0: - output.print_html('!!............Distant objects are still being detected!') - output.print_html('!!............Further Analysis Required.') + output.print_md('### :thumbs_down_medium_skin_tone: ............Distant objects are still being detected!') + output.print_md('### :warning: ............Further Analysis Required.') else: - output.print_html('OK............All Objects are located less than 10 miles away from the Internal Origin.') + output.print_md('### :OK_hand_medium_skin_tone: ............All Objects are located less than 10 miles (16KM) away from the Internal Origin.***') sys.exit() print(stringseperator) - output.print_html('
Checking everything, It is going to take a while.
') - output.print_html('
please be patient.
') + output.print_md('# Checking everything, It is going to take a while.') + output.print_md('# Please be patient.') #__________________________________________Check Bounding Box of Every Element in the Model print(stringseperator) getbadelements = bbox_instance.get_tempbbox(0,0,0,1) badelements = getbadelements[4] + counter = 0 + limit = 10 if len(badelements) > 0: - output.print_html('!!............Elements below are located more than 10 miles away from the Internal Origin') + if len(badelements) > limit: + output.print_md('### :warning: ............Showing the first 10 objects***') + output.print_md('### :warning: ............Manual investigation is required***') + output.print_md('### :thumbs_down_medium_skin_tone: ............Elements below are located more than 10 miles (16KM) away from the Internal Origin') for x in badelements: print(output.linkify(x.Id)+ ' ' + str(x.Name) + ' ' + str(x.Category.Name)) + if counter == limit: + break + counter += 1 else: - output.print_html('.........All Objects are located less than 10 miles away from the Internal Origin.') + output.print_md('### :OK_hand_medium_skin_tone: ............All Objects are located less than 10 miles (16KM) away from the Internal Origin.***') TestScore += 1 #______________________________________________Model Checker Class @@ -298,7 +338,7 @@ class ModelChecker(PreflightTestCase): """ Checks the extents of all elements in the model. This Model Checker swiftly verifies the extents of the Revit model. -Placing model extents more than 10 miles from the project's +Placing model extents more than 10 miles (16KM) from the project's internal origin can lead to issues with accuracy, tolerance, performance, and viewport display. This check ensures that the model remains within a 10-mile radius of the internal origin. From ac74b95ee7395e5d6d09c33521472a1b65d907de Mon Sep 17 00:00:00 2001 From: nasmovk <136859499+nasmovk@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:17:06 +0200 Subject: [PATCH 05/20] Create CADAUDIT_check.py My proposal for a comprehensive audit of cad instances. See details described in the ModelChecker class comments. It would replace the old LISTDWGs by F. Beaupere, from the Project panel, link tools. Bonus: It also extends the basic functionality of RPW by adding radio buttons and a cancel button. --- .../checks/CADAUDIT_check.py | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 extensions/pyRevitTools.extension/checks/CADAUDIT_check.py diff --git a/extensions/pyRevitTools.extension/checks/CADAUDIT_check.py b/extensions/pyRevitTools.extension/checks/CADAUDIT_check.py new file mode 100644 index 000000000..3dfb9310e --- /dev/null +++ b/extensions/pyRevitTools.extension/checks/CADAUDIT_check.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- + +# Import Reference Modules +from pyrevit import script, revit, DB, DOCS +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 = __revit__.ActiveUIDocument.ActiveView # Get the active view + + +# Definitions + +def collect_cadinstances(active_view_only): + """ Collect ImportInstance class from whole model or from just active view """ + #cadinstances = DB.FilteredElementCollector(doc).OfClass(DB.ImportInstance).WhereElementIsNotElementType().ToElements() + 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: + print("No CAD instances found in the {}.".format("active view" if active_view_only else "model")) + script.exit() + +# 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"] # user choice of collection mode + + 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 From 13c98b86ad90c4480ff05d2721f6a17df256f8b4 Mon Sep 17 00:00:00 2001 From: "tay.0" Date: Fri, 18 Oct 2024 09:44:53 -0700 Subject: [PATCH 06/20] Update radar_check.py Updated the script for better compliance and fixed issues reported by @jmcouffin and @dosymep --- .../checks/radar_check.py | 592 +++++++++++------- 1 file changed, 357 insertions(+), 235 deletions(-) diff --git a/extensions/pyRevitTools.extension/checks/radar_check.py b/extensions/pyRevitTools.extension/checks/radar_check.py index 68705dc4a..0ae45e202 100644 --- a/extensions/pyRevitTools.extension/checks/radar_check.py +++ b/extensions/pyRevitTools.extension/checks/radar_check.py @@ -1,221 +1,312 @@ # -*- coding: utf-8 -*- -# Read Me, you can download a test model from the following link: -# https://drive.google.com/file/d/1xt7IVmCh2_5MIVXrXWPPFJ50glG1RAEK/view?usp=sharing - - -#______________________________________________Imports - -#General Imports -import sys +# ______________________________________________________________________Imports +# General Imports import math - -#.NET Imports -import clr -clr.AddReference('System') -from System.Collections.Generic import List -clr.AddReference('RevitAPI') -from Autodesk.Revit.DB import * - -#pyRevit -from pyrevit import DB, DOCS, HOST_APP +# pyRevit +from pyrevit import revit, DB, DOCS, HOST_APP from pyrevit import script from pyrevit.preflight import PreflightTestCase -#______________________________________________Global Variables -doc = DOCS.doc #type:Document -intOrig = (0,0,0) -extentdistance = 52800 #Linear Feet +# ______________________________________________________________Global Variables +doc = DOCS.doc # Current Document +INTERNAL_ORIGIN = (0, 0, 0) +EXTENT_DISTANCE = 52800 # Linear Feet +BAD_STRG = '### :thumbs_down_medium_skin_tone: ............' +GOOD_STRG = '### :OK_hand_medium_skin_tone: ............' +WARN_STRG = '### :warning: ............' +CRITERIA_STRG = '10 Mi (16KM) away from the Internal Origin.' -#______________________________________________3D to Bounding Box Analysis +# ___________________________________________________3D to Bounding Box Analysis class Get3DViewBoundingBox(): - def get_tempbbox(self, ToggleCAD, ToggleRVT, ToggleIFC, ToggleAll): - violatingCAD = [] - violatingRVT = [] - badelements = [] + def get_tempbbox(self, toggle_cads, toggle_rvts, toggle_ifcs, toggle_all): + bad_cads = [] + bad_rvts = [] + bad_elements = [] # Create a 3D view - t = Transaction(doc, "Create 3D View") - t.Start() - view3Dtypes = DB.FilteredElementCollector(doc).OfClass(DB.ViewFamilyType).WhereElementIsElementType().ToElements() - view3dtype = [v for v in view3Dtypes if v.ViewFamily == DB.ViewFamily.ThreeDimensional][0] - view = DB.View3D.CreateIsometric(doc, view3dtype.Id) - worksets = FilteredWorksetCollector(doc).OfKind(WorksetKind.UserWorkset).ToWorksets() - for ws in worksets: - view.SetWorksetVisibility(ws.Id, DB.WorksetVisibility.Visible) - view.IsSectionBoxActive = True - bb = view.GetSectionBox() - if ToggleCAD: - cads = FilteredElementCollector(doc).OfClass(ImportInstance).ToElements() - if len(cads) == 0: - print("No CAD Links in the model") - else: - for cad in cads: - cadbox = cad.get_BoundingBox(None) - cadmin = (cadbox.Min.X, cadbox.Min.Y, cadbox.Min.Z) - cadmax = (cadbox.Max.X, cadbox.Max.Y, cadbox.Max.Z) - if calculate_distance(cadmin, intOrig) > extentdistance or calculate_distance(cadmax, intOrig) > extentdistance: - violatingCAD.append(cad) - if ToggleRVT: - rvtlinks = FilteredElementCollector(doc).OfClass(RevitLinkInstance).ToElements() - if len(rvtlinks) == 0: - print("No RVT Links in the model") - else: - for rvt in rvtlinks: - rvtbox = rvt.get_BoundingBox(view) - rvtmin = (rvtbox.Min.X, rvtbox.Min.Y, rvtbox.Min.Z) - rvtmax = (rvtbox.Max.X, rvtbox.Max.Y, rvtbox.Max.Z) - if calculate_distance(rvtmin, intOrig) > extentdistance or calculate_distance(rvtmax, intOrig) > extentdistance: - violatingRVT.append(rvt) - if ToggleIFC: - pass - allrvt = FilteredElementCollector(doc).OfClass(RevitLinkType).ToElements() - allcad = FilteredElementCollector(doc).OfClass(ImportInstance).ToElements() - if len(allrvt) > 0: - view.HideElements(FilteredElementCollector(doc).OfClass(RevitLinkType).ToElementIds()) - if len(allcad) > 0: - view.HideElements(FilteredElementCollector(doc).OfClass(ImportInstance).ToElementIds()) - view.IsSectionBoxActive = False - view.IsSectionBoxActive = True - bbh = view.GetSectionBox() - if ToggleAll: - elcollector = FilteredElementCollector(doc,view.Id).WhereElementIsNotElementType().ToElements() - for el in elcollector: - if el.get_BoundingBox(view) is not None and hasattr(el, 'Name') and hasattr(el, 'Category'): - bbox = el.get_BoundingBox(view) - if analyzebbox(bbox, intOrig, 52800) == 0: - badelements.append(el) - - t.Dispose() - return bb, violatingCAD, violatingRVT, bbh, badelements - -#____________________________________________ Calculate Distance -def calculate_distance(point1, point2): - # Unpack the tuples - x1, y1, z1 = point1 - x2, y2, z2 = point2 - - # Calculate the distance using the Euclidean distance formula - distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2 + (z2 - z1)**2) - - return distance + with revit.DryTransaction("Create 3D View"): + view_3D_types = (DB.FilteredElementCollector(doc) + .OfClass(DB.ViewFamilyType) + .WhereElementIsElementType() + .ToElements()) + three_d_view_type = ([v for v in view_3D_types + if v.ViewFamily == DB.ViewFamily.ThreeDimensional][0]) + view = DB.View3D.CreateIsometric(doc, three_d_view_type.Id) + worksets = (DB.FilteredWorksetCollector(doc). + OfKind(DB.WorksetKind.UserWorkset). + ToWorksets()) + for ws in worksets: + view.SetWorksetVisibility(ws.Id, DB.WorksetVisibility.Visible) + view.IsSectionBoxActive = True + bb = view.GetSectionBox() + if toggle_cads: + cads = (DB.FilteredElementCollector(doc). + OfClass(DB.ImportInstance). + ToElements()) + if len(cads) == 0: + print("No CAD Links in the model") + else: + for cad in cads: + cadbox = cad.get_BoundingBox(None) + cadmin = (cadbox.Min.X, cadbox.Min.Y, cadbox.Min.Z) + cadmax = (cadbox.Max.X, cadbox.Max.Y, cadbox.Max.Z) + if (calculate_distance( + cadmin, INTERNAL_ORIGIN) > EXTENT_DISTANCE or + calculate_distance(cadmax, INTERNAL_ORIGIN) > + EXTENT_DISTANCE): + bad_cads.append(cad) + if toggle_rvts: + rvtlinks = (DB.FilteredElementCollector(doc). + OfClass(DB.RevitLinkInstance). + ToElements()) + if len(rvtlinks) == 0: + print("No RVT Links in the model") + else: + for rvt in rvtlinks: + rvtbox = rvt.get_BoundingBox(view) + rvtmin = (rvtbox.Min.X, rvtbox.Min.Y, rvtbox.Min.Z) + rvtmax = (rvtbox.Max.X, rvtbox.Max.Y, rvtbox.Max.Z) + if (calculate_distance + (rvtmin, INTERNAL_ORIGIN) > EXTENT_DISTANCE or + calculate_distance + (rvtmax, INTERNAL_ORIGIN) > EXTENT_DISTANCE): + bad_rvts.append(rvt) + if toggle_ifcs: + pass + revit_link_types = (DB.FilteredElementCollector(doc). + OfClass(DB.RevitLinkType). + ToElements()) + cads = (DB.FilteredElementCollector(doc).OfClass(DB.ImportInstance). + ToElements()) + if len(revit_link_types) > 0: + (view.HideElements(DB.FilteredElementCollector(doc). + OfClass(DB.RevitLinkType). + ToElementIds())) + if len(cads) > 0: + (view.HideElements(DB.FilteredElementCollector(doc). + OfClass(DB.ImportInstance). + ToElementIds())) + view.IsSectionBoxActive = False + view.IsSectionBoxActive = True + bbh = view.GetSectionBox() + if toggle_all: + elements = (DB.FilteredElementCollector(doc, view.Id). + WhereElementIsNotElementType(). + ToElements()) + for element in elements: + if (element.get_BoundingBox(view) is not None and + hasattr(element, 'Name') and hasattr(element, 'Category')): + bbox = element.get_BoundingBox(view) + if check_bounding_box( + bbox, INTERNAL_ORIGIN, EXTENT_DISTANCE) == 0: + bad_elements.append(element) + return bb, bad_cads, bad_rvts, bbh, bad_elements + + +# ___________________________________________________________ Convert values +def convert_values(value, document=doc): + if HOST_APP.is_newer_than(2021): + ui_units = (document.GetUnits().GetFormatOptions(DB.SpecTypeId.Length) + .GetUnitTypeId()) + else: + ui_units = (document.GetUnits().GetFormatOptions(DB.UnitType.UT_Length) + .DisplayUnits) -def convert_units(doc, distance): + ui_values = DB.UnitUtils.ConvertFromInternalUnits(value, ui_units) + return ui_values +# ___________________________________________________________ Convert Units +def convert_units(distance, document=doc): if HOST_APP.is_newer_than(2021): - UIunits = DB.UnitFormatUtils.Format(units=doc.GetUnits(), + ui_units = DB.UnitFormatUtils.Format(units=document.GetUnits(), specTypeId=DB.SpecTypeId.Length, value=distance, - forEditing=False) - - + forEditing=True) else: - UIunits = DB.UnitFormatUtils(units=doc.GetUnits(), - unitType=DB.UnitType.UT_Length, - value=distance, - maxAccuracy=False, - forEditing=False) + ui_units = DB.UnitFormatUtils.Format(units=document.GetUnits(), + unitType=DB.UnitType.UT_Length, + value=distance, maxAccuracy=False, + forEditing=True) + return ui_units - return UIunits - -#____________________________________________ Calculate Horizontal Distance -def calculate_Horizontal_Distance(point1, point2): +# ___________________________________________________________ Calculate Distance +def calculate_distance(point1, point2): # Unpack the tuples x1, y1, z1 = point1 x2, y2, z2 = point2 + # Calculate the distance using the Euclidean distance formula + distance =( #rounded to the nearest inch + math.sqrt((x2 - x1)**2 + (y2 - y1)**2 + (z2 - z1)**2)) + return distance + +# ________________________________________________ Calculate Horizontal Distance +def calculate_horizontal_distance(point1, point2): + # Unpack the tuples + x1, y1, z1 = point1 + x2, y2, z2 = point2 # Calculate the distance using the Euclidean distance formula - distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2 ) - deltaX = (x2 - x1) - deltaY = (y2 - y1) + distance = ( + math.sqrt((x2 - x1)**2 + (y2 - y1)**2)) + delta_x = (x2 - x1) + delta_y = (y2 - y1) + return distance, delta_x, delta_y + + +# ______________________________________________Calculate Angle Between 2 Points +def calculate_angle(point1, point2): + # Unpack the tuples + x1, y1, z1 = point1 + x2, y2, z2 = point2 + # Calculate the angle using the arctangent function + angle = round(math.degrees(math.atan2(y2 - y1, x2 - x1)), 2) + return angle - return distance, deltaX, deltaY -#____________________________________________ Get Bounding Box +# _____________________________________________________________ Get Bounding Box def get_bounding_box(view): bb = view.CropBox min = bb.Min max = bb.Max return min, max -#____________________________________________ Analyze Bounding Box -def analyzebbox(bbox, intOrig, extentdistance): + +# _________________________________________________________ Analyze Bounding Box +def check_bounding_box(bbox, intorig, extentdistance): min = (bbox.Min.X, bbox.Min.Y, bbox.Min.Z) max = (bbox.Max.X, bbox.Max.Y, bbox.Max.Z) - if calculate_distance(min, intOrig) > extentdistance or calculate_distance(max, intOrig) > extentdistance: + if (calculate_distance(min, intorig) > extentdistance or + calculate_distance(max, intorig) > extentdistance): Status = 0 else: Status = 1 return Status -#____________________________________________ Get ProjectBase and Survey Points -def get_project_base_and_survey_points(doc): - basept = DB.BasePoint.GetProjectBasePoint(doc).Position - basept_tuple = (basept.X, basept.Y, basept.Z) - survpt = DB.BasePoint.GetSurveyPoint(doc).Position - survpt_tuple = (survpt.X, survpt.Y, survpt.Z) - intOrig = (0,0,0) - return basept_tuple, survpt_tuple, intOrig - -#____________________________________________ Get Design Options & Objects -def getalldesignoptionobjects(doc): - dbobjs = [] - design_options = FilteredElementCollector(doc).OfClass(DesignOption).ToElements() - for do in design_options: - do_filter = ElementDesignOptionFilter(do.Id) - x = FilteredElementCollector(doc).WherePasses(do_filter).ToElements() - dbobjs.append(x) - return design_options, dbobjs +# ____________________________________________ Get ProjectBase and Survey Points +def get_project_base_and_survey_pts(document=doc): + base_point = DB.BasePoint.GetProjectBasePoint(document).Position + base_point_coordinates = ( + base_point.X, + base_point.Y, + base_point.Z) + survey_point = DB.BasePoint.GetSurveyPoint(document).Position + survey_point_coordinates = ( + survey_point.X, + survey_point.Y, + survey_point.Z) + return base_point_coordinates, survey_point_coordinates, INTERNAL_ORIGIN + + +# _______________________________________________________________Get Model Units +def get_model_units_type(document=doc): + unitsystem = document.DisplayUnitSystem + return unitsystem + -#__________________________________________________________ -#____________________________________________ MAIN FUNCTION -#__________________________________________________________ +# _________________________________________________ Get Design Options & Objects +def get_design_options_elements(document=doc): + design_option_elements = [] + design_option_sets = [] + design_options = (DB.FilteredElementCollector(document). + OfClass(DB.DesignOption). + ToElements()) -def check_model_extents(doc, output): - #______________________________________________HTML Styles + for do in design_options: + option_set_param = DB.BuiltInParameter.OPTION_SET_ID + option_set_id = do.get_Parameter(option_set_param).AsElementId() + design_option_sets.append(option_set_id) + design_option_filter = DB.ElementDesignOptionFilter(do.Id) + x = (DB.FilteredElementCollector(document). + WherePasses(design_option_filter). + ToElements()) + design_option_elements.append(x) + return design_options, design_option_elements, design_option_sets + + +# ______________________________________________________________________________ +# ________________________________________________________________ MAIN FUNCTION +# ______________________________________________________________________________ +def check_model_extents(document=doc): + unit_system = get_model_units_type(document=doc) + # _______________________________________________________________HTML Styles output = script.get_output() output.add_style('cover {color:black; font-size:24pt; font-weight:bold;}') output.add_style('header {color:black; font-size:15pt;}') - stringseperator = "_________________________________________________________________________________________________" - TestScore = 0 - #__________________________________________check the distnaces of base and survey points - output.print_html('__________:satellite_antenna:__10-Mile Radar___________') - print(stringseperator) + divider = "_"*100 + test_score = 0 + output.print_html( + ('__________:satellite_antenna:__10-Mile Radar___________')) + print(divider) print("") - output.print_md('# Checking model placement and coordinates') - print(stringseperator) - intOrig = (0,0,0) - basept, survpt, intOrig = get_project_base_and_survey_points(doc) - baseptdistance = abs(calculate_distance(basept, intOrig)) - surveydistance = abs(calculate_distance(survpt, intOrig)) - if baseptdistance > extentdistance: - output.print_md('### :thumbs_down_medium_skin_tone: ............Project Base Point is more than 10 miles (16KM) away from the Internal Origin.***') - if surveydistance > extentdistance: - output.print_md('### :thumbs_down_medium_skin_tone: ............Survey Point is more than 10 miles (16KM) away from the Internal Origin.***') + print(divider) + # _____________________________Check the distances of base and survey points + baspt, survpt, INTERNAL_ORIGIN = get_project_base_and_survey_pts(document) + basptdistance = abs(calculate_distance(baspt, INTERNAL_ORIGIN)) + surveydistance = abs(calculate_distance(survpt, INTERNAL_ORIGIN)) + + if basptdistance > EXTENT_DISTANCE: + output.print_md(BAD_STRG + + 'Base Point is more than ' + CRITERIA_STRG) + if surveydistance > EXTENT_DISTANCE: + output.print_md(BAD_STRG + + 'Survey Point is more than ' + CRITERIA_STRG) else: - output.print_md('### :OK_hand_medium_skin_tone: ............Survey Point is less than 10 miles (16KM) away from the Internal Origin.***') - baseptdistance = calculate_distance(basept, intOrig) - - tabledata = [['InternaL Origin Coordinates', str(intOrig)], - ['Project Base Point Coordinates', str(basept)], - ['Survey Point Coordinates', str(survpt)], - ['Project Base Point Distance from Internal Origin', str(convert_units(doc, baseptdistance))], - ['Survey Point Distance from Internal Origin', str(convert_units(doc, surveydistance))], - ['Project Base Point to Survey Delta X', str(convert_units(doc, calculate_Horizontal_Distance(basept, survpt)[1]))], - ['Project Base Point to Survey Delta Y', str(convert_units(doc, calculate_Horizontal_Distance(basept, survpt)[2]))], - ['Horizontal Distance between Project Base Point and Survey Point', str(convert_units(doc, calculate_Horizontal_Distance(basept, survpt)[0]))], - ['Project Elevation', str(convert_units(doc, (survpt[2] - basept[2])))]] - + output.print_md(GOOD_STRG + + 'Survey Point is less than ' + CRITERIA_STRG) + basptdistance = calculate_distance(baspt, INTERNAL_ORIGIN) + # ____________________________________Calculate the angle between the points + baseptangle = calculate_angle(baspt, INTERNAL_ORIGIN) + surveyptangle = calculate_angle(survpt, INTERNAL_ORIGIN) + truenorthangle = calculate_angle(baspt, survpt) + # _______________________________Print the Project Coordinates and Distances + tbdata = [['Internal Origin Coordinates', + str(INTERNAL_ORIGIN[0]),str(INTERNAL_ORIGIN[1]),str(INTERNAL_ORIGIN[2]), + ' ', ' '], + ['Project Base Point Coordinates to Internal Origin', + str(convert_values(baspt[0],doc)), + str(convert_values(baspt[1],doc)), + str(convert_values(baspt[2], doc)), + str(convert_units(basptdistance, document)), baseptangle], + ['Survey Point Coordinates to Internal Origin', + str(convert_values(survpt[0], doc)), + str(convert_values(survpt[1], doc)), + str(convert_values(survpt[2], doc)), + str(convert_units(surveydistance, document)), surveyptangle], + ['Project Base Point to Survey Delta X', ' ', ' ', ' ', + str(convert_units(calculate_horizontal_distance( + baspt, survpt)[1], document))], + ['Project Base Point to Survey Delta Y', ' ', ' ', ' ', + str(convert_units(calculate_horizontal_distance( + baspt, survpt)[2], document))], + ['Planar Distance between Base Point and Survey Point', + ' ', ' ', ' ', + str(convert_units(calculate_horizontal_distance( + baspt, survpt)[0], document)), + truenorthangle], + ['Total Distance between Base Point and Survey Point', + ' ', ' ', ' ', + str(convert_units(calculate_distance( + baspt, survpt), document)), + ' '], + ['Project Elevation', + ' ', ' ', str(convert_values(baspt[2], doc)), + str(convert_units((survpt[2] - baspt[2]), document))]] # Print Table - output.print_table(table_data=tabledata, - title='Project Coordinates and Distances', - columns=['Coordinates', 'Values'], - formats=['', '']) - - #__________________________________________Get the bounding box of the 3D view + output.print_table(table_data=tbdata, + title='Project Coordinates and Distances', + columns= + ['Coordinates', + ' X ', + ' Y ', + ' Z ', + ' Distance (' + str(unit_system) + ') ', + ' ANGLE (°) '], + formats=['', '' , '', '', '', '']) + # _______________________________________Get the bounding box of the 3D view print("") output.print_md('# Checking the extents of the 3D view bounding box') bbox_instance = Get3DViewBoundingBox() @@ -223,22 +314,27 @@ def check_model_extents(doc, output): min = (bbox.Min.X, bbox.Min.Y, bbox.Min.Z) max = (bbox.Max.X, bbox.Max.Y, bbox.Max.Z) print("") - print(stringseperator) + print(divider) print("") - if calculate_distance(min, intOrig) > extentdistance or calculate_distance(max, intOrig) > extentdistance: - output.print_md('### :thumbs_down_medium_skin_tone: ............3D View Bounding Box extends more than 10 miles (16KM) away from the Internal Origin.***') + if (calculate_distance(min, INTERNAL_ORIGIN) > EXTENT_DISTANCE or + calculate_distance(max, INTERNAL_ORIGIN) > EXTENT_DISTANCE): + output.print_md(BAD_STRG + + '3D View Bounding Box extends more than ' + CRITERIA_STRG) else: - output.print_md('### :OK_hand_medium_skin_tone: ............3D View Bounding Box is located less than 10 miles (16KM) away from the Internal Origin.***') - TestScore += 1 - - #__________________________________________Get Objects in Design Options + output.print_md(GOOD_STRG + + '3D View Bounding Box is located less than ' + CRITERIA_STRG) + test_score += 1 + # _____________________________________________Get Objects in Design Options print("") - print(stringseperator) + print(divider) output.print_md('# Checking the extents of the design option objects') - print(stringseperator) - design_option_objects = getalldesignoptionobjects(doc) + print(divider) + design_option_objects = get_design_options_elements(document) violating_design_option_objects = [] violating_options = [] + violating_option_sets = [] + option_set_param = DB.BuiltInParameter.OPTION_SET_ID + for x in design_option_objects[1]: for y in x: dbbox = y.get_BoundingBox(None) @@ -247,39 +343,55 @@ def check_model_extents(doc, output): else: dbmin = (dbbox.Min.X, dbbox.Min.Y, dbbox.Min.Z) dbmax = (dbbox.Max.X, dbbox.Max.Y, dbbox.Max.Z) - if calculate_distance(dbmin, intOrig) > extentdistance or calculate_distance(dbmax, intOrig) > extentdistance: - violating_design_option_objects.append(x) + if (calculate_distance(dbmin, INTERNAL_ORIGIN) > EXTENT_DISTANCE + or + calculate_distance(dbmax, INTERNAL_ORIGIN) > EXTENT_DISTANCE): + violating_design_option_objects.append(y) if y.DesignOption.Name not in violating_options: - violating_options.append(y.DesignOption.Name) + violating_options.append(y.DesignOption) + violating_option_sets.append( + y.DesignOption. + get_Parameter(option_set_param). + AsElementId()) if len(violating_design_option_objects) > 0: - output.print_md('### :thumbs_down_medium_skin_tone: ............Design Option Objects are located more than 10 miles (16KM) away from the Internal Origin.***') + output.print_md(BAD_STRG + + 'Design Option Objects are located more than ' + CRITERIA_STRG) if len(violating_design_option_objects) > 10: - output.print_md('### :warning: ............Showing the first 10 objects***') - output.print_md('### :warning: ............Manual investigation is required***') + output.print_md(WARN_STRG + 'Showing the first 10 objects') + output.print_md(WARN_STRG + 'Manual investigation is required') counter = 0 limit = 10 for x in violating_design_option_objects: + if counter > limit: + break + else: + setid = violating_option_sets[counter] + print(output.linkify(x.Id) + + " " + + str(x.Name) + + " - Is part of " + + str(doc.GetElement(setid).Name) + + " - " + + str(x.DesignOption.Name) + ) + counter += 1 - for y in x: - if counter == limit: - break - print(output.linkify(y.Id)+ str(y.Name)+ " - Is part of design option - "+ str(y.DesignOption.Name) ) - counter += 1 else: - output.print_md('### :OK_hand_medium_skin_tone: ............No object in any design option is located more than 10 miles (16KM) away from the Internal Origin.***') - TestScore += 1 - #__________________________________________Check Test Score - if TestScore >= 2: - output.print_md('### :OK_hand_medium_skin_tone: ............All Tests Passed.***') - sys.exit() + output.print_md(GOOD_STRG + + 'No object in any design option is located more than ' + CRITERIA_STRG) + test_score += 1 + # __________________________________________________________Check Test Score + if test_score >= 2: + output.print_md('GOOD_STRGAll Tests Passed.') + script.exit() else: - output.print_md('### :thumbs_down_medium_skin_tone: ............Distant objects detected, Proceeding with additional analysis') - - #__________________________________________Check CAD and RVT Links - print(stringseperator) + output.print_md(BAD_STRG + + 'Distant objects detected, Proceeding with additional analysis') + # ___________________________________________________Check CAD and RVT Links + print(divider) output.print_md('# Checking the extents of the CAD and RVT links') - print(stringseperator) - bboxLink = bbox_instance.get_tempbbox(1,1,1,0) + print(divider) + bboxLink = bbox_instance.get_tempbbox(1, 1, 1, 0) badcads = bboxLink[1] badrvts = bboxLink[2] cleanbbox = bboxLink[3] @@ -289,64 +401,75 @@ def check_model_extents(doc, output): limit = 5 if len(badcads) > 0 or len(badrvts) > 0: for x in badcads: - print(output.linkify(x.Id)+"__" + str(x.Name) + ' ' + str(x.Category.Name)) + print(output.linkify(x.Id)+"__" + + str(x.Name) + ' ' + str(x.Category.Name)) if counter == limit: break counter += 1 counter = 0 for x in badrvts: - print(output.linkify(x.Id)+"__" + str(x.Name) + ' ' + str(x.Category.Name)) + print(output.linkify(x.Id)+"__" + + str(x.Name) + ' ' + str(x.Category.Name)) if counter == limit: break counter += 1 else: - output.print_md('### :OK_hand_medium_skin_tone: ............All CAD and RVT Links are located less than 10 miles (16KM) away from the Internal Origin.***') - TestScore += 1 - print(stringseperator) - if analyzebbox(cleanbbox, intOrig, 5) == 0: - output.print_md('### :thumbs_down_medium_skin_tone: ............Distant objects are still being detected!') - output.print_md('### :warning: ............Further Analysis Required.') + output.print_md(GOOD_STRG + + 'All CAD and RVT Links are located less than ' + CRITERIA_STRG) + test_score += 1 + print(divider) + if check_bounding_box(cleanbbox, INTERNAL_ORIGIN, 5) == 0: + output.print_md(BAD_STRG + + 'Distant objects are still being detected!') + output.print_md(WARN_STRG + + 'Further Analysis Required.') else: - output.print_md('### :OK_hand_medium_skin_tone: ............All Objects are located less than 10 miles (16KM) away from the Internal Origin.***') - sys.exit() - print(stringseperator) + output.print_md(GOOD_STRG + + 'All Objects are located less than ' + CRITERIA_STRG) + script.exit() + print(divider) output.print_md('# Checking everything, It is going to take a while.') output.print_md('# Please be patient.') - #__________________________________________Check Bounding Box of Every Element in the Model - print(stringseperator) + # __________________________Check Bounding Box of Every Element in the Model + print(divider) getbadelements = bbox_instance.get_tempbbox(0,0,0,1) badelements = getbadelements[4] counter = 0 limit = 10 if len(badelements) > 0: if len(badelements) > limit: - output.print_md('### :warning: ............Showing the first 10 objects***') - output.print_md('### :warning: ............Manual investigation is required***') - output.print_md('### :thumbs_down_medium_skin_tone: ............Elements below are located more than 10 miles (16KM) away from the Internal Origin') + output.print_md(WARN_STRG + + 'Showing the first 10 objects') + output.print_md(WARN_STRG + + 'Manual investigation is required') + output.print_md(BAD_STRG + + 'Elements below are located more than ' + CRITERIA_STRG) for x in badelements: - print(output.linkify(x.Id)+ ' ' + str(x.Name) + ' ' + str(x.Category.Name)) + print(output.linkify(x.Id)+ ' ' + + str(x.Name) + ' ' + str(x.Category.Name)) if counter == limit: break counter += 1 else: - output.print_md('### :OK_hand_medium_skin_tone: ............All Objects are located less than 10 miles (16KM) away from the Internal Origin.***') - TestScore += 1 + output.print_md(GOOD_STRG + + 'All Objects are located less than ' + CRITERIA_STRG) + test_score += 1 -#______________________________________________Model Checker Class +# ______________________________________________Model Checker Class class ModelChecker(PreflightTestCase): """ Checks the extents of all elements in the model. -This Model Checker swiftly verifies the extents of the Revit model. -Placing model extents more than 10 miles (16KM) from the project's -internal origin can lead to issues with accuracy, tolerance, -performance, and viewport display. This check ensures that the -model remains within a 10-mile radius of the internal origin. + This Model Checker swiftly verifies the extents of the Revit model. + Placing model extents more than 10 Mi (16KM) from the project's + internal origin can lead to issues with accuracy, tolerance, + performance, and viewport display. This check ensures that the + model remains within a 10-mile radius of the internal origin. -The test case examines the following, reporting extents -concerning the project's internal origin. The script prioritizes -based on the assumption that most model extent issues are -related to the following: + The test case examines the following, reporting extents + concerning the project's internal origin. The script prioritizes + based on the assumption that most model extent issues are + related to the following: - The distance between project basepoint and internal origin - The distance between survey point and internal origin @@ -355,7 +478,6 @@ class ModelChecker(PreflightTestCase): - The bounding box of the CAD and RVT links - The bounding box of all elements in the model """ - name = "10 Mile Radar" author = "Tay Othman" @@ -363,10 +485,10 @@ def setUp(self, doc, output): pass def startTest(self, doc, output): - check_model_extents(doc, output) + check_model_extents(doc) def tearDown(self, doc, output): pass - + def doCleanups(self, doc, output): pass From d351df63366187cec040c4f42f422d6c4a0f3c91 Mon Sep 17 00:00:00 2001 From: Andreas Draxl Date: Tue, 29 Oct 2024 19:12:43 +0100 Subject: [PATCH 07/20] Create Naming_Convention_Check This code is based on a true story. Naming conventions are the foundation of a project. Naming is essential for model checks, filters, and view settings. Until now, I created my checks using Revit export text files with Power-BI. PyRevit enables continuous checking of all categories against their predefined names with just a click. --- .../checks/Naming_Convention | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 extensions/pyRevitTools.extension/checks/Naming_Convention diff --git a/extensions/pyRevitTools.extension/checks/Naming_Convention b/extensions/pyRevitTools.extension/checks/Naming_Convention new file mode 100644 index 000000000..c0bc3b8f4 --- /dev/null +++ b/extensions/pyRevitTools.extension/checks/Naming_Convention @@ -0,0 +1,318 @@ +# -*- coding: UTF-8 -*- +# pylint: disable=import-error,invalid-name,broad-except,superfluous-parens + +import datetime +from collections import Counter + +# Revit-specific imports +from pyrevit import coreutils, revit, script +from Autodesk.Revit.DB import BuiltInCategory, FilteredElementCollector + +# Set up preflight for model check +from pyrevit.preflight import PreflightTestCase + + +# Define allowed wall types OF YOUR PROJECT!!! +wall_list = ["Wand_STB_0250_IfcBeam", +"Wand_STB_0300", +"Wand_STB_0350_IfcBeam", +"Wand_STB_0250", +"Wand_STB_0300_IfcBeam", +"Wand_STB_0180", +"Wand_STB_0350", +"IW09b_Wand_GIP_0125_2-Fach_EI90", +"Wand_STB_0400", +"Wand_STB_0200", +"Daem_WDH_0060", +"Daem_WDH_0160", +"Daem_WDH_0085", +"IW08a_Wand_GIP_0125_2-Fach", +"IW05a_Schachttrennwand_GIP_0095_Typ A_Schachtwand", +"VS2a_VSS_GIP_0075_2fach", +"IW04b_Wand_GIP_0215_Unterricht_Schallschutz", +"IW05b_Schattrennwand_GIP_0075 Typ B_2-Fach_Trennwand", +"IW09f_Wand_GIP_0230_2-Fach_EI90", +"Wand_HWS_0030_WC-Trennwand", +"IW08_Wand_GIP_0100_2-Fach", +"IW04_Wand_GIP_0215_Unterricht_Schallschutz_REI", +"VS2b_VSS_GIP_0100_2-Fach", +"IW05c_Schattrennwand_GIP_0010_Typ C_2-Fach_Trennwand", +"Wand_GLA_0050_Profilglas", +"VS5_VSS_Nassraum W3_GIP_0175_2-Fach", +"VSS_GIP_0100_2-Fach_REI", +"VS5c_GIP_01625_2-Fach", +"VS2c_VSS_GIP_0125_2-Fach", +"IW011a_Schattrennwand_GIP_Typ B_ Fliesen_0300_2-Fach_Trennwand_doppelt", +"Innenwand_GIP_0175_2 Fach", +"Wand_STB_0450", +"IW09c_Wand_GIP_0150_EI90_2-Fach", +"IW011b_Schattrennwand_GIP_Nassraum W3 Typ b_0340_2-Fach_Trennwand_doppelt", +"VS5_VSS_Nassraum W3/W4_GIP_0150_2-Fach", +"IW14 GP_Wand_GIP_0125_2-Fach_Schallschutz_Silentboard Knauf", +"IW05e_Schattrennwand Typ B_0125_2-Fach_Trennwand", +"IW15_Schachttrennwand Typ A_VSS_GIP_1450_Schachtwand", +"Wand_STB_0600", +"IW08b_Wand_GIP_0150_2-Fach", +"Wand_GLA_0100_Profilglas", +"Wand_STB_0450_IfcFooting", +"Wand_STB_0300_IfcFooting", +"Wand_GIP_0225_2-Fach_Wohnungstrennwand", +"Wand_GIP_0230_2-Fach_Trennwand", +"IW09_Wand_GIP_0100_REI_2-Fach", +"Wand_STB_0350_IfcFooting", +"Wand_STB_0150", +"Wand_ZIE_0250", +"AW01_Daem_WDW_0244_MW_hinterluftet", +"Wand_STB_0400_IfcFooting", +"Wand_STB_0200_REI", +"VS2a_VSS_GIP_0125_2-Fach_WC", +"VS5_VSS_Nassraum W3/W4_GIP_0250_2-Fach", +"VS5b_VSS_Nassraum W3_GIP_0100_2-Fach", +"Wand_GIP_0180_2-Fach", +"Wand_WDW_Kühlhauspaneele_0100", +"Wand_STB_0250_IfcFooting", +"Wand_GIP_0100_1-Fach_Mobile Trennwand", +"Wand_STB_0700_IfcFooting", +"Wand_GIP_0240_2-Fach_Trennwand_einseitig", +"AW04_Daem_WDH_0090_gegen Erdreich (unbeheizt)", +"AW03_Daem_WDH_0170_gegen Erdreich (beheizt)", +"Fass_GLA_0130_PfostenRiegel_Fenster Stiegenhaus", +"Wand_GIP_0250_2-Fach_Trennwand_REI", +"VS2_VSS_GIP_0175_2-Fach", +"VS2_VSS_GIP_0150_2-Fach", +"IW12a_W5_Wand_GIP_0125_2-Fach", +"VS5c_VSS_Nassraum W3/W4_GIP_0125_2-Fach", +"AW01_Daem_WDW_0150_MW_hinterluftet", +"AW05_Daem_WDVS_0230", +"VSS_GIP_0225_WC", +"Wand_GIP_0250_2-Fach_Trennwand", +"IW12e_Schattrennwand_GIP_Typ B_CW100_2-Fach_Trennwand", +"Wand_GLA_0100_Profilglas_REI", +"IW12d_W5_Wand_GIP_0215", +"VS1a_VSS_GIP_0062,5", +"Fass_GLA_0250_PfostenRiegel_Raster_2000_EN", +"Fass_GLA_0250_PfostenRiegel (Mittlere)", +"Fass_GLA_0190_PfostenRiegel_Rundung", +"Fass_GLA_0250_PfostenRiegel_1300_EN", +"Fass_GLA_0130_PfostenRiegel_ALU_EN", +"VS5e_W5_VSS_GIP_0150_2-Fach", +"VS5_VSS_Nassraum W3_GIP_0220_2-Fach_WC", +"Wand_STB_0340_IfcFooting", +"Wand_GIP_0250_2-Fach_REI", +"Fass_GLA_0250_PfostenRiegel (Unter)", +"Wand_STB_0500", +"IW05c_Wand_WDW_Daem_GKB_0100_gegen umbeh. Massivbau", +"IW01_Wand_WDH_Daem_Tektalan_0100_gegen umbeh. Massivbau", +"Flie_FLI_0010_Fliesen", +"Daem_WDH_0180", +"Daem_WDH_0100", +"Fass_GLA_0130_PfostenRiegel_ALU", +"VS5c_GIP_0270_2-Fach", +"VS2_VSS_GIP_0200_2-Fach", +"VSS_GIP_0270_2-Fach", +"Wand_GIP_0165_2-Fach", +"VS2c_VSS_GIP_0175_2-Fach", +"IW05_VSS_GIP_0100_1-Fach_Schürze", +"Wand_STB_0430_Dämmung_Oberlicht", +"VSS_GIP_0250_2-Fach", +"IW_Wand_GIP_0200_2-Fach", +"VSS_WDW_0055", +"VSS_HWS_0200_Aku_Trikustik", +"WDVS_WDH_0160_EPS", +"IW05_VSS_GIP_0065_Schürze", +"Wand_STB_1000_IfcFooting", +"VS5d_W5_VSS_GIP_0100_2-Fach", +"Flie_FLI_0010_Teeküche", +"Wand_STB_0380", +"VS2_VSS_GIP_0250_2-Fach_WC", +"VS4b_VSS_HWS_0110", +"VS4c_VSS_HWS_0150", +"VS4a_VSS_HWS_0050", +"VS4a_VSS_HWS_0100", +"VS3b_VSS_HWS_AKUSTIK_Verkleidung_0200", +"VS3b_VSS_HWS_AKUSTIK_Verkleidung_0050", +"VS3c_VSS_HWS_AKUSTIK_Verkleidung_0150", +"VS3b_VSS_HWS_AKUSTIK_Verkleidung_0100", +"VS5a_Nassraum W3_Daem_WDW_GKB_0100_gegen umbeh. Massivbau", +"IW_Wand_GIP_0375_2-Fach", +"Wand_STB_0237", +"VS5a_VSS_Nassraum W3_GIP_0062,5", +"Daem_WDH_0120", +"AW05_Daem_WDVS_0130", +"Daem_WDH_0200", +"Wand_STB_0700", +"VSS_GIP_0090_2-Fach", +"VSS_GIP_0220_2-Fach_WC", +"VSS_GIP_0420_2-Fach", +"Wand_STB_0120", +"IW14_Wand_GIP_0125_3-Fach", +"VS2a_VSS_GIP_0125_2-Fach", +"IW12c_W5_EI90_Brandschutzwand_GIP_0150_2-Fach", +"AW0-_Daem_WDW_0250_MW_hinterluftet xx", +"Wand_ZIE_0200", +"Wand_GIP_0040_Promat EI90", +"Wand_STB_0175", +"AW01_Daem_WDW_0100_MW_hinterluftet Grau", +"Wand_GLA_0050_Profilglas_REI", +"Wand_GIT", +"VSS_GIP_0350_2-Fach", +"IW13a_Wand_ZIE_YTONG_Fliesen_0100", +"Wand_GIP_0395_2-Fach", +"Wand_STB_0750_IfcFooting", +"VSS_GIP_0320_W3Nassraum _S_2-Fach", +"VS5_VSS_Nassraum W3_GIP_0200_2-Fach", +"IW06_Wand_GIP_01125_1-Fach", +"Daem_WDH_0250", +"Gela_GIT_Lamellen_0050x0080", +"VS5c_VSS_Nassraum W3_GIP_0175_2-Fach", +"VSS_GIP_0240_2-Fach_WC", +"VS6a_VSS_AKUSTIK_Daemmung_0075", +"VSS_GIP_0180_2-Fach", +"VS2_VSS_GIP_0260_2-Fach", +"VSS_GIP_003", +"Wand_GIP_0025_Promat 2-fach EI90", +"Daem_WDH_0150", +"VSS_GIP_005", +"Wand_BLE_0025_Blech", +"Wand_STB_0400_Randstein_Sitzauflage", +"Wand_ZIE_0180", +"VS1b_VSS_GIP_01250", +"IW06_Wand_GIP_0075_1-Fach", +"VS2_VSS_GIP_0350_Schürze", +"VS5e_W5_VSS_GIP_0175_2-Fach", +"IW05_VSS_GIP_0065_Kanal", +"VS4_VSS_HWS_0040", +"Fass_GLA_Portal_STGH4", +"Flie_FLI_0020_30x60", +"IW_Wand_GIP_0220_2-Fach", +"Wand_STB_0150_Blumentrog", +"AW0-_Daem_WDW_0100_MW_xx", +"IW06_Wand_GIP_0875_1-Fach", +"Wand_GIP_0125_2-Fach xx", +"VSS_GIP_025", +"VSS_WDW_0050", +"IW13_Wand_ZIE_YTONG_0100", +"IW06_Wand_GIP_0050_1-Fach_Trennwand", +"Fass_GLA_0250_PfostenRiegel (Leer)", +"AW-_Wand_HWS_Wand_Overtec_0050", +"VSS_HWS_0030", +"Werkstoffplatte_HWS_0040_furniert", +"VSS_GIP_0125_2-Fach", +"AW-_Wand_HWS_Wand_Overtec_0060", +"IW_Schachttrennwand_GIP_1450_Typ A_Schachtwand Fensterband", +"Wand_STB_0100", +"Wand_STB_1000", +"Daem_WDH_0080", +"Wand_GIP_0130_2-Fach_EI90", +"Wand_MACUPHON_0200", +"AW0-_Daem_WDW_0060_MW", +"Wand_STB_2820", +"Wand_STB_1300", +"Wand_STB_0800", +"Wand_BLE_Alucubon_007", +"Fass_GLA_0130_PfostenRiegel_Fenster_UG2_Stiegenhaus", +"Werkstoffplatte_0040_furniert_braun", +"Wand_HOL_0500_Klappbares Element", +"Schiebewände Paket Festsaal", +"VS2a_VSS_GIP_0205_mit Luftraum schleuse", +"VS2_VSS_GIP_0225_2-Fach", +"Wand_ST_0040_Stahlwinkel Attika", +"Wand_MACUPHON_0250", +"Wand_Holzpaneel_0030_Kellertrennwand", +"VSS_GIP_0400", +"Wand_ZIE_0150", +"Wände_special_1", +"WT_STB_25,0", +"Wand_GLA_0020_glas", +"Wand_GIP_0025_ 2-fach", +"AW_Windfang_TB", +"AW_Windfang_Blech", +"LÖSCHEN IW10e_Innenwand_GIP_0100_Fliesen Einseitig_2-Fach"] + +# Define function to display text in red +def print_red(output, text): + output.print_html('
{}
'.format(text)) # FONT-COLOR + +# Function to check the model's wall naming conventions +def check_model(doc, output): + """ + Checks if wall types in the model match the allowed wall names list. + Displays summary with correct and incorrect wall names. + """ + output.print_md('# Model Naming Convention Report') + + # Get all wall elements and their names + walls = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls).WhereElementIsNotElementType().ToElements() + wall_names = [wall.Name for wall in walls] + + # Count occurrences of each wall name + wall_list_counts = Counter(wall_names) + + # Initialize results dictionary for found and wrong wall types + wall_comparison_result = { + "found": {wall_type: count for wall_type, count in wall_list_counts.items() if wall_type in wall_list}, + "wrong": [wall_type for wall_type in wall_list_counts if wall_type not in wall_list] + } + + # Prepare data for output table + data = [ + (wall_type, count, "Wrong Name" if wall_type in wall_comparison_result["wrong"] else "") + for wall_type, count in wall_list_counts.items() + ] + + # Print table and highlight incorrect wall names + output.print_table( + table_data=data, + title="Naming Convention Wall Check", + columns=["Wall Type", "Count", "Status"], + formats=['', '{}', ''] + ) + + if wall_comparison_result["wrong"]: + output.print_md('## Incorrectly Named Wall Types Found:') + for wrong in wall_comparison_result["wrong"]: + print_red(output, wrong) + + +class ModelChecker(PreflightTestCase): + """ + Verifies whether family type names conform to a specified list, + as defined by a wall type list within Revit. + """ + + name = "Naming Convention" + author = "Andreas Draxl" + + def setUp(self, doc, output): + pass + + def startTest(self, doc, output): + timer = coreutils.Timer() + check_model(doc, output) + endtime = timer.get_time() + endtime_hms = str(datetime.timedelta(seconds=endtime)) + print("Transaction took {}".format(endtime_hms)) + + def tearDown(self, doc, output): + pass + + def doCleanups(self, doc, output): + pass + + +# Initialize variables +doc = __revit__.ActiveUIDocument.Document +output = script.get_output() + +# Start model checker +if __name__ == "__main__": + checker = ModelChecker() + checker.startTest(doc, output) + + + + + + + + From 700534ceebef05b8c330719eee9306d022191a00 Mon Sep 17 00:00:00 2001 From: "tay.0" Date: Tue, 29 Oct 2024 23:58:39 -0700 Subject: [PATCH 08/20] Update radar_check.py - Rounding Coordinates Added basic rounding for coordinates for better readability, --- extensions/pyRevitTools.extension/checks/radar_check.py | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/pyRevitTools.extension/checks/radar_check.py b/extensions/pyRevitTools.extension/checks/radar_check.py index 0ae45e202..2ea963890 100644 --- a/extensions/pyRevitTools.extension/checks/radar_check.py +++ b/extensions/pyRevitTools.extension/checks/radar_check.py @@ -113,6 +113,7 @@ def convert_values(value, document=doc): .DisplayUnits) ui_values = DB.UnitUtils.ConvertFromInternalUnits(value, ui_units) + ui_values = round(ui_values, 3) return ui_values From 2f6f786ef7afb3b6b39997f83156f4ccb15f1471 Mon Sep 17 00:00:00 2001 From: Andreas Draxl Date: Wed, 30 Oct 2024 18:51:22 +0100 Subject: [PATCH 09/20] Create wall_list.json A template for a loadable .json file. Just a list of strings. --- wall_list.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 wall_list.json diff --git a/wall_list.json b/wall_list.json new file mode 100644 index 000000000..4cdb41ecc --- /dev/null +++ b/wall_list.json @@ -0,0 +1,10 @@ +{ + "allowed_wall_types": [ + "Wand_STB_0250_IfcBeam", + "Wand_STB_0300", + "Wand_STB_0350_IfcBeam", + "Wand_STB_0250", + "Wand_STB_0300_IfcBeam", + "Any wall" + ] +} From 05b70522b08e8474277f4a1c0cb25bdd1f3b1512 Mon Sep 17 00:00:00 2001 From: Andreas Draxl Date: Wed, 30 Oct 2024 19:05:35 +0100 Subject: [PATCH 10/20] Update and rename Naming_Convention to Naming_Convention_Check.py update extionsion.py added. file.json template list-template added. --- .../checks/Naming_Convention | 318 ------------------ .../checks/Naming_Convention_Check.py | 106 ++++++ 2 files changed, 106 insertions(+), 318 deletions(-) delete mode 100644 extensions/pyRevitTools.extension/checks/Naming_Convention create mode 100644 extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py diff --git a/extensions/pyRevitTools.extension/checks/Naming_Convention b/extensions/pyRevitTools.extension/checks/Naming_Convention deleted file mode 100644 index c0bc3b8f4..000000000 --- a/extensions/pyRevitTools.extension/checks/Naming_Convention +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: UTF-8 -*- -# pylint: disable=import-error,invalid-name,broad-except,superfluous-parens - -import datetime -from collections import Counter - -# Revit-specific imports -from pyrevit import coreutils, revit, script -from Autodesk.Revit.DB import BuiltInCategory, FilteredElementCollector - -# Set up preflight for model check -from pyrevit.preflight import PreflightTestCase - - -# Define allowed wall types OF YOUR PROJECT!!! -wall_list = ["Wand_STB_0250_IfcBeam", -"Wand_STB_0300", -"Wand_STB_0350_IfcBeam", -"Wand_STB_0250", -"Wand_STB_0300_IfcBeam", -"Wand_STB_0180", -"Wand_STB_0350", -"IW09b_Wand_GIP_0125_2-Fach_EI90", -"Wand_STB_0400", -"Wand_STB_0200", -"Daem_WDH_0060", -"Daem_WDH_0160", -"Daem_WDH_0085", -"IW08a_Wand_GIP_0125_2-Fach", -"IW05a_Schachttrennwand_GIP_0095_Typ A_Schachtwand", -"VS2a_VSS_GIP_0075_2fach", -"IW04b_Wand_GIP_0215_Unterricht_Schallschutz", -"IW05b_Schattrennwand_GIP_0075 Typ B_2-Fach_Trennwand", -"IW09f_Wand_GIP_0230_2-Fach_EI90", -"Wand_HWS_0030_WC-Trennwand", -"IW08_Wand_GIP_0100_2-Fach", -"IW04_Wand_GIP_0215_Unterricht_Schallschutz_REI", -"VS2b_VSS_GIP_0100_2-Fach", -"IW05c_Schattrennwand_GIP_0010_Typ C_2-Fach_Trennwand", -"Wand_GLA_0050_Profilglas", -"VS5_VSS_Nassraum W3_GIP_0175_2-Fach", -"VSS_GIP_0100_2-Fach_REI", -"VS5c_GIP_01625_2-Fach", -"VS2c_VSS_GIP_0125_2-Fach", -"IW011a_Schattrennwand_GIP_Typ B_ Fliesen_0300_2-Fach_Trennwand_doppelt", -"Innenwand_GIP_0175_2 Fach", -"Wand_STB_0450", -"IW09c_Wand_GIP_0150_EI90_2-Fach", -"IW011b_Schattrennwand_GIP_Nassraum W3 Typ b_0340_2-Fach_Trennwand_doppelt", -"VS5_VSS_Nassraum W3/W4_GIP_0150_2-Fach", -"IW14 GP_Wand_GIP_0125_2-Fach_Schallschutz_Silentboard Knauf", -"IW05e_Schattrennwand Typ B_0125_2-Fach_Trennwand", -"IW15_Schachttrennwand Typ A_VSS_GIP_1450_Schachtwand", -"Wand_STB_0600", -"IW08b_Wand_GIP_0150_2-Fach", -"Wand_GLA_0100_Profilglas", -"Wand_STB_0450_IfcFooting", -"Wand_STB_0300_IfcFooting", -"Wand_GIP_0225_2-Fach_Wohnungstrennwand", -"Wand_GIP_0230_2-Fach_Trennwand", -"IW09_Wand_GIP_0100_REI_2-Fach", -"Wand_STB_0350_IfcFooting", -"Wand_STB_0150", -"Wand_ZIE_0250", -"AW01_Daem_WDW_0244_MW_hinterluftet", -"Wand_STB_0400_IfcFooting", -"Wand_STB_0200_REI", -"VS2a_VSS_GIP_0125_2-Fach_WC", -"VS5_VSS_Nassraum W3/W4_GIP_0250_2-Fach", -"VS5b_VSS_Nassraum W3_GIP_0100_2-Fach", -"Wand_GIP_0180_2-Fach", -"Wand_WDW_Kühlhauspaneele_0100", -"Wand_STB_0250_IfcFooting", -"Wand_GIP_0100_1-Fach_Mobile Trennwand", -"Wand_STB_0700_IfcFooting", -"Wand_GIP_0240_2-Fach_Trennwand_einseitig", -"AW04_Daem_WDH_0090_gegen Erdreich (unbeheizt)", -"AW03_Daem_WDH_0170_gegen Erdreich (beheizt)", -"Fass_GLA_0130_PfostenRiegel_Fenster Stiegenhaus", -"Wand_GIP_0250_2-Fach_Trennwand_REI", -"VS2_VSS_GIP_0175_2-Fach", -"VS2_VSS_GIP_0150_2-Fach", -"IW12a_W5_Wand_GIP_0125_2-Fach", -"VS5c_VSS_Nassraum W3/W4_GIP_0125_2-Fach", -"AW01_Daem_WDW_0150_MW_hinterluftet", -"AW05_Daem_WDVS_0230", -"VSS_GIP_0225_WC", -"Wand_GIP_0250_2-Fach_Trennwand", -"IW12e_Schattrennwand_GIP_Typ B_CW100_2-Fach_Trennwand", -"Wand_GLA_0100_Profilglas_REI", -"IW12d_W5_Wand_GIP_0215", -"VS1a_VSS_GIP_0062,5", -"Fass_GLA_0250_PfostenRiegel_Raster_2000_EN", -"Fass_GLA_0250_PfostenRiegel (Mittlere)", -"Fass_GLA_0190_PfostenRiegel_Rundung", -"Fass_GLA_0250_PfostenRiegel_1300_EN", -"Fass_GLA_0130_PfostenRiegel_ALU_EN", -"VS5e_W5_VSS_GIP_0150_2-Fach", -"VS5_VSS_Nassraum W3_GIP_0220_2-Fach_WC", -"Wand_STB_0340_IfcFooting", -"Wand_GIP_0250_2-Fach_REI", -"Fass_GLA_0250_PfostenRiegel (Unter)", -"Wand_STB_0500", -"IW05c_Wand_WDW_Daem_GKB_0100_gegen umbeh. Massivbau", -"IW01_Wand_WDH_Daem_Tektalan_0100_gegen umbeh. Massivbau", -"Flie_FLI_0010_Fliesen", -"Daem_WDH_0180", -"Daem_WDH_0100", -"Fass_GLA_0130_PfostenRiegel_ALU", -"VS5c_GIP_0270_2-Fach", -"VS2_VSS_GIP_0200_2-Fach", -"VSS_GIP_0270_2-Fach", -"Wand_GIP_0165_2-Fach", -"VS2c_VSS_GIP_0175_2-Fach", -"IW05_VSS_GIP_0100_1-Fach_Schürze", -"Wand_STB_0430_Dämmung_Oberlicht", -"VSS_GIP_0250_2-Fach", -"IW_Wand_GIP_0200_2-Fach", -"VSS_WDW_0055", -"VSS_HWS_0200_Aku_Trikustik", -"WDVS_WDH_0160_EPS", -"IW05_VSS_GIP_0065_Schürze", -"Wand_STB_1000_IfcFooting", -"VS5d_W5_VSS_GIP_0100_2-Fach", -"Flie_FLI_0010_Teeküche", -"Wand_STB_0380", -"VS2_VSS_GIP_0250_2-Fach_WC", -"VS4b_VSS_HWS_0110", -"VS4c_VSS_HWS_0150", -"VS4a_VSS_HWS_0050", -"VS4a_VSS_HWS_0100", -"VS3b_VSS_HWS_AKUSTIK_Verkleidung_0200", -"VS3b_VSS_HWS_AKUSTIK_Verkleidung_0050", -"VS3c_VSS_HWS_AKUSTIK_Verkleidung_0150", -"VS3b_VSS_HWS_AKUSTIK_Verkleidung_0100", -"VS5a_Nassraum W3_Daem_WDW_GKB_0100_gegen umbeh. Massivbau", -"IW_Wand_GIP_0375_2-Fach", -"Wand_STB_0237", -"VS5a_VSS_Nassraum W3_GIP_0062,5", -"Daem_WDH_0120", -"AW05_Daem_WDVS_0130", -"Daem_WDH_0200", -"Wand_STB_0700", -"VSS_GIP_0090_2-Fach", -"VSS_GIP_0220_2-Fach_WC", -"VSS_GIP_0420_2-Fach", -"Wand_STB_0120", -"IW14_Wand_GIP_0125_3-Fach", -"VS2a_VSS_GIP_0125_2-Fach", -"IW12c_W5_EI90_Brandschutzwand_GIP_0150_2-Fach", -"AW0-_Daem_WDW_0250_MW_hinterluftet xx", -"Wand_ZIE_0200", -"Wand_GIP_0040_Promat EI90", -"Wand_STB_0175", -"AW01_Daem_WDW_0100_MW_hinterluftet Grau", -"Wand_GLA_0050_Profilglas_REI", -"Wand_GIT", -"VSS_GIP_0350_2-Fach", -"IW13a_Wand_ZIE_YTONG_Fliesen_0100", -"Wand_GIP_0395_2-Fach", -"Wand_STB_0750_IfcFooting", -"VSS_GIP_0320_W3Nassraum _S_2-Fach", -"VS5_VSS_Nassraum W3_GIP_0200_2-Fach", -"IW06_Wand_GIP_01125_1-Fach", -"Daem_WDH_0250", -"Gela_GIT_Lamellen_0050x0080", -"VS5c_VSS_Nassraum W3_GIP_0175_2-Fach", -"VSS_GIP_0240_2-Fach_WC", -"VS6a_VSS_AKUSTIK_Daemmung_0075", -"VSS_GIP_0180_2-Fach", -"VS2_VSS_GIP_0260_2-Fach", -"VSS_GIP_003", -"Wand_GIP_0025_Promat 2-fach EI90", -"Daem_WDH_0150", -"VSS_GIP_005", -"Wand_BLE_0025_Blech", -"Wand_STB_0400_Randstein_Sitzauflage", -"Wand_ZIE_0180", -"VS1b_VSS_GIP_01250", -"IW06_Wand_GIP_0075_1-Fach", -"VS2_VSS_GIP_0350_Schürze", -"VS5e_W5_VSS_GIP_0175_2-Fach", -"IW05_VSS_GIP_0065_Kanal", -"VS4_VSS_HWS_0040", -"Fass_GLA_Portal_STGH4", -"Flie_FLI_0020_30x60", -"IW_Wand_GIP_0220_2-Fach", -"Wand_STB_0150_Blumentrog", -"AW0-_Daem_WDW_0100_MW_xx", -"IW06_Wand_GIP_0875_1-Fach", -"Wand_GIP_0125_2-Fach xx", -"VSS_GIP_025", -"VSS_WDW_0050", -"IW13_Wand_ZIE_YTONG_0100", -"IW06_Wand_GIP_0050_1-Fach_Trennwand", -"Fass_GLA_0250_PfostenRiegel (Leer)", -"AW-_Wand_HWS_Wand_Overtec_0050", -"VSS_HWS_0030", -"Werkstoffplatte_HWS_0040_furniert", -"VSS_GIP_0125_2-Fach", -"AW-_Wand_HWS_Wand_Overtec_0060", -"IW_Schachttrennwand_GIP_1450_Typ A_Schachtwand Fensterband", -"Wand_STB_0100", -"Wand_STB_1000", -"Daem_WDH_0080", -"Wand_GIP_0130_2-Fach_EI90", -"Wand_MACUPHON_0200", -"AW0-_Daem_WDW_0060_MW", -"Wand_STB_2820", -"Wand_STB_1300", -"Wand_STB_0800", -"Wand_BLE_Alucubon_007", -"Fass_GLA_0130_PfostenRiegel_Fenster_UG2_Stiegenhaus", -"Werkstoffplatte_0040_furniert_braun", -"Wand_HOL_0500_Klappbares Element", -"Schiebewände Paket Festsaal", -"VS2a_VSS_GIP_0205_mit Luftraum schleuse", -"VS2_VSS_GIP_0225_2-Fach", -"Wand_ST_0040_Stahlwinkel Attika", -"Wand_MACUPHON_0250", -"Wand_Holzpaneel_0030_Kellertrennwand", -"VSS_GIP_0400", -"Wand_ZIE_0150", -"Wände_special_1", -"WT_STB_25,0", -"Wand_GLA_0020_glas", -"Wand_GIP_0025_ 2-fach", -"AW_Windfang_TB", -"AW_Windfang_Blech", -"LÖSCHEN IW10e_Innenwand_GIP_0100_Fliesen Einseitig_2-Fach"] - -# Define function to display text in red -def print_red(output, text): - output.print_html('
{}
'.format(text)) # FONT-COLOR - -# Function to check the model's wall naming conventions -def check_model(doc, output): - """ - Checks if wall types in the model match the allowed wall names list. - Displays summary with correct and incorrect wall names. - """ - output.print_md('# Model Naming Convention Report') - - # Get all wall elements and their names - walls = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls).WhereElementIsNotElementType().ToElements() - wall_names = [wall.Name for wall in walls] - - # Count occurrences of each wall name - wall_list_counts = Counter(wall_names) - - # Initialize results dictionary for found and wrong wall types - wall_comparison_result = { - "found": {wall_type: count for wall_type, count in wall_list_counts.items() if wall_type in wall_list}, - "wrong": [wall_type for wall_type in wall_list_counts if wall_type not in wall_list] - } - - # Prepare data for output table - data = [ - (wall_type, count, "Wrong Name" if wall_type in wall_comparison_result["wrong"] else "") - for wall_type, count in wall_list_counts.items() - ] - - # Print table and highlight incorrect wall names - output.print_table( - table_data=data, - title="Naming Convention Wall Check", - columns=["Wall Type", "Count", "Status"], - formats=['', '{}', ''] - ) - - if wall_comparison_result["wrong"]: - output.print_md('## Incorrectly Named Wall Types Found:') - for wrong in wall_comparison_result["wrong"]: - print_red(output, wrong) - - -class ModelChecker(PreflightTestCase): - """ - Verifies whether family type names conform to a specified list, - as defined by a wall type list within Revit. - """ - - name = "Naming Convention" - author = "Andreas Draxl" - - def setUp(self, doc, output): - pass - - def startTest(self, doc, output): - timer = coreutils.Timer() - check_model(doc, output) - endtime = timer.get_time() - endtime_hms = str(datetime.timedelta(seconds=endtime)) - print("Transaction took {}".format(endtime_hms)) - - def tearDown(self, doc, output): - pass - - def doCleanups(self, doc, output): - pass - - -# Initialize variables -doc = __revit__.ActiveUIDocument.Document -output = script.get_output() - -# Start model checker -if __name__ == "__main__": - checker = ModelChecker() - checker.startTest(doc, output) - - - - - - - - diff --git a/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py b/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py new file mode 100644 index 000000000..96836d71f --- /dev/null +++ b/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py @@ -0,0 +1,106 @@ +# -*- coding: UTF-8 -*- +# pylint: disable=import-error,invalid-name,broad-except,superfluous-parens + +import datetime +import json +from collections import Counter + +# Revit-specific imports +from pyrevit import coreutils, revit, script +from Autodesk.Revit.DB import BuiltInCategory, FilteredElementCollector + +# Set up preflight for model check +from pyrevit.preflight import PreflightTestCase + +# Load wall list from JSON file +import os +import json + +def load_wall_list(): + json_file_path = r"C:\Users\andre\Documents\checks\wall_list.json" + with open(json_file_path, "r") as f: + data = json.load(f) + return data["allowed_wall_types"] + + +# Define function to display text in red +def print_red(output, text): + output.print_html('
{}
'.format(text)) + +# Function to check the model's wall naming conventions +def check_model(doc, output): + """ + Checks if wall types in the model match the allowed wall names list. + Displays summary with correct and incorrect wall names. + """ + output.print_md('# Model Naming Convention Report') + + # Get all wall elements and their names + walls = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls).WhereElementIsNotElementType().ToElements() + wall_names = [wall.Name for wall in walls] + + # Count occurrences of each wall name + wall_list_counts = Counter(wall_names) + + # Load wall list from JSON + wall_list = load_wall_list() + + # Initialize results dictionary for found and wrong wall types + wall_comparison_result = { + "found": {wall_type: count for wall_type, count in wall_list_counts.items() if wall_type in wall_list}, + "wrong": [wall_type for wall_type in wall_list_counts if wall_type not in wall_list] + } + + # Prepare data for output table + data = [ + (wall_type, count, "Wrong Name" if wall_type in wall_comparison_result["wrong"] else "") + for wall_type, count in wall_list_counts.items() + ] + + # Print table and highlight incorrect wall names + output.print_table( + table_data=data, + title="Naming Convention Wall Check", + columns=["Wall Type", "Count", "Status"], + formats=['', '{}', ''] + ) + + if wall_comparison_result["wrong"]: + output.print_md('## Incorrectly Named Wall Types Found:') + for wrong in wall_comparison_result["wrong"]: + print_red(output, wrong) + + +class ModelChecker(PreflightTestCase): + """ + Verifies whether family type names conform to a specified list, + as defined by a wall type list within Revit. + """ + + name = "Naming Convention" + author = "Andreas Draxl" + + def setUp(self, doc, output): + pass + + def startTest(self, doc, output): + timer = coreutils.Timer() + check_model(doc, output) + endtime = timer.get_time() + endtime_hms = str(datetime.timedelta(seconds=endtime)) + print("Transaction took {}".format(endtime_hms)) + + def tearDown(self, doc, output): + pass + + def doCleanups(self, doc, output): + pass + +# Initialize variables +doc = __revit__.ActiveUIDocument.Document +output = script.get_output() + +# Start model checker +if __name__ == "__main__": + checker = ModelChecker() + checker.startTest(doc, output) From 91ea604f59ed6752d06b60a77443de0d848cd7e6 Mon Sep 17 00:00:00 2001 From: Andreas Draxl Date: Wed, 30 Oct 2024 19:53:13 +0100 Subject: [PATCH 11/20] Update Naming_Convention_Check.py replace static path by select_file(*.json) --- .../pyRevitTools.extension/checks/Naming_Convention_Check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py b/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py index 96836d71f..0452266ae 100644 --- a/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py +++ b/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py @@ -17,7 +17,7 @@ import json def load_wall_list(): - json_file_path = r"C:\Users\andre\Documents\checks\wall_list.json" + json_file_path = select_file("JSON Files (*.json)|*.json") with open(json_file_path, "r") as f: data = json.load(f) return data["allowed_wall_types"] From 3626f35bf44d5a4d432abab12171ef160e83c4f6 Mon Sep 17 00:00:00 2001 From: Andreas Draxl Date: Wed, 30 Oct 2024 19:55:07 +0100 Subject: [PATCH 12/20] Update Naming_Convention_Check.py add rpw module --- .../pyRevitTools.extension/checks/Naming_Convention_Check.py | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py b/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py index 0452266ae..3b7f611e4 100644 --- a/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py +++ b/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py @@ -15,6 +15,7 @@ # Load wall list from JSON file import os import json +from rpw.ui.forms import select_file def load_wall_list(): json_file_path = select_file("JSON Files (*.json)|*.json") From a24cb36029912cc3b6ff8a1a3bc1726f3288004c Mon Sep 17 00:00:00 2001 From: Andreas Draxl Date: Wed, 30 Oct 2024 20:01:49 +0100 Subject: [PATCH 13/20] Update Naming_Convention_Check.py add a default path, just for having more confort --- .../checks/Naming_Convention_Check.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py b/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py index 3b7f611e4..6441625c0 100644 --- a/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py +++ b/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py @@ -18,9 +18,20 @@ from rpw.ui.forms import select_file def load_wall_list(): - json_file_path = select_file("JSON Files (*.json)|*.json") + # Set default directory + default_dir = r"C:\Users\andre\Documents\checks\wall_list.json" + + # Open the file dialog with the default path + json_file_path = select_file("JSON Files (*.json)|*.json", default_dir) + + # Check if a file was selected + if not json_file_path: + raise FileNotFoundError("No JSON file selected.") + + # Load JSON data with open(json_file_path, "r") as f: data = json.load(f) + return data["allowed_wall_types"] From b70dccf911fbc3e715131ba7e0fefe177507b5ad Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Fri, 1 Nov 2024 09:46:56 +0100 Subject: [PATCH 14/20] Update and rename wall_list.json to extensions/pyRevitTools.extension/checks/wall_list.json --- .../pyRevitTools.extension/checks/wall_list.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename wall_list.json => extensions/pyRevitTools.extension/checks/wall_list.json (91%) diff --git a/wall_list.json b/extensions/pyRevitTools.extension/checks/wall_list.json similarity index 91% rename from wall_list.json rename to extensions/pyRevitTools.extension/checks/wall_list.json index 4cdb41ecc..ef435c160 100644 --- a/wall_list.json +++ b/extensions/pyRevitTools.extension/checks/wall_list.json @@ -5,6 +5,6 @@ "Wand_STB_0350_IfcBeam", "Wand_STB_0250", "Wand_STB_0300_IfcBeam", - "Any wall" + "Any wall" ] } From d6beb74b43a681e676626bbb6eec99070e3cc756 Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Mon, 4 Nov 2024 21:50:21 +0100 Subject: [PATCH 15/20] Rename Naming_Convention_Check.py to walltypes_naming_convention_check.py --- ...g_Convention_Check.py => walltypes_naming_convention_check.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename extensions/pyRevitTools.extension/checks/{Naming_Convention_Check.py => walltypes_naming_convention_check.py} (100%) diff --git a/extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py b/extensions/pyRevitTools.extension/checks/walltypes_naming_convention_check.py similarity index 100% rename from extensions/pyRevitTools.extension/checks/Naming_Convention_Check.py rename to extensions/pyRevitTools.extension/checks/walltypes_naming_convention_check.py From 6ef1beaa00047baef73853e5456dbe8e190938dc Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Mon, 4 Nov 2024 21:51:31 +0100 Subject: [PATCH 16/20] Rename wall_list.json to walltypes_list.json --- .../checks/{wall_list.json => walltypes_list.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename extensions/pyRevitTools.extension/checks/{wall_list.json => walltypes_list.json} (100%) diff --git a/extensions/pyRevitTools.extension/checks/wall_list.json b/extensions/pyRevitTools.extension/checks/walltypes_list.json similarity index 100% rename from extensions/pyRevitTools.extension/checks/wall_list.json rename to extensions/pyRevitTools.extension/checks/walltypes_list.json From da42ec6910ab0e99cafd2b2a0ce386e329d73af6 Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Mon, 4 Nov 2024 21:53:56 +0100 Subject: [PATCH 17/20] Update walltypes_naming_convention_check.py --- .../checks/walltypes_naming_convention_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/pyRevitTools.extension/checks/walltypes_naming_convention_check.py b/extensions/pyRevitTools.extension/checks/walltypes_naming_convention_check.py index 6441625c0..8790cefc7 100644 --- a/extensions/pyRevitTools.extension/checks/walltypes_naming_convention_check.py +++ b/extensions/pyRevitTools.extension/checks/walltypes_naming_convention_check.py @@ -19,7 +19,7 @@ def load_wall_list(): # Set default directory - default_dir = r"C:\Users\andre\Documents\checks\wall_list.json" + default_dir = r"%appdata%\pyRevit-Master\extensions\pyRevitTools.extension\checks\walltypes_list.json" # Open the file dialog with the default path json_file_path = select_file("JSON Files (*.json)|*.json", default_dir) From f9d66d8ca1043b244b7cd5639c880bb00dd26d8a Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Mon, 4 Nov 2024 21:59:38 +0100 Subject: [PATCH 18/20] Rename CADAUDIT_check.py to cad_audit_check.py --- .../checks/{CADAUDIT_check.py => cad_audit_check.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename extensions/pyRevitTools.extension/checks/{CADAUDIT_check.py => cad_audit_check.py} (100%) diff --git a/extensions/pyRevitTools.extension/checks/CADAUDIT_check.py b/extensions/pyRevitTools.extension/checks/cad_audit_check.py similarity index 100% rename from extensions/pyRevitTools.extension/checks/CADAUDIT_check.py rename to extensions/pyRevitTools.extension/checks/cad_audit_check.py From 06b79cded237cc6cc3dfb62525393c5ba1a9a3fa Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Mon, 4 Nov 2024 22:02:32 +0100 Subject: [PATCH 19/20] Update cad_audit_check.py --- extensions/pyRevitTools.extension/checks/cad_audit_check.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/extensions/pyRevitTools.extension/checks/cad_audit_check.py b/extensions/pyRevitTools.extension/checks/cad_audit_check.py index 3dfb9310e..24c897512 100644 --- a/extensions/pyRevitTools.extension/checks/cad_audit_check.py +++ b/extensions/pyRevitTools.extension/checks/cad_audit_check.py @@ -12,14 +12,13 @@ from datetime import timedelta # Used for timing the check doc = DOCS.doc -ac_view = __revit__.ActiveUIDocument.ActiveView # Get the active view +ac_view = doc.ActiveView # Definitions def collect_cadinstances(active_view_only): """ Collect ImportInstance class from whole model or from just active view """ - #cadinstances = DB.FilteredElementCollector(doc).OfClass(DB.ImportInstance).WhereElementIsNotElementType().ToElements() if active_view_only: cadinstances = DB.FilteredElementCollector(doc, ac_view.Id).OfClass(DB.ImportInstance).WhereElementIsNotElementType().ToElements() else: @@ -111,7 +110,7 @@ def checkModel(doc, output): output.set_title("CAD audit of model '{}'".format(doc.Title)) output.set_width (1700) - coll_mode = get_user_input()["active_view"] # user choice of collection mode + 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 From 9fd9c57950dc859e12dd2c45bf724827d94fc838 Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Mon, 4 Nov 2024 22:04:51 +0100 Subject: [PATCH 20/20] Update cad_audit_check.py --- extensions/pyRevitTools.extension/checks/cad_audit_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/pyRevitTools.extension/checks/cad_audit_check.py b/extensions/pyRevitTools.extension/checks/cad_audit_check.py index 24c897512..2c2a28021 100644 --- a/extensions/pyRevitTools.extension/checks/cad_audit_check.py +++ b/extensions/pyRevitTools.extension/checks/cad_audit_check.py @@ -2,6 +2,7 @@ # 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 @@ -27,8 +28,7 @@ def collect_cadinstances(active_view_only): if len(cadinstances) >0: return cadinstances, len(cadinstances) else: - print("No CAD instances found in the {}.".format("active view" if active_view_only else "model")) - script.exit() + 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)