Python Coding Etiquette - Tips and Tricks - Common Mistakes

Hello pyRevit community :slight_smile:

I´m at the very start of transforming all my dynamo graphs to python code.
So why not ask some python experts what I´m doing wrong, what could be done better, what has to be avoided, what i have to keep in mind. Especially, what can I do easyer by importing pyrevit methods.

So please let´s take a look at my first, working, larger script that runs the following tasks:

  • Filter Selection (Views/Legends/Schedules/Sectionlines)
  • Duplicate Views/Legends/Schedules
  • Places them on Sheets
  • Sets Types, Locations,…
  • Sets Detail Numbers
  • Read/Write csv log
  • Throw Taskdialogs

Am I importing properly?
Am I wasting time somewhere?
It took me forever to create my csv read/write method, and now i see pyRevit has this implemented. Should i switch to the pyRevit method? Are there other important pyRevit methods(?)/modules(?) I should use, that are a must have for every workflow?

Would really appreciate any help at getting better, don´t be kind! :smiley:

Kind Regards!

# -*- coding: utf-8 -*-

__doc__ = "TestText"
__title__ = "Duplicate"
__author__ = "Gerhard"

import io
import csv
import clr
import sys
import time

st = time.time()

from pyrevit import script
from pyrevit import forms
from pyrevit import EXEC_PARAMS

sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib')
import System
from System import Array
from System.Collections.Generic import *
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference("RevitServices")
import RevitServices

from Autodesk.Revit.UI import TaskDialog, TaskDialogCommonButtons, TaskDialogResult, TaskDialogCommandLinkId, TaskDialogIcon
from datetime import datetime, timedelta

from RevitServices.Persistence import DocumentManager 
from RevitServices.Transactions import TransactionManager 
clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")

import Autodesk 
from Autodesk.Revit import DB
from Autodesk.Revit.DB import (FamilySymbol,Viewport,  XYZ, UnitTypeId, UnitUtils, ViewDuplicateOption, SpecTypeId, FilteredElementCollector, ParameterValueProvider, Element, View, ViewType, ElementId, ElementParameterFilter, FilterElementIdRule, BuiltInParameter, BuiltInCategory)

doc = __revit__.ActiveUIDocument.Document
uiapp = DocumentManager.Instance.CurrentUIApplication 
app = uiapp.Application 
uidoc = uiapp.ActiveUIDocument

now = datetime.now()
StartTimeFormat = "%X"
StartDateformat = "%d.%m.%Y"
StartTime = now.strftime(StartTimeFormat)
StartDate = now.strftime(StartDateformat)
FileName = script.get_bundle_name()
RevitFileName = doc.Title
app = DocumentManager.Instance.CurrentUIApplication.Application
User = app.Username
LogFilePath = "C:\Users\...\Desktop\Test.csv"

def CurrentSelection():
	selid = uidoc.Selection.GetElementIds()
	return [doc.GetElement(id) for id in selid]
	
def FilterViewports():
	viewportlist = []
	for element in elements:
		if element.Category.Id == ElementId(BuiltInCategory.OST_Viewports):
			viewportlist.append(element)
	return viewportlist
	
def FilterSectionlines():
	sectionlineslist = []
	for element in elements:
		string = element.ToString()
		if element.Category.Id == ElementId(BuiltInCategory.OST_Viewers) and string == "Autodesk.Revit.DB.Element":
			sectionlineslist.append(element)
	return sectionlineslist
	
def FilterSchedules():
	outlist = []
	for element in elements:
		if element.Category.Id == ElementId(BuiltInCategory.OST_ScheduleGraphics):
			outlist.append(element)
	return outlist
	
def ScheduleViews():
	outlist = []
	for ScheduleGraphic in ScheduleGraphics:
		ScheduleView = doc.GetElement(ScheduleGraphic.ScheduleId)
		outlist.append(ScheduleView)
	return outlist
		
def ViewFromViewports():
	ViewFromViewportList = []
	for viewport in viewports:
		viewId = viewport.ViewId
		ViewFromViewportList.append(doc.GetElement(viewId))
	return ViewFromViewportList
			
def ViewFromSectionLine():
	viewlist = []
	views = FilteredElementCollector(doc).OfClass(View).ToElements()
	for element in elements:
		for view in views:
			if element.Name == view.Name:
				viewlist.append(view)
	return viewlist
	
def ViewportFromSectionLine():
	viewportlist = []
	viewports = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Viewports).WhereElementIsNotElementType().ToElements()
	for view in ViewFromSectionLine:
		viewId = view.Id
		for viewport in viewports:
			viewportId = viewport.ViewId
			if viewportId == viewId:
				viewportlist.append(viewport)
	return viewportlist
				
def DuplicateViews(views):
	myNewViews = []
	for v in views:
		if v.ViewType == ViewType.Legend:
			option = ViewDuplicateOption.WithDetailing
		elif FileName == "WithDetailing.pushbutton":
			option = ViewDuplicateOption.WithDetailing
		elif FileName == "Duplicate.pushbutton":
			option = ViewDuplicateOption.Duplicate 
		newview = v.Duplicate(option)
		myNewView = doc.GetElement(newview)
		myNewViews.append(myNewView)
	return myNewViews
	
def DuplicateSchedules(views):
	myNewViews = []
	option = ViewDuplicateOption.Duplicate
	for v in views:	
		newview = v.Duplicate(option)
		myNewView = doc.GetElement(newview)
		myNewViews.append(myNewView)
	return myNewViews

def DuplicateLegends():
	option = ViewDuplicateOption.WithDetailing
	myNewLegends = []
	for v in ViewFromViewports:
		newview = v.Duplicate(option)
		myNewLegend = doc.GetElement(newview)
		myNewLegends.append(myNewLegend)
	return myNewLegends
	
def ConvertUnits(num):
	unitType = SpecTypeId.Length
	currentDisplayUnits = doc.GetUnits().GetFormatOptions(unitType).GetUnitTypeId()
	return UnitUtils.Convert(float(num), UnitTypeId.Millimeters, currentDisplayUnits)
	
def Offset():
	Offset_X= ConvertUnits(20)
	Offset_Y= ConvertUnits(-20)
	OffsetVec = XYZ(Offset_X,Offset_Y,0)
	return OffsetVec
		
def CreateViewport(views,viewports):
	NewViewports = []
	sheets = [doc.GetElement(viewport.SheetId) for viewport in viewports]
	points = [viewport.GetBoxCenter() + Offset() for viewport in viewports]
	for s,v,p in zip(sheets,views,points):
		try:
			a = Viewport.Create(doc, s.Id,v.Id, p)
		except:
			a = "View is already placed"
		NewViewports.append(a)
	return NewViewports
	
def CreateScheduleInstance(views,viewports):
	NewViewports = []
	sheets = [doc.GetElement(viewport.OwnerViewId) for viewport in viewports]
	points = [viewport.Point + Offset() for viewport in viewports]
	for s,v,p in zip(sheets,views,points):
		try:
			a = ScheduleSheetInstance.Create(doc, s.Id,v.Id, p)
		except:
			a = "View is already placed"
		NewViewports.append(a)
	return NewViewports
		
def GetTypeId(viewports):
	ViewportTypeIds = []
	for viewport in viewports:
		TypeId = viewport.GetTypeId()
		ViewportTypeIds.append(TypeId)
	return ViewportTypeIds
		
def SetTypeId(Viewports,ViewportTypeIds):
	for Viewport,ViewportTypeId  in zip(Viewports, ViewportTypeIds):
		Viewport.ChangeTypeId(ViewportTypeId)
		
def GetViewTitleLocation(viewports):
	ViewTitleLocationList = []
	for viewport in viewports:
		location = viewport.LabelOffset
		ViewTitleLocationList.append(location)
	return ViewTitleLocationList

def SetViewTitleLocation(Viewports, ViewTitleLocations):
	for Viewport, ViewTitleLocation in zip(Viewports, ViewTitleLocations):
		Viewport.LabelOffset = ViewTitleLocation
		
def UserMessage():

	taskDialog = TaskDialog("Results")
	taskDialog.MainInstruction = "No View selected"
	taskDialog.MainContent = "Select a View on a Sheet before running"
	taskDialog.TitleAutoPrefix = False	
	taskDialog.MainIcon = TaskDialogIcon.TaskDialogIconInformation	
	taskDialog.CommonButtons = TaskDialogCommonButtons.Close #| TaskDialogCommonButtons.Yes	
	taskDialog.FooterText = "Help"
	userinput = taskDialog.Show()
	return userinput

def LOG_Entry():
	now = datetime.now()
	dateformat = "%d.%m.%Y"
	timeformat = "%X"
	Date = now.strftime(dateformat)
	Time = now.strftime(timeformat)
	
	LOG_Entry = Time+"\t"+Date+"\t"+User+"\t"+FileName+"\t"+RevitFileName
	return LOG_Entry
	
def Read_LOG():
    retry = 0
    max_retries=5
    while True:
        try:
            with io.open(LogFilePath,"r", encoding = "utf-8") as LOGfile:
				LOG = LOGfile.read().splitlines()
        except OSError:
            time.sleep(0.5)
            retry += 1
            if retry > max_retries:
                raise
        else:
            return LOG
		
def Write_LOG():
	LOG = Read_LOG()
	LOG_New_Entry = LOG_Entry()
	LOG.append(LOG_New_Entry)
	retry = 0
	max_retries=5
	while True:
		try:
			with io.open(LogFilePath,"w", encoding = "UTF8", newline='') as LOGfile:	
				for L in LOG:
					writer = csv.writer(LOGfile,delimiter="'")
					writer.writerow([L])
		except OSError:
			time.sleep(0.5)
			retry += 1
			if retry > max_retries:
				raise
		else:
			return LOG_New_Entry

			
def CurrentViewports_DetailNumbers(viewports):	
	CurrentViewports_DetailNumbers = []
	for viewport in viewports:
		SheetId = viewport.SheetId
		Sheet = doc.GetElement(SheetId)
	CurrentViewportIDs = Sheet.GetAllViewports()
	CurrentViewports= [doc.GetElement(CurrentViewportID) for CurrentViewportID in CurrentViewportIDs]
	for CurrentViewport in CurrentViewports:
		CurrentViewports_DetailNumber = CurrentViewport.get_Parameter(BuiltInParameter.VIEWPORT_DETAIL_NUMBER).AsString()
		try:
			integer=int(CurrentViewports_DetailNumber)
			CurrentViewports_DetailNumbers.append(integer)
		except:
			pass
	return CurrentViewports_DetailNumbers
	
def NewViewports_DetailNumbers():
	NewViewports_DetailNumbers = []
	CurrentViewports_DetailNumbers = []
	for NewViewport in NewViewports:
		NewViewports_DetailNumber = NewViewport.get_Parameter(BuiltInParameter.VIEWPORT_DETAIL_NUMBER).AsString()
		try:
			integer=int(NewViewports_DetailNumber)
			NewViewports_DetailNumbers.append(integer)
		except:
			pass
		
def CreateDetailNumbers():
	Final_DetailNumbers = []
	MaximumDetailNumber = int(max(CurrentViewports_DetailNumbers))
	n = 100
	numbers = []
	for i in range(MaximumDetailNumber+1, n+1):
  		numbers.append(i)
  	for i in numbers:
		if str(i) not in CurrentViewports_DetailNumbers:
			Final_DetailNumbers.append(str(i))
	return Final_DetailNumbers
		
def SetDetailNumbers():
	Final_DetailNumbers = CreateDetailNumbers()
	for V, D in zip(NewViewports, Final_DetailNumbers):
		set = V.get_Parameter(BuiltInParameter.VIEWPORT_DETAIL_NUMBER).Set(D)

elements = CurrentSelection()
viewports = FilterViewports()
sectionline = FilterSectionlines()
ViewFromSectionLine = ViewFromSectionLine()
ViewportFromSectionLine = ViewportFromSectionLine()
ScheduleGraphics = FilterSchedules()

t = DB.Transaction(doc, 'SampleText')

t.Start()

if viewports:
	CurrentViewports_DetailNumbers = CurrentViewports_DetailNumbers(viewports)
	ViewFromViewports = ViewFromViewports()
	DuplicatedViews = DuplicateViews(ViewFromViewports)
	NewViewports = CreateViewport(DuplicatedViews, viewports)
	ViewportTypeIds = GetTypeId(viewports)
	SetTypeId = SetTypeId(NewViewports,ViewportTypeIds)
	ViewTitleLocations = GetViewTitleLocation(viewports)
	SetViewTitleLocation = SetViewTitleLocation(NewViewports, ViewTitleLocations)
	SetDetailNumbers = SetDetailNumbers()
	
if ViewportFromSectionLine:
	CurrentViewports_DetailNumbers = CurrentViewports_DetailNumbers(ViewportFromSectionLine)
	DuplicatedViews = DuplicateViews(ViewFromSectionLine)
	NewViewports = CreateViewport(DuplicatedViews, ViewportFromSectionLine)
	ViewportTypeIds = GetTypeId(ViewportFromSectionLine)
	SetTypeId = SetTypeId(NewViewports,ViewportTypeIds)
	ViewTitleLocations = GetViewTitleLocation(ViewportFromSectionLine)
	SetViewTitleLocation = SetViewTitleLocation(NewViewports, ViewTitleLocations)
	SetDetailNumbers = SetDetailNumbers()

if ScheduleGraphics:
	ScheduleViews = ScheduleViews()
	DuplicatedSchedules = DuplicateSchedules(ScheduleViews)
	NewScheduleGraphics = CreateScheduleInstance(ScheduleViews, ScheduleGraphics)

if not viewports and not ViewportFromSectionLine and not ScheduleGraphics:
	UserMessage = UserMessage()

t.Commit()

Write = Write_LOG()

print time.time() - st
1 Like

Bonus question: Do i have to build my own winforms to get a userinterface or has pyrevit something out of the box for me?

Info on pyRevit forms found here:
https://pyrevit.readthedocs.io/en/latest/pyrevit/forms.html

1 Like

If the built in pyRevit forms aren’t accomplishing what you’re looking for, I recommend Ehsan’s series of videos on YouTube on XAML and WPF

With the help of Visual Studio and these tools, you can create pretty much any UI elements you want.

2 Likes

Hello Dan,
Thank you very much for your reply :slight_smile:
That will take some time to get familiar with building own UIs with these tools but I´m looking forward to it.
Downloading " Visual Studio Community" right now.

I´m still investigating what i can do with pyrevit forms, i already saw list-input/checkbox forms, what i would need is to combine such form with some other selection method, like radio buttons…

Hi,
Too large of a topic IMHO

1 Topic, 1 question, you will get better answers and that will help others eventually.

Also, if you are still new to coding in python, I suggest you go with baby steps and start by looking at pyrevit tools code.

Therefore, I will limit my answers

sys.path.append(‘C:\Program Files (x86)\IronPython 2.7\Lib’)

you don’t need that. It is already there most probably.

import System
from System import Array
from System.Collections.Generic import *

you import all of System module and them re-import specific portions.
Good practice is to import only what you need, this way is OK:

from System import Array
from System.Collections.Generic import *

RevitNodes, ProtoGeometry (Dynamo library for geometry), Revit Services, you can find all that in the pyrevit modules and submodules.
Both is fine, you just have to pick one or the other:

  • Either the Autodesk modules
  • or the pyrevit ones that wrap the Autodesk ones

The pyRevit modules gives you access to many shortcuts in terms of accessing Autodesk and Revit stuff.

for example, wrapping action on the revit 3D ou data in a Transaction looks like this:

# with Autodesk Revit Modules
t = DB.Transaction(doc, 'Sample')
t.Start()

# do whatever

t.Commit()
# with pyRevit Modules
with revit.Transaction(doc, 'Sample'):
    # do whatever

end of the day you will get more concise code, with a little toll on the time it takes to run your scripts, pyRevit wrapping Autodesk Revit API, it should take a little bit longer to run strict Autodek Revit API code vs pyrevit wrapped one.

:point_up: me limiting my answers :smile:

1 Like

forms are great and good enough IMHO
Good luck with WPF and xaml if you are new to this

I 'd rather create a series of forms than a full blown one at first to ask user to pick, select, input, … . And then build from there a full form with wpf in the end if necessary.

When I design a new tool, I work my way out this way:

  1. Test the API calls at small scale
  2. Code without UI, and try to make the code do as little as possible, so that the debugging does not take forever
  3. add forms as a UI one at a time
  4. Then eventually improve the UX by creating a more concise UI (but most of the time, it is not worth it)
1 Like

Hello @Jean-Marc:

Thank you so much for your reply, this helps ma a lot.
I agree with you that you can learn the most by reading code from others and I´m glad that pyRevit gives me the opportunity to see so much scripts. For now they are often a little bit to complicated for me to fully understand whats going on, but I´m sure things will get better.

When it comes to UIs i like them uncomplicated and tiny, but I´m using the system of multiple inputs in one UI now for a long time and i like this more than sending users from one UI to the next.
For example I often use Radio Buttons and a Dropdown (datashapes UI):

If pyRevit forms can´t do that, I already have a winform for list inputs with checkboxes, i will try to add additional inputs to that.

out of the box, no. But you can use the wpf wrapper to get there.

what you can also do is sequence the predefined UIs:

  1. radio buttons
  2. the the dowpdown
1 Like

Hello again @Jean-Marc,

As the CSV-Log is a part of all my scripts, would you mind giving me a suggestion if I should continue to use my code or use the pyRevit module?

Looking at the pyRevit tools i saw some that have several script files in the button folder. Do I understand correct, that i can call the code from another .py file? Would it be a good idea to put the CSV-Log code in a several file? Or is that no good practice?

Thankful for any advice :slight_smile:

I mean this:

def LOG_Entry():
	now = datetime.now()
	dateformat = "%d.%m.%Y"
	timeformat = "%X"
	Date = now.strftime(dateformat)
	Time = now.strftime(timeformat)
	
	LOG_Entry = Time+"\t"+Date+"\t"+User+"\t"+FileName+"\t"+RevitFileName
	return LOG_Entry
	
def Read_LOG():
    retry = 0
    max_retries=5
    while True:
        try:
            with io.open(LogFilePath,"r", encoding = "utf-8") as LOGfile:
				LOG = LOGfile.read().splitlines()
        except OSError:
            time.sleep(0.5)
            retry += 1
            if retry > max_retries:
                raise
        else:
            return LOG
		
def Write_LOG():
	LOG = Read_LOG()
	LOG_New_Entry = LOG_Entry()
	LOG.append(LOG_New_Entry)
	retry = 0
	max_retries=5
	while True:
		try:
			with io.open(LogFilePath,"w", encoding = "UTF8", newline='') as LOGfile:	
				for L in LOG:
					writer = csv.writer(LOGfile,delimiter="'")
					writer.writerow([L])
		except OSError:
			time.sleep(0.5)
			retry += 1
			if retry > max_retries:
				raise
		else:
			return LOG_New_Entry

Please, make a different topic @Gerhard.P

1 Like