Update property amount crashes FreeCAD

Need help, or want to share a macro? Post here!
dekoning
Posts: 51
Joined: Wed Nov 19, 2014 4:32 pm

Update property amount crashes FreeCAD

Postby dekoning » Tue Jan 27, 2015 1:34 pm

Hey,

I'm using properties that add or remove properties of the same object. See the code below. The property 'nProp' is used to create an amount of 'proper%s' %i objects. It works by changing its value in the FreeCAD property screen. However, you have to hide the property window and show it again in order for the window to update. This causes FreeCAD to crash (shut down) in the following case: When decreasing the value of 'nProp' and changing one of the 'proper' properties without hide/showing the property window. This is because you want to change the value of a property that doesn't exist anymore (but is still shown).

Is there a way to enforce to update the property window when the value of 'nProp' has changed?
Like some way to write in script: click on screen and then click on object again (to hide and show property window).

Code: Select all

import FreeCAD
import FreeCADGui

class tester(object):
    def __init__(self,obj):
        obj.addProperty("App::PropertyInteger","nProp","group","Number of properties").nProp = 1
        obj.addProperty("App::PropertyInteger", 'proper0', 'group', 'Property').proper0 = 5
        obj.Proxy = self 

    def onChanged(self, obj, prop): 
        if prop == 'nProp':
            try:
                for i in range(len(obj.PropertiesList)):
                    obj.removeProperty('proper%s' %i)
            except:
                pass
            dictionary = {}
            for i in range(obj.nProp):
                dictionary['proper%s' %i] = 0
            for name, value in dictionary.items():
                setattr(obj.addProperty("App::PropertyInteger", name, 'group', 'Property'), name, value)
                
    def execute(self,obj):
        pass
    
FreeCADGui.showMainWindow()
docMain = FreeCAD.newDocument("mydoc")
testObj = docMain.addObject("Part::FeaturePython","test") 
test = tester(testObj)
FreeCADGui.exec_loop() 
wmayer
Site Admin
Posts: 15471
Joined: Thu Feb 19, 2009 10:32 am

Re: Update property amount crashes FreeCAD

Postby wmayer » Tue Jan 27, 2015 4:44 pm

The property view works upon selection. So, you when you make sure to deselect the object first, change its nProp property and then select it again everything should work fine.
dekoning
Posts: 51
Joined: Wed Nov 19, 2014 4:32 pm

Re: Update property amount crashes FreeCAD

Postby dekoning » Wed Jan 28, 2015 7:43 am

But I have to select the object in order to see its properties right? I want the user to change the property by changing the value in the property window, not by using the Python console.
wmayer
Site Admin
Posts: 15471
Joined: Thu Feb 19, 2009 10:32 am

Re: Update property amount crashes FreeCAD

Postby wmayer » Wed Jan 28, 2015 1:06 pm

A proper solution must be directly implemented in C++. Feel free to file a bug report. I'll have a look at it as soon as I am back (end of next week).
User avatar
wandererfan
Posts: 3651
Joined: Tue Nov 06, 2012 5:42 pm

Re: Update property amount crashes FreeCAD

Postby wandererfan » Thu Jan 29, 2015 2:35 pm

prrvchr
Posts: 117
Joined: Sun Oct 05, 2014 7:38 pm
Location: France

Re: Update property amount crashes FreeCAD

Postby prrvchr » Sat Jan 31, 2015 8:59 am

Hi dekoning,

Try this:

Code: Select all

# -*- coding: utf8 -*-
# FreeCAD module provding base classes for document objects and view provider
# (c) 2011 Werner Mayer LGPL
from __future__ import unicode_literals
import FreeCAD as App
import FreeCADGui as Gui
from pivy import coin


class DocumentObject(object):
    """The Document object is the base class for all FreeCAD objects."""

    def __init__(self):
        self.__object__ = None
        self.initialised = False

    #------------------------------Methods for the user to override :

    def execute(self):
        "this method is executed on object creation and whenever the document is recomputed"
        raise Exception("Not yet implemented")
    def init(self):
        #will be called just after object creation, you can use this for example to create properties
        pass
    def propertyChanged(self,prop):
        #will be called each time a property is changed
        pass

    #--------------------------------

    def __getattr__(self, attr):
        if attr !="__object__" and hasattr(self.__object__,attr):
            return getattr(self.__object__,attr)
        else:
            return object.__getattribute__(self,attr)
    def __setattr__(self, attr, value):
        if attr !="__object__" and hasattr(self.__object__,attr):
            setattr(self.__object__,attr,value)
        else:
            object.__setattr__(self,attr,value)
    def onChanged(self,prop):
        if prop=="Proxy":
            #recreate the functions in the __object__
            d = self.__class__.__dict__
            for key in d:
                item = d[key]
                #check if the function is valid
                if hasattr(item, '__call__') and key!="onChanged" and key!="execute" and key!="init" and key[0]!="_":
                    #check if the function doesn't already exist in the object:
                    if not(hasattr(self.__object__,key)):
                        #add a link to the Proxy function in the __object__ :
                        self.addProperty("App::PropertyPythonObject",key,"","",2)
                        setattr(self.__object__,key,getattr(self,key))
                    else:
                        App.Console.PrintWarning('!!! The function : "'+key+'" already exist in the object, cannot override. !!!\n')
            #call the init function
            if hasattr(self,'initialised'):
                if self.initialised==False:
                    self.init()
                    self.initialised = True
        self.propertyChanged(prop)
    def addProperty(self,typ,name='',group='',doc='',attr=0,readonly=False,hidden=False):
        "adds a new property to this object"
        return self.__object__.addProperty(typ,name,group,doc,attr,readonly,hidden)
    def supportedProperties(self):
        "lists the property types supported by this object"
        return self.__object__.supportedProperties()
    def isDerivedFrom(self, obj):
        """returns True if this object is derived from the given C++ class, for
        example Part::Feature"""
        return self.__object__.isDerivedFrom(obj)
    def getAllDerivedFrom(self):
        "returns all parent C++ classes of this object"
        return self.__object__.getAllDerivedFrom()
    def getProperty(self,attr):
        "returns the value of a given property"
        return self.__object__.getPropertyByName(attr)
    def getTypeOfProperty(self,attr):
        "returns the type of a given property"
        return self.__object__.getTypeOfProperty(attr)
    def getGroupOfProperty(self,attr):
        "returns the group of a given property"
        return self.__object__.getGroupOfProperty(attr)
    def getDocumentationOfProperty(self,attr):
        "returns the documentation string of a given property"
        return self.__object__.getDocumentationOfProperty(attr)
    def touch(self):
        "marks this object to be recomputed"
        return self.__object__.touch()
    def purgeTouched(self):
        "removes the to-be-recomputed flag of this object"
        return self.__object__.purgeTouched()
    def __setstate__(self,value):
        """allows to save custom attributes of this object as strings, so
        they can be saved when saving the FreeCAD document"""
        return None
    def __getstate__(self):
        """reads values previously saved with __setstate__()"""
        return None
    @property
    def PropertiesList(self):
        "lists the current properties of this object"
        return self.__object__.PropertiesList
    @property
    def Type(self):
        "shows the C++ class of this object"
        return self.__object__.Type
    @property
    def Module(self):
        "gives the module this object is defined in"
        return self.__object__.Module
    @property
    def Content(self):
        """shows the contents of the properties of this object as an xml string.
        This is the content that is saved when the file is saved by FreeCAD"""
        return self.__object__.Content
    @property
    def MemSize(self):
        "shows the amount of memory this object uses"
        return self.__object__.MemSize
    @property
    def Name(self):
        "the name of this object, unique in the FreeCAD document"
        return self.__object__.Name
    @property
    def Document(self):
        "the document this object is part of"
        return self.__object__.Document
    @property
    def State(self):
        "shows if this object is valid (presents no errors)"
        return self.__object__.State
    @property
    def ViewObject(self):
        return self.__object__.ViewObject
    @ViewObject.setter
    def ViewObject(self,value):
        """returns or sets the ViewObject associated with this object. Returns
        None if FreeCAD is running in console mode"""
        self.__object__.ViewObject=value
    @property
    def InList(self):
        "lists the parents of this object"
        return self.__object__.InList
    @property
    def OutList(self):
        "lists the children of this object"
        return self.__object__.OutList



class ViewProvider(object):

    """The ViewProvider is the counterpart of the DocumentObject in
    the GUI space. It is only present when FreeCAD runs in GUI mode.
    It contains all that is needed to represent the DocumentObject in
    the 3D view and the FreeCAD interface"""
    def __init__(self):
        self.__vobject__ = None
    #def getIcon(self):
    #    return ""
    #def claimChildren(self):
    #    return self.__vobject__.Object.OutList
    #def setEdit(self,mode):
    #    return False
    #def unsetEdit(self,mode):
    #    return False
    #def attach(self):
    #    return None
    #def updateData(self, prop):
    #    return None
    #def onChanged(self, prop):
    #    return None
    def addDisplayMode(self,node,mode):
        "adds a coin node as a display mode to this object"
        self.__vobject__.addDisplayMode(node,mode)
    #def getDefaultDisplayMode(self):
    #    return ""
    #def getDisplayModes(self):
    #    return []
    #def setDisplayMode(self,mode):
    #    return mode
    def addProperty(self,type,name='',group='',doc='',attr=0,readonly=False,hidden=False):
        "adds a new property to this object"
        self.__vobject__.addProperty(type,name,group,doc,attr,readonly,hidden)
    def update(self):
        "this method is executed whenever any of the properties of this ViewProvider changes"
        self.__vobject__.update()
    def show(self):
        "switches this object to visible"
        self.__vobject__.show()
    def hide(self):
        "switches this object to invisible"
        self.__vobject__.hide()
    def isVisible(self):
        "shows wether this object is visible or invisible"
        return self.__vobject__.isVisible()
    def toString(self):
        "returns a string representation of the coin node of this object"
        return self.__vobject__.toString()
    def startEditing(self,mode=0):
        "sets this object in edit mode"
        return self.__vobject__.startEditing(mode)
    def finishEditing(self):
        "leaves edit mode for this object"
        self.__vobject__.finishEditing()
    def isEditing(self):
        "shows wether this object is in edit mode"
        self.__vobject__.isEditing()
    def setTransformation(self,trsf):
        "defines a transformation for this object"
        return self.__vobject__.setTransformation(trsf)
    def supportedProperties(self):
        "lists the property types this ViewProvider supports"
        return self.__vobject__.supportedProperties()
    def isDerivedFrom(self, obj):
        """returns True if this object is derived from the given C++ class, for
        example Part::Feature"""
        return self.__vobject__.isDerivedFrom(obj)
    def getAllDerivedFrom(self):
        "returns all parent C++ classes of this object"
        return self.__vobject__.getAllDerivedFrom()
    def getProperty(self,attr):
        "returns the value of a given property"
        return self.__vobject__.getPropertyByName(attr)
    def getTypeOfProperty(self,attr):
        "returns the type of a given property"
        return self.__vobject__.getTypeOfProperty(attr)
    def getGroupOfProperty(self,attr):
        "returns the group of a given property"
        return self.__vobject__.getGroupOfProperty(attr)
    def getDocumentationOfProperty(self,attr):
        "returns the documentation string of a given property"
        return self.__vobject__.getDocumentationOfProperty(attr)
    def __setstate__(self,value):
        """allows to save custom attributes of this object as strings, so
        they can be saved when saving the FreeCAD document"""
        return None
    def __getstate__(self):
        """reads values previously saved with __setstate__()"""
        return None
    @property
    def Annotation(self):
        "returns the Annotation coin node of this object"
        return self.__vobject__.Annotation
    @property
    def RootNode(self):
        "returns the Root coin node of this object"
        return self.__vobject__.RootNode
    @property
    def DisplayModes(self):
        "lists the display modes of this object"
        return self.__vobject__.listDisplayModes()
    @property
    def PropertiesList(self):
        "lists the current properties of this object"
        return self.__vobject__.PropertiesList
    @property
    def Type(self):
        "shows the C++ class of this object"
        return self.__vobject__.Type
    @property
    def Module(self):
        "gives the module this object is defined in"
        return self.__vobject__.Module
    @property
    def Content(self):
        """shows the contents of the properties of this object as an xml string.
        This is the content that is saved when the file is saved by FreeCAD"""
        return self.__vobject__.Content
    @property
    def MemSize(self):
        "shows the amount of memory this object uses"
        return self.__vobject__.MemSize
    @property
    def Object(self):
        "returns the DocumentObject this ViewProvider is associated to"
        return self.__vobject__.Object



class ViewProviderTester(ViewProvider):

    def attach(self, vp):
        "'''Setup the scene sub-graph of the view provider, this method is mandatory'''"
        shaded = coin.SoSeparator()
        vp.addDisplayMode(shaded,"Shaded");

    def onChanged(self, vp, prop):
        "'''Here we can do something when a single property got changed'''"
        pass

    def updateData(self, fp, prop):
        "'''If a property of the handled feature has changed we have the chance to handle this here'''"
        # fp is the handled feature, prop is the name of the property that has changed
        pass

    def getDisplayModes(self,vp):
        "'''Return a list of display modes.'''"
        return [b"Shaded"]

    def getDefaultDisplayMode(self):
        "'''Return the name of the default display mode. It must be defined in getDisplayModes.'''"
        return "Shaded"

    def setDisplayMode(self,mode):
        "'''Map the display mode defined in attach with those defined in getDisplayModes.'''"
        "'''Since they have the same names nothing needs to be done. This method is optional'''"
        return mode

    def __getstate__(self):
        return None

    def __setstate__(self, value):
        return None


class Tester(DocumentObject):

    type = "App::FeaturePython"

    def init(self):
        self.addProperty("App::PropertyInteger","nProp","group","Number of properties").nProp = 1

    def initTester(self):
        self._addProper()

    def propertyChanged(self, prop):
        App.Console.PrintMessage("Tester property changed : {} value: {}\n".format(prop, self.getProperty(prop)))

    def execute(self):
        App.Console.PrintMessage("Recompute Python Tester feature\n")
        self._addProper()
        self._removeProper()
        #This is needed because if you click on a deleted property you have a crash
        Gui.Selection.clearSelection()

    def _addProper(self):
        for i in range(0, self.nProp):
            name = "_proper{}".format(i)
            if name not in self.PropertiesList:
                self.addProperty("App::PropertyInteger", name, "group", "Property")

    def  _removeProper(self):
        _properList = ["_proper{}".format(i) for i in range(0, self.nProp)]
        for p in self.PropertiesList:
            if p.startswith("_proper"):
                if p not in _properList:
                    self.removeProperty(p)

def makeTest():
    doc = FreeCAD.ActiveDocument
    if doc is None:
        doc = FreeCAD.newDocument()
    test = doc.addObject(Tester.type, "test", Tester(), ViewProviderTester())
    test.initTester()

if __name__ == "__main__":
    # Automatically load this Feature with execfile() in Python console
    makeTest()
The property view works upon selection. So, you when you make sure to deselect the object first, change its nProp property and then select it again everything should work fine.
wmayer is right as always :D If you remove this ligne: Gui.Selection.clearSelection()
you have same probleme...

In this implementation, the change of value is validated by:
Assigning a new value and FreeCAD.ActiveDocument.recompute() in Python console
Or when there is a loss of focus after a change in the combo view of object properties (apparently)
dekoning
Posts: 51
Joined: Wed Nov 19, 2014 4:32 pm

Re: Update property amount crashes FreeCAD

Postby dekoning » Sat Jan 31, 2015 11:47 pm

Thnx, the Gui.Selection.clearSelection() works great to avoid crashes and reduces the 'user effort' by a click :)

I was playing around a bit with the Gui.Selection and Gui.Control but couldn't figure out how to get back to the Data property window. If this rule would be applied after the clearSelection than the update is complete. Do you also know how to do this?
Thnx again, I really appreciate all the help I get here !
prrvchr
Posts: 117
Joined: Sun Oct 05, 2014 7:38 pm
Location: France

Re: Update property amount crashes FreeCAD

Postby prrvchr » Sun Feb 01, 2015 12:53 am

Hi,

Put this code

Code: Select all

Gui.Selection.clearSelection()
Gui.Selection.addSelection(self.Document.getObject(self.Name))
You don't lost your selection anymore...
dekoning
Posts: 51
Joined: Wed Nov 19, 2014 4:32 pm

Re: Update property amount crashes FreeCAD

Postby dekoning » Sun Feb 01, 2015 12:35 pm

I tried your code in my onChanged method and although it selected the object again after clearing the selection, it didn't show the property window.
Then I put the code in the execute method just like you did and it works perfect :)
prrvchr
Posts: 117
Joined: Sun Oct 05, 2014 7:38 pm
Location: France

Re: Update property amount crashes FreeCAD

Postby prrvchr » Sun Feb 01, 2015 1:56 pm

Then I put the code in the execute method just like you did and it works perfect :)
That's true, you must put this code viewtopic.php?f=22&t=9499#p77498 at the end of execute() method.