[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