Revit API Invalid Object - revit-api

The below snippet is supposed to delete any view that isn't on a sheet OR has no value present in the project view parameter called "View Owner". I've tested this on a blank project and it appears to work as planned. However, on the "real" project, after churning and churning the following error is returned....
Autodesk.Revit.Exceptions.InvalidObjectException: The referenced object is not valid, possibly because it has been deleted from the database, or its creation was undone. at validateNativeInstance(Void* ptr) at Autodesk.Revit.RevitAPIManagedWeakPointer.getValidPtr() at Autodesk.Revit.DB.Element.get_Id() at Microsoft.Scripting.Interpreter.FuncCallInstruction2.Invoke(Object arg0) at IronPython.Runtime.Binding.PythonGetMemberBinder.FastPropertyGet1.GetProperty(CallSite site, TSelfType target, CodeContext context) at Microsoft.Scripting.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame) at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame) at Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1) at IronPython.Compiler.PythonScriptCode.RunWorker(CodeContext ctx) at Microsoft.Scripting.Hosting.ScriptSource.Execute(ScriptScope scope) at RevitPythonShell.RpsRuntime.ScriptExecutor.ExecuteScript(String source)...
I'm not really sure what to make of this. For starters, what is it and what does it mean? Secondly - how would I "catch" this and prevent it from throwing the error? I assume that one of the collected elements is "invalid"? Is there some way to determine if an object is invalid and ignore it? is there a way to get rid of the invalid object? what makes an object invalid?
__window__.Width = 1100
from Autodesk.Revit.DB import FilteredElementCollector, BuiltInCategory, View, Transaction
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
selection = [ doc.GetElement( elId ) for elId in __revit__.ActiveUIDocument.Selection.GetElementIds() ]
views = []
viewstodelete = []
#Build the list full of views
if len(selection) == 0:
cl_views = FilteredElementCollector(doc)
views = cl_views.OfCategory( BuiltInCategory.OST_Views ).WhereElementIsNotElementType().ToElements()
else:
for sel in selection:
if isinstance(sel, View):
views.append(sel)
count = 0
#Get all views with a view owner
for v in views:
if (v.LookupParameter("Sheet Number") is None or v.LookupParameter("Sheet Number").AsString() == "---") and (v.LookupParameter("View Owner").AsString() is None or v.LookupParameter("View Owner").AsString() == ""):
if v.LookupParameter("View Name") is not None:
vOwner = v.LookupParameter("View Name").AsString()
count= count+1
viewstodelete.append(v)
else:
vOwner = "[View Template] - Not Deleted"
print(vOwner)
t = Transaction(doc, 'Delete Views')
t.Start()
for el in viewstodelete:
doc.Delete(el.Id)
t.Commit()
print "Views in Project: %s" % len(views)
print "Deleted views: %s" % count
I've made the following edit which allows the script to continue running, however, each of these "possibly deleted from the database" errors are quite time consuming to process...
for el in viewstodelete:
t.Start()
try:
doc.Delete(el.Id)
except Exception as e:
print("Error: %s" %str(e))
t.Commit()

There is an IsValidObject method on all Revit elements. You can use it to check if your .NET wrapper is still valid.
The documentation says:
If the corresponding Revit native object is destroyed, or creation of the corresponding object is undone, a managed API object containing it is no longer valid. API methods cannot be called on invalidated wrapper objects.
In your case, may be there is some dependency between the views, so when you delete one, another one is deleted by propagation.

Probably some of the elements you are retrieving are required by the system and are impossible to delete. Your exception handler looks like a good step in the right direction. Look at the printout of that to identify the problematic views and determine how to skip them in the first place. There is probably a good reason why they cannot be deleted. Here is some further analysis of related issues:
http://thebuildingcoder.typepad.com/blog/2012/03/melbourne-devlab.html
http://thebuildingcoder.typepad.com/blog/2015/10/rtc-classes-and-getting-started-with-revit-macros.html#24

Related

Appending to list replaces last item in Django middleware

I have a middleware I'm using to retain route history within my Django app to use with breadcrumbs, but for some reason the last item in the list keeps getting replaced rather than the item appending to the end of the list.
ROOT_ROUTE_PATH = '/labels/'
class RouteHistoryMiddleware(object):
request = None
history = None
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
self.request = request
if 'history' not in request.session:
request.session['history'] = []
self.history = request.session['history']
request.session['history'].append(request.path)
if len(self.history) == 0:
self.request.previous_route = ROOT_ROUTE_PATH
elif len(self.history) == 1:
self.request.previous_route = request.session['history'][-1]
elif len(self.history) > 1:
self.request.previous_route = request.session['history'][-2]
return self.get_response(request)
Illustration of request.session['history'] mutation with above:
Open Page A
['/page_a/']
Open Page B
['/page_a/', '/page_b/']
Open Page C
['/page_a/', '/page_c/']
Instead of appending path to session try appending to self.history then overwrite history with new array:
...
self.history = request.session['history']
self.history.append(request.path)
request.session['history'] = self.history
...
You may need to change your if/else conditions after that
The issue you're running into is that Django doesn't know you've modified the list, and so the data is getting written to the session inconsistently. From the documentation:
By default, Django only saves to the session database when the session has been modified – that is if any of its dictionary values have been assigned or deleted.
i.e, if you modify the list without reassigning it or deleting it, then it will not know the session has been modified. Again from the documentation:
we can tell the session object explicitly that it has been modified by setting the modified attribute on the session object.
So your original code should work if you add this line after modifying the list in place:
request.session.modified = True
(Replacing the list entirely, as suggested in the other answer, also works - I'm just trying to explain why your original code didn't work).

Problem with StaleElementReferenceException error pops up occasionally

Hi I have this code which is generating me:
selenium.common.exceptions.StaleElementReferenceException:
Message: stale element reference: element is not attached to the page document
What can be the root cause for it? This code works ok in 10 cases but in 11 not. Is there any chance to improve it to prevent such errors?
Lines which generates the fails are:
compare_announcement_text(context, option_name, element_offer_type)
And:
if element_offer_type.text == option_name:
def select_announcement(context, option_name):
"""Select announcement from 'Offer Type' drop down
:param context:
:param option_name:
:return: Announcement page
"""
offer_type = context.driver.find_element_by_xpath(Locators.offer_type)
offer_type.click()
offer_type_list = context.driver.find_elements_by_xpath(Locators.all_elements_buttons_offer_type)
offer_type_text = []
for element_offer_type in offer_type_list:
compare_announcement_text(context, option_name, element_offer_type)
offer_type_text.append(element_offer_type.text)
if option_name not in offer_type_text:
time.sleep(0.5) # menu not fully rendered - need to wait
offer_type_list = context.driver.find_elements_by_xpath(Locators.all_elements_buttons_offer_type)
for element_offer_type in offer_type_list:
compare_announcement_text(context, option_name, element_offer_type)
from features.pages.announcement_page import AnnouncementPage
return AnnouncementPage(context)
def compare_announcement_text(context, option_name, element_offer_type):
if element_offer_type.text == option_name:
WebDriverWait(context.driver, 20).until(
expected_conditions.visibility_of_all_elements_located(
(By.XPATH, Locators.all_elements_buttons_offer_type)))
for check in range(4):
try:
ActionChains(context.driver).move_to_element(element_offer_type).click(element_offer_type).perform()
return
except selenium.common.exceptions.ElementClickInterceptedException:
time.sleep(0.5)
else:
print(f"Tried click {option_name} for 2 seconds - making final click")
ActionChains(context.driver).move_to_element(element_offer_type).click(element_offer_type).perform()
else:
print(f'Element selected in offer type is not {option_name} and is {element_offer_type.text}')
It's easily cause this problem if you use find_elements_by_xpath to get a set of elements first.
I suggest you find element again in for loop like this:
offer_type_list = context.driver.find_elements_by_xpath(Locators.all_elements_buttons_offer_type)
offer_type_text = []
for i in range(1, len(offer_type_list)+1):
# xpath example: (//div[#class='abc'])[1]
element_offer_type = context.driver.find_element_by_xpath("("+Locators.all_elements_buttons_offer_type+")["+str(i)+"]")
compare_announcement_text(context, option_name, element_offer_type)
offer_type_text.append(element_offer_type.text)
I faced same exception a time ago. Its caused because the element you are interacting is not loaded in the DOM at that moment. You can solve it with a webDriverWait waiting for the element to be displayed.
#Jonx -> It's not the only reason. Question is also about app, #ranger is using. If it's dynamic (with reloading elements e.g. cause new live data) it's also raises this issue. depending on time when data comes in (e.g. after getting reference to element but before performing action). This can be pain in the ass -,-
#Yun - like this idea, but also (if in testing), using Page Object Pattern, good idea is to use views, and then perform actions within try-except blocks, or actually defining Page elements as property methods, so You'll get clean code, and WebDriver will look for element each time call. That eases up issue.
Yeah, I know that this is not so "well-performance" solution, but - as always - trade offs for stable behaviour with live-dynamic apps.

Why is the deconstructor not automatically being called?

I'm working on an assignment for school and having some difficulty understanding the __del__ method. I understand that it is called after all the references to the object are deleted, but I'm not exactly sure how to get to that point. It states that the __del__ method should be called automatically, but I'm having a rough time even getting the del() to automatically call __del__ as I understand it should.
I've tried to manually call the del method and have tried looking at various sample coding. Something is just not clicking with me for this. The only way I can some-what get it to be called is by using this piece of code at the end:
for faq in faqs:
Faq.__del__(faq)
But I know that is not correct.
class Faq:
def __init__(self, question, answer):
self.question = question
self.answer = answer
return
def print_faq(self):
print('\nQuestion: {}'.format(self.question))
print('Answer: {}'.format(self.answer))
def __del__(self):
print('\nQuestion: {}'.format(self.question))
print('FAQ deleted')
faqs = []
faq1 = Faq('Does this work?', 'Yes.')
faqs.append(faq1)
faq2 = Faq('What about now?', 'Still yes.')
faqs.append(faq2)
faq3 = Faq('Should I give up?', 'Nope!')
faqs.append(faq3)
print("FAQ's:")
print('='*30)
for faq in faqs:
obj = Faq.print_faq(faq)
print()
print('='*30)
I expect the code to output the __del__ print statements to verify the code ran.
The method __del__ is called
when the instance is about to be destroyed
This happens when there are no more references to it.
del x doesn’t directly call x.__del__() — the former decrements the reference count for x by one, and the latter is only called when x’s reference count reaches zero.
So the reason you don't see the expected prints is because each Faq object has 2 references to it:
The variable it is assigned to (faq1, faq2 ...)
A reference from the list faqs
So doing del faq1 is not enough as this will only leave one last reference from the list. To delete those references too, you can do del faqs[:].
As to the code posted here, I am guessing you expect to see the prints because when the program finishes all resources are released. Well that is true, but:
It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits.
You bind faqN and faqs and you keep these references. You need to destroy bindings.
For example:
faq1 = None # del faq1
faq2 = None
faq3 = None
faqs = None # [] or del faqs

Creating a list of Class objects from a file with no duplicates in attributes of the objects

I am currently taking some computer science courses in school and have come to a dead end and need a little help. Like the title says, I need of create a list of Class objects from a file with objects that have a duplicate not added to the list, I was able to successfully do this with a python set() but apparently that isn't allowed for this particular assignment, I have tried various other ways but can't seem to get it working without using a set. I believe the point of this assignment is comparing data structures in python and using the slowest method possible as it also has to be timed. my code using the set() will be provided.
import time
class Students:
def __init__(self, LName, FName, ssn, email, age):
self.LName = LName
self.FName = FName
self.ssn = ssn
self.email = email
self.age = age
def getssn(self):
return self.ssn
def main():
t1 = time.time()
f = open('InsertNames.txt', 'r')
studentlist = []
seen = set()
for line in f:
parsed = line.split(' ')
parsed = [i.strip() for i in parsed]
if parsed[2] not in seen:
studentlist.append(Students(parsed[0], parsed[1], parsed[2], parsed[3], parsed[4]))
seen.add(parsed[2])
else:
print(parsed[2], 'already in list, not added')
f.close()
print('final list length: ', len(studentlist))
t2 = time.time()
print('time = ', t2-t1)
main()
A note, that the only duplicates to be checked for are those of the .ssn attribute and the duplicate should not be added to the list. Is there a way to check what is already in the list by that specific attribute before adding it?
edit: Forgot to mention only 1 list allowed in memory.
You can write
if not any(s.ssn==parsed[2] for s in studentlist):
without committing to this comparison as the meaning of ==. At this level of work, you probably are expected to write out the loop and set a flag yourself rather than use a generator expression.
Since you already took the time to write a class representing a student and since ssn is a unique identifier for the instances, consider writing an __eq__ method for that class.
def __eq__(self, other):
return self.ssn == other.ssn
This will make your life easier when you want to compare two students, and in your case make a list (specifically not a set) of students.
Then your code would look something like:
with open('InsertNames.txt') as f:
for line in f:
student = Student(*line.strip().split())
if student not in student_list:
student_list.append(student)
Explanation
Opening a file with with statement makes your code more clean and
gives it the ability to handle errors and do cleanups correctly. And
since 'r' is a default for open it doesn't need to be there.
You should strip the line before splitting it just to handle some
edge cases but this is not obligatory.
split's default argument is ' ' so again it isn't necessary.
Just to clarify the meaning of this item is that the absence of a parameter make the split use whitespaces. It does not mean that a single space character is the default.
Creating the student before adding it to the list sounds like too
much overhead for this simple use but since there is only one
__init__ method called it is not that bad. The plus side of this
is that it makes the code more readable with the not in statement.
The in statement (and also not in of course) checks if the
object is in that list with the __eq__ method of that object.
Since you implemented that method it can check the in statement
for your Student class instances.
Only if the student doesn't exist in the list, it will be added.
One final thing, there is no creation of a list here other than the return value of split and the student_list you created.

How to get object name of the widget in focus?

Very similiar to
print("focus object class:", window2.focus_get().__class__)
taken from here:
Python get focused entry name
, but I need the exact name of the object.
Something like: self.entrywidget_1
OR:
What to fill the place holder to make if true ?
print(self.focus_get().__class__)
if self.focus_get().__class__ == "placeholder":
print("I work")
The first print returns < class 'tkinter.Entry' >
You can't. Any given object can have many names. For example, given the following code, which name would you expect to get?
self.foo = tk.Button(...)
self.bar = self.foo
You rarely, if ever, need the name of the widget. Having a reference to the widget -- like that is returned by focus_get() -- is all you need. With that yo can insert data, delete data, destroy the widget, get the widget contents, etc.
If you really do need some predictable, unique identifier in the case where the same function must work for multiple widgets, you can give each widget a custom attribute that is a symbolic name. You can then get that name at runtime. For example:
e = tk.Entry(...)
e.name = "placeholder"
...
focused_widget = root.focus_get()
print (the name of the focused widget is %s" % focused_widget.name)
Again, you likely won't ever need that. For example, if you want to delete the text in the focused widget, just use the reference to the widget:
focused_widget.delete(0, "end")
If all you really need is to check if the focused widget is a specific widget, just do a direct comparison:
...
self.placeholder = tk.Entry(...)
...
def whatever(self):
focused_widget = root.focus_get()
if focused_widget == self.placeholder:
print("I work")

Resources