[x3d-public] x3d.py problem
Andreas Plesch
andreasplesch at gmail.com
Sun May 17 06:46:18 PDT 2020
Thanks, Don. I can confirm that 0.0.30 fixes the issue. -Andreas
On Sun, May 17, 2020 at 1:25 AM Don Brutzman <brutzman at nps.edu> wrote:
> Thanks for all the help. Yes it was pretty involved to untangle some
> things but it all appears to be working now. Special thanks to Loren for
> help through the thicket.
>
> Immutable "constants" such as DEFAULT_VALUE etc. are now exposed as
> functions. Default values are being inserted wherever appropriate.
> Hopefully a lot fewer mysteries.
>
> Also upgraded tooltips documentation to X3Dv4.
>
> Also added a smoke test for the original problem you reported Andreas.
>
> A lot changed internally, but little should change when using x3d.py
> package. Deployed on PyPi as version 0.0.30.
>
> [1] Python package x3d
> https://pypi.org/project/x3d
>
> [2] X3DPSAIL, X3D Python Scene Access Interface Library
> https://www.web3d.org/x3d/stylesheets/python/python.html
>
> PythonX3dSmokeTests.py excerpts with corresponding build.examples.log.txt
> outputs:
> ================================================================
> print ('SFBool().NAME() = ' + SFBool().NAME());
> print ('SFBool().DEFAULT_VALUE()= ' + str(SFBool().DEFAULT_VALUE()));
> print ('SFBool().ARRAY_TYPE() = ' + str(SFBool().ARRAY_TYPE()));
> print ('SFBool().TUPLE_SIZE() = ' + str(SFBool().TUPLE_SIZE()));
> -----
> SFBool().NAME() = SFBool
> SFBool().DEFAULT_VALUE()= True
> SFBool().ARRAY_TYPE() = False
> SFBool().TUPLE_SIZE() = 1
> ================================================================
>
> # smoke test adapted from Andreas Plesch 12 May 2020
> # http://web3d.org/pipermail/x3d-public_web3d.org/2020-May/012596.html
> # x3d.py solution adapted from Vince Marchetti and Loren Peitso
> print()
> testScene1 = Scene()
> testScene1.children.append(Group())
> testScene2 = Scene()
> if testScene2.hasChild():
> print('*** empty default children() test failed:')
> else:
> print('*** empty default children() test passed:')
> print('initialization testScene1.XML()=')
> print(testScene1.XML())
> print('initialization testScene2.XML()=')
> print(testScene2.XML())
> -----
> *** empty default children() test passed:
> initialization testScene1.XML()=
> <Scene>
> <Group/>
> </Scene>
>
> initialization testScene2.XML()=
> <Scene/>
> ================================================================
>
> Here is example pattern for Group. Pure python throughout.
>
> Feedback and test reports welcome. Have fun with X3D Python! 8)
>
> ================================================================
> class Group(_X3DGroupingNode):
> """
> Group is a Grouping node that can contain most nodes.
> """
> def NAME(self):
> return 'Group'
> def SPECIFICATION_URL(self):
> return ''
> def TOOLTIP_URL(self):
> return 'https://www.web3d.org/x3d/tooltips/X3dTooltips.html#Group
> '
> def FIELD_DECLARATIONS(self):
> return [ # name, defaultValue, type, accessType, inheritedFrom
> ('bboxCenter', (0, 0, 0), FieldType.SFVec3f,
> AccessType.initializeOnly, 'X3DGroupingNode'),
> ('bboxSize', (-1, -1, -1), FieldType.SFVec3f,
> AccessType.initializeOnly, 'X3DGroupingNode'),
> ('displayBBox', False, FieldType.SFBool, AccessType.inputOutput,
> 'X3DGroupingNode'),
> ('visible', True, FieldType.SFBool, AccessType.inputOutput,
> 'X3DGroupingNode'),
> ('children', list(), FieldType.MFNode, AccessType.inputOutput,
> 'X3DGroupingNode'),
> ('DEF', '', FieldType.SFString, AccessType.inputOutput,
> 'X3DNode'),
> ('USE', '', FieldType.SFString, AccessType.inputOutput,
> 'X3DNode'),
> ('class_', '', FieldType.SFString, AccessType.inputOutput,
> 'X3DNode'),
> ('IS', None, FieldType.SFNode, AccessType.inputOutput, 'X3DNode'),
> ('metadata', None, FieldType.SFNode, AccessType.inputOutput,
> 'X3DNode')]
> def __init__(self,
> bboxCenter=(0, 0, 0),
> bboxSize=(-1, -1, -1),
> displayBBox=False,
> visible=True,
> children=None,
> DEF='',
> USE='',
> class_='',
> IS=None,
> metadata=None):
> # if _DEBUG: print('... in ConcreteNode Group __init__ calling
> super.__init__(' + str(DEF) + ',' + str(USE) + ',' + str(class_) + ',' +
> str(metadata) + ',' + str(IS) + ')', flush=True)
> super().__init__(DEF, USE, class_, IS, metadata) # fields for
> _X3DNode only
> self.bboxCenter = bboxCenter
> self.bboxSize = bboxSize
> self.displayBBox = displayBBox
> self.visible = visible
> self.children = children
> @property # getter - - - - - - - - - -
> def bboxCenter(self):
> """Bounding box center accompanies bboxSize and provides an
> optional hint for bounding box position offset from origin of local
> coordinate system."""
> return self.__bboxCenter
> @bboxCenter.setter
> def bboxCenter(self, bboxCenter):
> if bboxCenter is None:
> bboxCenter = (0, 0, 0) # default
> assertValidSFVec3f(bboxCenter)
> self.__bboxCenter = bboxCenter
> @property # getter - - - - - - - - - -
> def bboxSize(self):
> """Bounding box size is usually omitted, and can easily be
> calculated automatically by an X3D player at scene-loading time with
> minimal computational cost."""
> return self.__bboxSize
> @bboxSize.setter
> def bboxSize(self, bboxSize):
> if bboxSize is None:
> bboxSize = (-1, -1, -1) # default
> assertValidSFVec3f(bboxSize)
> assertBoundingBox('bboxSize', bboxSize)
> self.__bboxSize = bboxSize
> @property # getter - - - - - - - - - -
> def displayBBox(self):
> """Whether to display bounding box for associated geometry,
> aligned with world coordinates."""
> return self.__displayBBox
> @displayBBox.setter
> def displayBBox(self, displayBBox):
> if displayBBox is None:
> displayBBox = False # default
> assertValidSFBool(displayBBox)
> self.__displayBBox = displayBBox
> @property # getter - - - - - - - - - -
> def visible(self):
> """Whether or not renderable content within this node is visually
> displayed."""
> return self.__visible
> @visible.setter
> def visible(self, visible):
> if visible is None:
> visible = True # default
> assertValidSFBool(visible)
> self.__visible = visible
> @property # getter - - - - - - - - - -
> def children(self):
> """[X3DChildNode] Grouping nodes contain an ordered list of
> children nodes."""
> return self.__children
> @children.setter
> def children(self, children):
> if children is None:
> children = MFNode.DEFAULT_VALUE(self)
> assertValidMFNode(children)
> self.__children = children
> # hasChild() function - - - - - - - - - -
> def hasChild(self):
> ''' Whether or not this node has any child node, statement or
> comment '''
> return (self.children) or (self.IS) or (self.metadata)
> # output function - - - - - - - - - -
> def XML(self, indentLevel=0, syntax="XML"):
> """ Provide Canonical X3D output serialization using XML
> encoding. """
> result = ''
> indent = ' ' * indentLevel
> result = indent ### confirm
> # if _DEBUG: result += indent + '# invoked class function
> Group.XML(self=' + str(self) + ', indentLevel=' + str(indentLevel) + '),
> indent="' + indent + '"'
> # print(result)
> result += '<Group'
> if self.DEF:
> result += " DEF='" + self.DEF + "'"
> if self.USE:
> result += " USE='" + self.USE + "'"
> if self.bboxCenter != (0, 0, 0):
> result += " bboxCenter='" + SFVec3f(self.bboxCenter).XML() +
> "'"
> if self.bboxSize != (-1, -1, -1):
> result += " bboxSize='" + SFVec3f(self.bboxSize).XML() + "'"
> if self.class_:
> result += " class_='" + self.class_ + "'"
> if self.displayBBox != False:
> result += " displayBBox='" + SFBool(self.displayBBox).XML() +
> "'"
> if self.visible != True:
> result += " visible='" + SFBool(self.visible).XML() + "'"
> if not self.hasChild():
> if syntax.upper() == "HTML5":
> result += '></Group>' + '\n' # no self-closing tags
> allowed by HTML5
> elif syntax.upper() == "XML":
> result += '/>' + '\n' # singleton element
> else:
> raise X3DValueError('.toXML(syntax=' + syntax + ') is
> incorrect, allowed values are "HTML5" and "XML"')
> else:
> result += '>' + '\n'
> if self.IS: # output this SFNode
> result +=
> self.IS.XML(indentLevel=indentLevel+1,syntax=syntax)
> if self.metadata: # output this SFNode
> result +=
> self.metadata.XML(indentLevel=indentLevel+1,syntax=syntax)
> ### if self.children: # walk each child in MFNode list, if any
> ### print('* Group found self.children with self.hasChild()='
> + str(self.hasChild()) + ' and len(children)=' + str(len(self.children)) +
> ', now invoking XML(' + str(indentLevel+1) + ')', flush=True)
> for each in self.children:
> result += each.XML(indentLevel=indentLevel+1,syntax=syntax)
> result += indent + '</Group>' + '\n'
> # print('XML serialization complete.', flush=True)
> return result
> # output function - - - - - - - - - -
> def HTML5(self, indentLevel=0):
> """ Provide HTML5 output serialization using XML encoding with no
> singleton self-closing elements. """
> return self.XML(indentLevel, syntax="HTML5")
> # output function - - - - - - - - - -
> def VRML(self, indentLevel=0, VRML97=False):
> """ Provide X3D output serialization using VRML encoding. """
> result = ''
> indent = ' ' * indentLevel
> # if _DEBUG: result += indent + '# invoked class function
> Group.VRML(self=' + str(self) + ', indentLevel=' + str(indentLevel) + '),
> indent="' + indent + '"'
> # print(result)
> if indentLevel == 0:
> result += '\n'
> if self.DEF:
> result += 'DEF ' + self.DEF + ' ' + 'Group' + ' {'
> elif self.USE:
> result += 'USE ' + self.USE # no node name, nothing follows
> else:
> result += 'Group' + ' {'
> if self.bboxCenter != (0, 0, 0):
> result += " bboxCenter " + SFVec3f(self.bboxCenter).VRML() +
> ""
> if self.bboxSize != (-1, -1, -1):
> result += " bboxSize " + SFVec3f(self.bboxSize).VRML() + ""
> if self.class_:
> result += " class_ " + '"' + self.class_ + '"' + ""
> if self.displayBBox != False:
> result += " displayBBox " + SFBool(self.displayBBox).VRML() +
> ""
> if self.visible != True:
> result += " visible " + SFBool(self.visible).VRML() + ""
> if self.IS: # output this SFNode
> result += '\n' + ' ' + indent + 'IS ' +
> self.IS.VRML(indentLevel=indentLevel+1,VRML97=VRML97)
> if self.metadata: # output this SFNode
> result += '\n' + ' ' + indent + 'metadata ' +
> self.metadata.VRML(indentLevel=indentLevel+1,VRML97=VRML97)
> if self.children: # walk each child in MFNode list, if any
> for each in self.children:
> result +=
> each.VRML(indentLevel=indentLevel+1,VRML97=VRML97)
> if not self.USE:
> result += ' }'
> # print('VRML serialization complete.', flush=True)
> return result
> ================================================================
>
>
> On 5/14/2020 3:14 PM, Peitso, Loren (CIV) wrote:
> > Don,
> >
> > Here is an even clearer explanation:
> >
> > https://docs.python-guide.org/writing/gotchas/
> >
> > OK, I have not run into this previously because my style heavily favors
> use of the property/setter pairs for instance attributes, and when you do
> it that way it naturally make you write the code more like vice has it.
> >
> > You already have those for children in Scene but in the setter you have
> a non-standerd implementation:
> >
> > current:
> > def children(self, children=None):
> > if children is None:
> > children = MFNode.DEFAULT_VALUE
> > isValidMFNode(children)
> > self.__children = children
> >
> > change to this:
> > #runs when self.children is accessed on the LHS of an assignment operator
> > # I am in favor of giving parameters unique names to avoid
> confusion between the arrtibute that is being managed and the values being
> passed
> > #also note that a setter NEVER needs a default argument, it is only EVER
> called if the attribute is being assigned, and the result of the RHS is
> passed as the single argument
> >
> > def children(self, childrenArg):
> > if childrenArg is None:
> > self.__children = MFNode.DEFAULT_VALUE
> > else:
> > isValidMFNode(childrenArg) #don't need this check
> on MFNode.DEFAULT_VALUE because it had better be correct or else...
> > self.__children = childrenArg
>
> > This manages getting children properly set and does not require an empty
> list default argument at all. So make the Scene init method start like
> this as vince suggests
> >
> >>> def __init__(self, children=None):
> >>> self.children = children
> >
> > and use the above modified property/setter pair and you can leave off
> the rest of what vince put because the setup property/setter pair is
> overall superior.
> >
> > v/r Loren
>
>
>
> On 5/13/2020 4:35 AM, Andreas Plesch wrote:
> > Thanks for recognizing the issue, and suggesting a solution. All nodes
> with children, or other mutable valued parameters, are likely affected.
> >
> > Andreas
> >
> > ---on the phone---
> >
> > On Tue, May 12, 2020, 9:26 PM vmarchetti at kshell.com <mailto:
> vmarchetti at kshell.com> <vmarchetti at kshell.com <mailto:
> vmarchetti at kshell.com>> wrote:
> >
> > This is an unfortunate and common bug that occurs in Python.
> >
> > In the definition of the class Scene (around line 8795 of x3d.py) is
> this definition of the __init__ function for Scene:
> >
> > def __init__(self, children=list()):
> > self.children = children
> >
> > This code is read once when the x3d.py module is loaded, and it
> creates a class-level default value for the children argument.
> > If you ever use the default value by calling Scene() {with no
> parameter}, this class-level instance of list is modified, and this SAME
> INSTANCE of list
> > if used for any further calls of Scene()
> >
> > The way to avoid this is to define the __init__ function this way:
> >
> > def __init__(self, children=None):
> > if children is None:
> > self.children = list() # creates a new empty list for each Scene
> instance
> > else:
> > self.children = children
> >
> >
> > Vince Marchetti
> >
> >
> >> On May 12, 2020, at 5:59 PM, Andreas Plesch <
> andreasplesch at gmail.com <mailto:andreasplesch at gmail.com>> wrote:
> >>
> >> I discovered a simple workaround. Always pass a children parameter,
> even if empty.
> >>
> >> On Tue, May 12, 2020 at 5:16 PM Andreas Plesch <
> andreasplesch at gmail.com <mailto:andreasplesch at gmail.com>> wrote:
> >>
> >> Hi,
> >>
> >> after some debugging I discovered another issue with x3d.py. It
> can be boiled down t o a few lines of code:
> >>
> >> import x3d.x3d as x
> >>
> >> my_scene = x.Scene()
> >> group = x.Group()
> >> my_scene.children.append(group)
> >> print(my_scene.XML())
> >>
> >> => output
> >> => <Scene>
> >> => <Group/>
> >> => </Scene>
> >>
> >> my_other_scene = x.Scene()
> >> print(my_other_scene.XML())
> >>
> >> => output
> >> => <Scene>
> >> => <Group/>
> >> => </Scene>
> >>
> >> I think my_other_scene should be empty but remembers the
> content of the first scene.
> >> Can you reproduce that ?
> >> On the other hand this works (in a new session):
> >>
> >> my_scene = x.Scene( children = [x.Group()] )
> >> print(my_scene.XML())
> >>
> >> => output
> >> => <Scene>
> >> => <Group/>
> >> => </Scene>
> >>
> >> my_other_scene = x.Scene()
> >> print(my_other_scene.XML())
> >>
> >> => output
> >> => <Scene>
> >> => </Scene>
> >>
> >> --
> >> Andreas Plesch
> >> Waltham, MA 02453
> >>
> >>
> >>
> >> --
> >> Andreas Plesch
> >> Waltham, MA 02453
> >> _______________________________________________
> >> x3d-public mailing list
> >> x3d-public at web3d.org <mailto:x3d-public at web3d.org>
> >> http://web3d.org/mailman/listinfo/x3d-public_web3d.org
> >
>
> all the best, Don
> --
> Don Brutzman Naval Postgraduate School, Code USW/Br
> brutzman at nps.edu
> Watkins 270, MOVES Institute, Monterey CA 93943-5000 USA +1.831.656.2149
> X3D graphics, virtual worlds, navy robotics
> http://faculty.nps.edu/brutzman
>
--
Andreas Plesch
Waltham, MA 02453
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://web3d.org/pipermail/x3d-public_web3d.org/attachments/20200517/6e1a6468/attachment-0001.html>
More information about the x3d-public
mailing list