PR#2862: Configuration Table using Spreadsheet

Post here if you have re-based and finalised code to integrate into master, which was discussed, agreed to and tested in other forums. You can also submit your PR directly on github.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
realthunder
Veteran
Posts: 2190
Joined: Tue Jan 03, 2017 10:55 am

PR#2862: Configuration Table using Spreadsheet

Post by realthunder »

Happy new year, everyone! This is my first PR of the year. It adds support of Configuration Table to FC using Spreadsheet. For those who have used SolidWorks, this works somewhat like its Design Table, but more fit for FC (obviously), and more flexible (argubally).

Edit: New development of the Variant Link feature can be found here.

Since this is the development section, this post will be aimed more for developers. I'll post a video later more suitable for end users.

The PR contains a few changes in the core that are necessary for the end product, a configuration table defined in a spreadsheet that allows user to dynamically switch between multiple sets of parameters for one or multiple objects.

First, 'Expression...' option is now always available in property viewer context menu. Previously it is hidden unless 'Show all' option is activated. This makes it easy for user to use expression on any type of property.

The user can now rename the group name of a dynamic property. The menu action is only shown when 'Show all' is activated. Note that group renaming is no undoable. Because dynamic properties can only be shown in alphabetic orders, this action allows user to customize the property grouping.

PropertyEnumeration is modified to allow user to customize its list. It is exposed through sub path named 'Enum', and is only shown in the property viewer when 'Show all' option is activated. The 'Enum' sub path accepts expression binding as well. This provides the possibility to dynamically create a property for user to choose the configuration. The following screencast shows how to add a dynamic PropertyEnumeration in property viewer.

Image



PropertySheetPy now supports mapping protocol for user to extract contents of a range of cells as a Python list. It can be used in both Python and Expression, e.g. in expression,

Code: Select all

# return a list of cell content in order A1, B1, A2, B2. Empty cells will correspond to a None item.
Sheet.cells[<<A1:B2>>]

# return the contents of row A starting from A1 until the first empty cell
Sheet.cells[<<A1:->>]

# return the contents of column A starting from A1 until the first empty cell
Sheet.cells[<<A1:|>>]
The screencast below shows the effect of auto growing column binding.

Image


New expression built-in function str(arg) is added to convert anything to string, and another more important function href(arg), which hides any object reference inside the argument to work around cyclic dependency error. You need to be careful when using href() as it may give unexpected result. As a rule of thumb, it will be safe if href() is refering (directly or indirectly) to some property that will only be changed by user instead of calculated by the object (e.g. with other expressions). When used right, href() can be a powerful tool. You'll see later that it is an essential component for Configuration Table to work.

Exposed a few convenience context menu in Spreadsheet view.
Screenshot from 2020-01-04 20-50-37.png
Screenshot from 2020-01-04 20-50-37.png (11.66 KiB) Viewed 23797 times

The 'Recompute' menu action above is to recompute the selected cells without recompute its dependencies first. Because of the introduction of href(), you may encounter chicken and egg dilemma. And this menu action offers one way to break the cycle.

'Cut/Copy/Delete/Paste' has been enhanced to support format copying and auto repeat. Another thing to note is that the normal form of cell address (e.g. A1) is considered as relative reference, and will be auto adjusted when pasted. If you do not want this, use absolute reference (e.g. $A1, $B$1, C$1).

Image


The 'Bind' action is to bind a range of cells to another range of cells in possibly another spreadsheet in maybe an externaldocument. The following screencast shows how it works. Select the target range first, and then optionally the source range. Once bound, the cell in target range will mirror what's in the source range. Bound cells are shown with blue border, and are not editable. Double clicking any bound cells to edit or discard the binding.

Image


The cell binding is implemented using PropertyExpressionEngine and PropertySheet::setPathValue() with a special path. For example, to bind cells B3:D3 to B5:D5 of the same spreadsheet,

Code: Select all

Spreadsheet.setExpression('.cells.Bind.B3.D3', 'tuple(.cells, <<B5>>, <<D5>>)') 

The 'Configuration Table' menu action is, of course, to actually create the table. The following screencast starts by showing a common workflow to create a list of parameters and bind them to various object properties. Using alias is not mandatory, but make it easy to move the cells around later on. Once you've tested that the parameters work well, you can duplicate the parameter row to create multiple configurations. You must name each of your configurations in the leading column. The first row is not part of the configuration list. It is considered as the 'current setting', and will be dynamically bind to the user's choice. The configuration table is created by select the top left corner cell, and activate the menu action. Enter an expression to refer to an object to publish the configurations using a PropertyEnumeration. The property group name is optional.

Image


As you can see in the above screencast, the configuration property is published into a parent object. In other word, the spreadsheet dynamic cell binding will have to refer to its parent, which would normally be a cyclic reference. This is why we need href(). The expression used for cell binding is something like this,

Code: Select all

Spreadsheet.setExpression('.cells.Bind.B1.ZZ1', 'tuple(.cells, <<B>> + str(href(Part.Configuration)+2), <<ZZ>> + str(href(Part.Configuration)+2))')

In short, each cell in the 'current setting' row is dynamically bound to a cell in some row below, with the row offset calculated using the current value of the published PropertyEnumeration named 'Configuration' in object 'Part'. There is another cyclic reference here. Part.Configuration.Enum binds to the configuration name column in the spreadsheet, while the 'current setting' row's binding refers to the Part.Configuration. And it works because of href().

There is no limitation on how many tables you can define in one spreadsheet. You can bind the published PropertyEnumeration of some object and use it as part of a higher level configuration.

The published PropertyEnumeration has a special mark called 'CopyOnChange' using the same status bitset that defines 'ReadOnly', 'Touched', etc. The Link has special handling when seeing object with these marked properties. The special handling can be enabled by setting the 'LinkCopyOnChange' property to 'Enabled'. Once enabled, the Link will first duplicate those 'CopyOnChange' properties from the linked object and inject to itself. When the user changes any of these properties, the link will make a deep copy of the linked object, apply the change to it, and change the link to point to the copied object. It will also switch the 'LinkCopyOnChange' to 'Owned' so that later changes to 'CopyOnChange' properties will not trigger further copy. One thing to note that, any property can be marked as 'CopyOnChange' through the property view menu, not just the one published by Spreadsheet. In other words, you can use other property for configuration purpose without using spreadsheet.

Link array mode support 'CopyOnChange' as well. Each element of the array can individually toggle 'LinkCopyOnChange', making a heterogeneous array. Note that this is only supported when the array elements are expanded as individual objects, not when collpased. Draft LinkArray support 'CopyOnChange' too.

Image


The SubShapeBinder from PartDesign takes another approach to support 'CopyOnChange'. It is enabled by property 'BindCopyOnChange'. SubShapeBinder can bind to multiple objects, but 'CopyOnChange' can only works when binding to one object. Instead of duplicating the linked object with all its hierarchy, the binder will make a flattened copy of the mutated object. Another difference to Link is that the binder will sync any changes of the original object back to the copy even if the configurations are different, while for Link, once copied, the two objects become independent. See the screencast below and notice how the binder syncs the change of the cyclinder height, which is not part of the configuration.

Behind the scene, SubShapeBinder actually makes a deep copy of the bound object just like Link, but with a twist. The copy is hidden in a temporary document. The core now supports creating a 'Temporary Document'. App.newDocument() accepts a new named argument 'temp', when set to True will create a temporary document that won't show up in the tree view. Another named argument 'hidden' controls whether to show 3D view of the new document. The temporary document is created with undo/redo disabled. It will not be auto saved. When closing all documents or exit application, the user will not be prompt for saving of any temporary document, although the document can still be saved by explictly calling its save() method in Python console. All binder shares a single temporary document behind the scene. The copied objects are temporary, too, so it does not take extra space when saved. The binder is still bound to the original object.

Image
Last edited by realthunder on Sat Sep 11, 2021 1:15 am, edited 1 time in total.
Try Assembly3 with my custom build of FreeCAD at here.
And if you'd like to show your support, you can donate through patreon, liberapay, or paypal
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: PR#2862: Configuration Table using Spreadsheet

Post by Zolko »

realthunder wrote: Sun Jan 05, 2020 1:36 am First, 'Expression...' option is now always available in property viewer context menu. Previously it is hidden unless 'Show all' option is activated.
...
PropertyEnumeration is modified to allow user to customize its list.
good stuff

realthunder wrote: Sun Jan 05, 2020 1:36 am The core now supports creating a 'Temporary Document'. App.newDocument() accepts a new named argument 'temp', when set to True will create a temporary document that won't show up in the tree view. Another named argument 'hidden' controls whether to show 3D view of the new document. The temporary document is created with undo/redo disabled. It will not be auto saved. When closing all documents or exit application, the user will not be prompt for saving of any temporary document
Halleluia, I was wishing this feature !!! Variant links are now a possibility, hurraaahh !!! I don't know for the other things (could wait for v0.20 ?), but this temporary document that isn't saved is an absolute necessity, even for v0.19. Could you possibly split it off from this big PR and make a small PR only for this ?

One other thing I noticed with documents is that when you want to open a document that is already open in the background, FreeCAD sends an error and does nothing. It would be much better if it simply switched to the requested (opened but hidden) document. May-be could you include this change into that small PR about temporary documents ?
try the Assembly4 workbench for FreCAD — tutorials here and here
realthunder
Veteran
Posts: 2190
Joined: Tue Jan 03, 2017 10:55 am

Re: PR#2862: Configuration Table using Spreadsheet

Post by realthunder »

Zolko wrote: Sun Jan 05, 2020 3:50 pm Halleluia, I was wishing this feature !!! Variant links are now a possibility, hurraaahh !!! I don't know for the other things (could wait for v0.20 ?), but this temporary document that isn't saved is an absolute necessity, even for v0.19. Could you possibly split it off from this big PR and make a small PR only for this ?
Sure, I can split it out. Be warned though, Link is not suitable for use with temporary objects, because it relies on the linked object to provide both geometry and visual. It is not entirely impossible to do it, but is kind of convoluted, and defeats the purpose of Link in most cases, although I did use it to implement the assembly 'Freeze' function in asm3.

Anyway, the shape binder is a much better candidate for using temporary document. It owns the shape and renders the shape by itself. The 'variant' Link I show above makes a copy of the object inside the same document, so the copy will be saved. Making a deep copy of the object is quite easy. Just call document.copyObject(obj, True). The second argument indicate whether to copy dependency as well. And it will work with the current upstream. Actually, I mentioned this to you in the other thread.

One other thing I noticed with documents is that when you want to open a document that is already open in the background, FreeCAD sends an error and does nothing. It would be much better if it simply switched to the requested (opened but hidden) document. May-be could you include this change into that small PR about temporary documents ?
The error only occurs when you call openDocument(). To create temporary document you call newDocument(), and it shall always succeed.
Try Assembly3 with my custom build of FreeCAD at here.
And if you'd like to show your support, you can donate through patreon, liberapay, or paypal
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: PR#2862: Configuration Table using Spreadsheet

Post by Zolko »

realthunder wrote: Sun Jan 05, 2020 11:53 pm
Zolko wrote: Sun Jan 05, 2020 3:50 pm Halleluia, I was wishing this feature !!! Variant links are now a possibility, hurraaahh !!! I
Be warned though, Link is not suitable for use with temporary objects, because it relies on the linked object to provide both geometry and visual.
...
Anyway, the shape binder is a much better candidate for using temporary document. It owns the shape and renders the shape by itself.
OK, but what if you want to do this with objects that don't have a shape ? I have funny ideas for this variant link that I'd like to try. May-be I'll fall-back to shape-binder if my ideas don't work

realthunder wrote: Sun Jan 05, 2020 11:53 pm
One other thing I noticed with documents is that when you want to open a document that is already open in the background, FreeCAD sends an error and does nothing. It would be much better if it simply switched to the requested (opened but hidden) document.
The error only occurs when you call openDocument(). To create temporary document you call newDocument(), and it shall always succeed.
The error I mention occurs when the users wants to open an already opened but hidden document with the GUI. No scripting. If you have an assembly with many files (links), the documents of the links are now opened hidden (which is good), and if the user wants to open with File -> Open a document that is already open (because he didn't realise that that file is already open in the background) then the error happens.
try the Assembly4 workbench for FreCAD — tutorials here and here
realthunder
Veteran
Posts: 2190
Joined: Tue Jan 03, 2017 10:55 am

Re: PR#2862: Configuration Table using Spreadsheet

Post by realthunder »

Zolko wrote: Mon Jan 06, 2020 7:57 am The error I mention occurs when the users wants to open an already opened but hidden document with the GUI. No scripting. If you have an assembly with many files (links), the documents of the links are now opened hidden (which is good), and if the user wants to open with File -> Open a document that is already open (because he didn't realise that that file is already open in the background) then the error happens.
Yes, that's because the openDocument(path) searches for already opened document with the same path, and will throw error if found. newDocument(name) does not do that. For non-temporary document, newDocument() will auto rename the document if there is an existing one with the same name (not path, as new document does not have path yet). For temporary document, there will be no auto renaming, but simply returning of the existing document if there is one. This is to reduce the likelihood of accidentally opening too many temporary documents. I guess this would be a common mistake because there is no visual clue of how many temporary documents are opened.
Try Assembly3 with my custom build of FreeCAD at here.
And if you'd like to show your support, you can donate through patreon, liberapay, or paypal
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: PR#2862: Configuration Table using Spreadsheet

Post by Zolko »

realthunder wrote: Mon Jan 06, 2020 8:44 am
Zolko wrote: Mon Jan 06, 2020 7:57 am The error I mention occurs when the users wants to open an already opened but hidden document with the GUI.

Yes, that's because the openDocument(path) searches for already opened document with the same path, and will throw error if found.
I understand, and that's the point: it should not throw an error, but return the existing document.

My request is independent from the hidden/temporary document thing: since you're in the document handling business, I was wondering whether you'd have the time to correct this behaviour. An average user will not understand that error: if you want to open a door and the door is already open, why get an error ? And what's more, the error message is not very helpful: if the document is already open but hidden, how should the user access it ? You can access all open documents with the activate document sub-menu, but only the name of the documents appear there, not the full path. Also, if the assembly is large with 100 files, it's cumbersome to find the correct document.
try the Assembly4 workbench for FreCAD — tutorials here and here
User avatar
Pauvres_honteux
Posts: 728
Joined: Sun Feb 16, 2014 12:05 am
Location: Far side of the moon

Re: PR#2862: Configuration Table using Spreadsheet

Post by Pauvres_honteux »

Congratulations to a well performed coding session realthunder!
Question, do I interpret this correct if I claim the 'Configuration Table' to be one way of making e.g. a principal screw (or profile) and this table can then be set to hold all dimension combinations there are of that screw (or profile)?
.
Screw_table_1.jpg
Screw_table_1.jpg (114.84 KiB) Viewed 23728 times
.
Screw_table_2.gif
Screw_table_2.gif (33.06 KiB) Viewed 23728 times
.
And of course the practical screws! :P
Practical_screws.png
Practical_screws.png (153.16 KiB) Viewed 23728 times
realthunder
Veteran
Posts: 2190
Joined: Tue Jan 03, 2017 10:55 am

Re: PR#2862: Configuration Table using Spreadsheet

Post by realthunder »

Zolko wrote: Mon Jan 06, 2020 8:55 am I understand, and that's the point: it should not throw an error, but return the existing document.
PR submitted here
Try Assembly3 with my custom build of FreeCAD at here.
And if you'd like to show your support, you can donate through patreon, liberapay, or paypal
realthunder
Veteran
Posts: 2190
Joined: Tue Jan 03, 2017 10:55 am

Re: PR#2862: Configuration Table using Spreadsheet

Post by realthunder »

Pauvres_honteux wrote: Mon Jan 06, 2020 8:56 am Congratulations to a well performed coding session realthunder!
Question, do I interpret this correct if I claim the 'Configuration Table' to be one way of making e.g. a principal screw (or profile) and this table can then be set to hold all dimension combinations there are of that screw (or profile)?
Thanks. And yes, this is one of the use case. But of course, you'll still need to make a model with all those parameters. The 'Configuration Table' just offers a way to easily switch between a batch of parameters.

Additionally, when using Link for alternative configurations, you'll need to realize that it makes a copy of the original object. So with your screw use case, it would be better to create a single 'variant' link for each type of screw to avoid unnecessary duplicates. Or better, use SubShapeBinder to strip out unnecessary model history. And again, create one 'variant' binder for each configuration. In case you want the normal shape color for binder, just turn off 'UseBinderStyle' in its view properties (only available in this PR).
Try Assembly3 with my custom build of FreeCAD at here.
And if you'd like to show your support, you can donate through patreon, liberapay, or paypal
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: PR#2862: Configuration Table using Spreadsheet

Post by Zolko »

realthunder wrote: Tue Jan 07, 2020 12:58 am
Zolko wrote: Mon Jan 06, 2020 8:55 am I understand, and that's the point: it should not throw an error, but return the existing document.
PR submitted here
unfortunately, that PR didn't yet make it into FreeCAD: I really think this is needed, please devs, can you give it a look ?

And while we're at it, is there a Python command/script that allows to open/activate a document that is already open in the background ? Something equivalent to right-click > Activate Document > ... or right-click > Link actions > Go to linked object ? Or, alternatively, bring that function Link actions > Go to linked object to the first right-click level, something like, when right-click on an App::Link object, Go to Document and that opens the corresponding document ?

This is really important from a usability point of view, and it's already there so it's only something to move commands around in the menus. PLEASE (with Bambi eyes)
try the Assembly4 workbench for FreCAD — tutorials here and here
Post Reply