pycatia
pycatia copied to clipboard
[QUESTION] pycatia perfo vs VBA
Hello,
Is there some optimization to follow to obtain better performances with pycatia?
When I use the same macro developed in python with pycatia than one developed in VBA, I can see that the pycatia one is slower (next to 2x) than the VBA one.
Do you have some tips and tricks for me please?
Thanks
Some more information / context?
I don't understand why there would be any difference as they're both accessing the COM object.
Same for me. But it is just a constatation. I'm the only guy who give you tgis feedback? Did you already perform performance tests on both languages?
I noticed the same, especially when dealing with many many user ref props. It's on my schedule to investigate this problem, but what I've found out so far isn't consistent, so I think I'm on a goose chase for now.
An interesting fact tho: I didn't have any perfomance issues when I used a very stripped version of pycatia. But I didn't test it under the same premisses as I do now.
I'm the only guy who give you tgis feedback?
Yes.
Did you already perform performance tests on both languages?
Nope. Believe it or not I rarely do any CATIA scripting, pycatia or VBA.
I've been thinking this over the past week or so and my guess is that any noticeable performance difference is probably in those class methods that use system_service.evaluate()
. For example Point.get_coordinates().
If only I / we / someone could figure out a way to remove the need for it. I've searched again today and no real luck. Lots of clues but nothing I can get to work.
As a test for the speed I created a part with 500 points in:
"""
Creates n random points in a new geometric set 'points'.
Requires CATIA V5 to be running and a CATPart active.
"""
import time
from random import randint
from pycatia import catia
from pycatia.mec_mod_interfaces.part_document import PartDocument
MIN = 0
MAX = 10000
TOTAL_POINTS = 500
start = time.time()
caa = catia()
active_document = PartDocument(caa.active_document.com_object)
part = active_document.part
hsf = part.hybrid_shape_factory
hbs = part.hybrid_bodies
gs_points = hbs.add()
gs_points.name = 'points'
for i in range(1, TOTAL_POINTS+1):
new_point = hsf.add_new_point_coord(randint(MIN, MAX), randint(MIN, MAX), randint(MIN, MAX))
gs_points.append_hybrid_shape(new_point)
part.update()
end = time.time()
print(end-start)
11 seconds
I then have a script that uses Point.get_coordinates()
for all 500 points.
import time
from pycatia import catia
from pycatia.hybrid_shape_interfaces.point import Point
from pycatia.mec_mod_interfaces.part_document import PartDocument
from pycatia.enumeration.enumeration_types import geometrical_feature_type
from pycatia.space_analyses_interfaces.inertia import Inertia
start = time.time()
caa = catia()
application = caa.application
part_doc = PartDocument(application.active_document.com_object)
part = part_doc.part
spa_workbench = part_doc.spa_workbench()
hsf = part.hybrid_shape_factory
hbs = part.hybrid_bodies
hb_points = hbs.item('points')
shapes = hb_points.hybrid_shapes
for shape in shapes:
gft = hsf.get_geometrical_feature_type(shape)
gft_text = geometrical_feature_type[gft]
if gft_text == 'Point':
p = Point(shape.com_object)
coord = p.get_coordinates()
print(shape.name, coord)
end = time.time()
print(end-start)
23 seconds.
For just 500 points this took my machine 23 seconds. I think that's slow? But I don't know. I tried writing the equivalent in VBA but gave up as I just never really learned that side of things and writing VBA makes me feel dirty. Perhaps one of you could so we can do a real comparison?
An interesting fact tho: I didn't have any performance issues when I used a very stripped version of pycatia. But I didn't test it under the same premisses as I do now.
That framework looks pretty much identical to pycatia so why would it be any different? You've just got less modules which would possibly show a small difference in RAM usage and startup time. I doubt it would be enough to show a significance difference in execution time for a script hundreds/thousands of functions. But I'd be happy to see some evidence.
Less conjecture and more examples my friends. ❤️ 😄
Also, for completeness we should time the point creation script too and create a VBA equivalent of that. This is because the point creation script doesn't use any system_service.evaluate()
calls.
edited above reply to include the point creation script time.
Hi, I propose to write the equivalent in VBA and also in VB.net to compare the 3 versions on the same device even if I really prefer python. But I need good performances with my scripts... Other point: I will be absent during the next 3 weeks. So it is not for very soon. Bye
No problem. 👍
I did some testing comparing your code to a VBA version.
pycatia ~ 5s VBA ~ 2.3s
Something I noticed was that when using pycatia the display is refreshed. You can see the points created (if that makes sense). When using VBA however you don't.
I tried caa.refresh_display = False, but no difference.
Sub CATMain()
Dim MIN
Dim MAX
Dim TOTAL_POINTS
Dim i
Dim startTime
Dim endTime
Dim catia
Dim activeDocument
Dim part
Dim hybridShapeFactory
Dim hybridBodies
Dim gsPoints
Dim newPoint
MIN = 0
MAX = 10000
TOTAL_POINTS = 500
startTime = Timer
Set catia = GetObject(, "CATIA.Application")
Set activeDocument = catia.ActiveDocument
Set part = activeDocument.Part
Set hybridShapeFactory = part.HybridShapeFactory
Set hybridBodies = part.HybridBodies
Set gsPoints = hybridBodies.Add()
gsPoints.Name = "points"
For i = 1 To TOTAL_POINTS
Set newPoint = hybridShapeFactory.AddNewPointCoord(Int((MAX - MIN + 1) * Rnd + MIN), Int((MAX - MIN + 1) * Rnd + MIN), Int((MAX - MIN + 1) * Rnd + MIN))
gsPoints.AppendHybridShape newPoint
Next
part.Update()
endTime = Timer
MsgBox "Elapsed time: " & (endTime - startTime) & " seconds"
End Sub
Great stuff. I'll have a play with this tomorrow.
Interesting what you say about the screen refresh. There's a pycatia script I was running the other day that I swear was faster by disabling the refresh_display, seemed to work. That was generating points I'm sure.
Also, we should really remove the random point generation outside of the timing. Probably won't make a noticeable difference but would be more scientific.
Correction: caa.refresh_display = True --> 6s caa.refresh_display = False --> 5s
So, thanks to this I got exposed to dealing with early and late bindings for the first time. I will never be the same. Hahaha
I did a test with a mix off early bindings, did not do much ~0.3s (NOTE: if you run this you have to clear gen_py afterwards otherwise it get stuck to early-bindings). After creating the bindings I commented it out:
from win32com.client import Dispatch, CastTo
from win32com.client.dynamic import DumbDispatch
from timeit import default_timer as timer
from random import randint
def GetRightDispatch(o):
return Dispatch(DumbDispatch(o))
MIN = 0
MAX = 10000
TOTAL_POINTS = 500
start = timer()
caa = Dispatch('CATIA.Application')
caa.RefreshDisplay = False
active_document = caa.ActiveDocument
part = Dispatch(active_document.Part)
#part = GetRightDispatch(active_document.Part)
#part = CastTo(part, "Part")
hsf = Dispatch(part.HybridShapeFactory)
#hsf = GetRightDispatch(part.HybridShapeFactory)
#hsf = CastTo(hsf, "HybridShapeFactory")
hbs = Dispatch(part.HybridBodies)
#hbs = GetRightDispatch(part.HybridBodies)
#hbs = CastTo(hbs, "HybridBodies")
gs_points = hbs.Add()
gs_points.Name = 'points'
for i in range(1, TOTAL_POINTS+1):
new_point = hsf.AddNewPointCoord(i, i, i)
gs_points.AppendHybridShape(new_point)
part.Update()
end = timer()
caa.RefreshDisplay = True
print(end - start)
When running both scripts, yours and this, I noted that (at random) the Geometrical Set was collapsed when the points was created. When it was, it was ~1s faster. I have not found a way to control this.
Interesting. Thanks so much for posting your findings.
I stated:
Interesting what you say about the screen refresh. There's a pycatia script I was running the other day that I swear was faster by disabling the refresh_display, seemed to work. That was generating points I'm sure.
It wasn't generating points, it was for speeding up the drawing of a template into the Drawing background for my little side project pycatia-tools (shamless plug).
Jumping about from project to project is frying my little brain. 😃
Finally had the time to play around a bit with this issue. I wrote 3 scripts, one with pycatia
, one using the win32com.Dispatch
and one in catvbs
. All 3 of them do the same:
- Create
10
new parts - Create
400
new StrParam type UserRefProperties in each part - Read each single StrParam by index
- Read each single StrParam by name
I added all scripts as zip so I don't clutter this issue with code: time_comp_240707.zip
Setup:
- Windows 11 on Dassault certified hardware
- CATIA V5-6R 2023 (B33)
- Python 3.10.7
- Requirements:
- pycatia==0.7.2
- pywin32==306
Results:
Script | Average creating time | Average reading time (index) | Average reading time (name) |
---|---|---|---|
catvbs | 0.009375s | 0.002734s | 0.952343s |
dispatch | 0.098642s | 0.038168s | 3.057094s |
pycatia | 0.043048s | 0.084434s | 3.586073s |
Some things I found noteworthy:
- there's a ~15% deviation in all results in all 3 scripts (I ran them many times, the results above are the average of the average)
- reading the UserRefProperties by index is significantly faster than reading them by name in all 3 scripts (could be important for anyone who might have performance issues regarding UserRefProperties)
- pycatia is faster than the dispatch when creating the StrParams, but slower in reading by index (I really did not expect this and I will try this on another machine)
- Using
caa.refresh_display = False
had no real impact on the performance, BUT if it's set toFalse
CATIA doesn't recognize the creation of UserRefProperties (there's no document change shown in the Save Management). This is extremely dangerous because ist can lead to loss of data!
Sidenotes:
- I don't know is if it's a fair comparison because I don't know the inner workings of the timing functions I used (python:
timeit.default_timer
vs. catvbs:Timer
) - I used the UserRefProperties to test this because one of my projects does rely on a fast creation/reading of them
- I hadn't had the time to investigate possible reasons for this issue (that aren't wild guessings)
Edit: Corrected values in result table
Great work @deloarts.
reading the UserRefProperties by index is significantly faster than reading them by name in all 3 scripts (could be important for anyone who might have performance issues regarding UserRefProperties)
That does make sense to me. Like a database lookup by id rather than string.
pycatia is faster than the dispatch when creating the StrParams, but slower in reading by index (I really did not expect this and I will try this on another machine)
A noticeable difference? That does surprise me. Recently, to pycatia's dispatch method I added pythoncom.CoInitialize(). This was so I could use pycatia in a GUI application (wouldn't work without it).
Could you run your manual dispatch version of the script with this added? If this is slowing things down I'll update pycatia to make that call optional. I'll do it myself later this week using your script if you haven't time. It didn't help prevent the behavior I have noted below.
So, this may or may not be related ... I've just run a script opening and closing a document a 1000 times in a for loop and I noticed that CATIA V5 kind of freezes every so often (no obvious pattern I can see).
The times were not consistent at all!
So, I did a bit of playing:
import time
import statistics
from pathlib import Path
from pycatia import catia
def open_document(file: Path, number_of_tries:int):
times = []
caa = catia()
caa.logger.disabled = True
documents = caa.documents
for i in range(number_of_tries):
start = time.time()
document = documents.open(test_file)
document.close()
end = time.time()
times.append(end-start)
return times
test_file = Path(r'C:\Users\evereux\cloud\pycatia\__archive__\Part1.CATPart')
number_of_tries = 100
times = open_document(test_file, number_of_tries)
print(f'TIMES_REUSE: The fastest opening time was {min(times)}.')
print(f'TIMES_REUSE: The slowest opening time was {max(times)}.')
print(f'TIMES_REUSE: The slowest opening time was {statistics.mean(times)}.')
-
caa.refresh_display = False
didn't stop the pauses. -
caa.visible = False
was just slower all round?!?!?! - this could be a bug in the CATIA version I'm using right now: R21.
- If I created the
caa
object each time before opening the file itself it was be consistently quicker to open the file. But if the time taken to create thecaa
object is factored it was slower. (edit: i did say similar but that wasn't the case)
Just another thought .. when comparing VB versus Python I don't think we should allow anything to output to terminal as this will slow things down. For example, disable logging caa.logger.disabled = True
and don't print anything to terminal within a timed operation.
I think I'm the only one that has done that above though. d'oh.
Hello, Here is my contribution and my results:
Tests done with python, python_alt, vb.net, catvba for 500 and 5000 points (yes, my laptop is very efficient and the results are more significant with 5000 points 😜) For 500 pts. For 5000 pts
- catvba 0,47 second 12 s
- vb.net 2,53 s 28 s
- python 3,85 s 223 s
- python_alt 3,90 s 215 s
You will find below used scripts for these tests.
Python
import time
from random import randint
##########################################################
# insert syspath to project folder so examples can be run.
# for development purposes.
import os
import sys
from pathlib import Path
sys.path.insert(0, os.path.abspath('..\\pycatia'))
##########################################################
from pycatia import catia
from pycatia.mec_mod_interfaces.part_document import PartDocument
from pycatia.mec_mod_interfaces.part import Part
from pycatia.hybrid_shape_interfaces.hybrid_shape_factory import HybridShapeFactory
from pycatia.mec_mod_interfaces.hybrid_bodies import HybridBodies
def cat_main():
min_val = 0
max_val = 10000
total_points = 5000
start_time = time.time()
# Get the CATIA application and active document
cat = catia()
cat.refresh_display = False
# docs = Documents(cat.documents.com_object)
part_doc = PartDocument(cat.active_document.com_object)
part = Part(part_doc.part.com_object)
hybrid_shape_factory = HybridShapeFactory(part.hybrid_shape_factory.com_object)
hybrid_bodies = HybridBodies(part.hybrid_bodies.com_object)
# Create a new hybrid body and set its name
gs_points = hybrid_bodies.add()
[gs_points.name](https://github.com/evereux/pycatia/issues/gs_points.name) = "points"
for i in range(1, total_points +1):
# Generate random coordinates for each point
x, y, z = (randint(min_val, max_val), randint(min_val, max_val), randint(min_val, max_val))
# Add a new point to the hybrid body
new_point = hybrid_shape_factory.add_new_point_coord(x, y, z)
gs_points.append_hybrid_shape(new_point)
part.update()
end_time = time.time()
elapsed_time = end_time - start_time
# cat.refresh_display = True
print(f"Elapsed time: {elapsed_time:.2f} seconds")
if __name__ == "__main__":
cat_main()
Python_alt
from win32com.client import Dispatch, CastTo
from win32com.client.dynamic import DumbDispatch
from timeit import default_timer as timer
from random import randint
def GetRightDispatch(o):
return Dispatch(DumbDispatch(o))
MIN = 0
MAX = 10000
TOTAL_POINTS = 5000
start = timer()
caa = Dispatch('CATIA.Application')
caa.RefreshDisplay = False
active_document = caa.ActiveDocument
part = Dispatch(active_document.Part)
#part = GetRightDispatch(active_document.Part)
#part = CastTo(part, "Part")
hsf = Dispatch(part.HybridShapeFactory)
#hsf = GetRightDispatch(part.HybridShapeFactory)
#hsf = CastTo(hsf, "HybridShapeFactory")
hbs = Dispatch(part.HybridBodies)
#hbs = GetRightDispatch(part.HybridBodies)
#hbs = CastTo(hbs, "HybridBodies")
gs_points = hbs.Add()
gs_points.Name = 'points'
for i in range(1, TOTAL_POINTS+1):
# Generate random coordinates for each point
x, y, z = (randint(MIN, MAX), randint(MIN, MAX), randint(MIN, MAX))
# Add a new point to the hybrid body
new_point = hsf.AddNewPointCoord(x, y, z)
gs_points.AppendHybridShape(new_point)
part.Update()
end = timer()
caa.RefreshDisplay = True
print(end - start)
Vb.net
Imports System.Diagnostics
Imports MECMOD
Imports INFITF
Imports PARTITF
Imports HybridShapeTypeLib
Module Module1
Sub Main()
Dim MIN
Dim MAX
Dim TOTAL_POINTS
Dim i As Integer
Dim startTime
Dim endTime
Dim CATIA As Application
MIN = 0
MAX = 10000
TOTAL_POINTS = 500
startTime = Timer
CATIA = GetObject(, "CATIA.Application")
CATIA.RefreshDisplay = False
' Récupérer le document actif
Dim partDocument As PartDocument
partDocument = CATIA.ActiveDocument
' Récupérer la partie active
Dim part As Part
part = partDocument.Part
' Récupérer la collection des bodies
Dim bodies As Bodies
bodies = part.Bodies
' Créer un nouveau set géométrique
Dim hybridBodies As HybridBodies
hybridBodies = part.HybridBodies
Dim hybridBody As HybridBody
hybridBody = hybridBodies.Add()
hybridBody.Name = "points"
' Créer un point
Dim hybridShapeFactory As HybridShapeFactory
hybridShapeFactory = part.HybridShapeFactory
Dim point As HybridShapePointCoord
Dim random As New Random()
Dim x As Double
Dim y As Double
Dim z As Double
For i = 1 To TOTAL_POINTS
x = random.NextDouble() * 1000
y = random.NextDouble() * 1000
z = random.NextDouble() * 1000
point = hybridShapeFactory.AddNewPointCoord(x, y, z)
' Ajouter le point au set géométrique
hybridBody.AppendHybridShape(point)
Next
' Mettre à jour la partie
part.Update()
endTime = Timer
CATIA.RefreshDisplay = True
MsgBox("Temps d'exécution : " & (endTime - startTime) & " Secondes")
End Sub
End Module
Catvba
Sub CATMain()
Dim MIN
Dim MAX
Dim TOTAL_POINTS
Dim i
Dim startTime
Dim endTime
Dim catia
Dim activeDocument
Dim part
Dim hybridShapeFactory
Dim hybridBodies
Dim gsPoints
Dim newPoint
MIN = 0
MAX = 10000
TOTAL_POINTS = 500
startTime = Timer
Set catia = GetObject(, "CATIA.Application")
Set activeDocument = catia.activeDocument
Set part = activeDocument.part
Set hybridShapeFactory = part.hybridShapeFactory
Set hybridBodies = part.hybridBodies
Set gsPoints = hybridBodies.Add()
gsPoints.Name = "points"
For i = 1 To TOTAL_POINTS
Set newPoint = hybridShapeFactory.AddNewPointCoord(Int((MAX - MIN + 1) * Rnd + MIN), Int((MAX - MIN + 1) * Rnd + MIN), Int((MAX - MIN + 1) * Rnd + MIN))
gsPoints.AppendHybridShape newPoint
Next
part.Update
endTime = Timer
MsgBox "Elapsed time: " & (endTime - startTime) & " seconds"
End Sub
Best regards
Thanks @Djey51!
my laptop is very efficient and the results are more significant with 5000 points
you're not kidding! 😄