ya2 · news · projects · code · about

test handle
authorFlavio Calva <f.calva@gmail.com>
Sat, 19 Nov 2022 07:35:24 +0000 (09:35 +0200)
committerFlavio Calva <f.calva@gmail.com>
Sat, 19 Nov 2022 07:35:24 +0000 (09:35 +0200)
assets/models/blend/test_handle/albedo.png [new file with mode: 0644]
assets/models/blend/test_handle/test_handle.blend [new file with mode: 0644]
pmachines/editor/inspector.py
pmachines/editor/scene.py
pmachines/items/test_item.py [new file with mode: 0644]
pmachines/scene.py
prj.org
ya2/p3d/asserts.py

diff --git a/assets/models/blend/test_handle/albedo.png b/assets/models/blend/test_handle/albedo.png
new file mode 100644 (file)
index 0000000..e7e35f8
Binary files /dev/null and b/assets/models/blend/test_handle/albedo.png differ
diff --git a/assets/models/blend/test_handle/test_handle.blend b/assets/models/blend/test_handle/test_handle.blend
new file mode 100644 (file)
index 0000000..28d7a3a
Binary files /dev/null and b/assets/models/blend/test_handle/test_handle.blend differ
index b5440a0cb9be91fab04d8ba23b13af88ec10c67a..4d64dc59b25a431dc0018afac5893f3a30c029ff 100644 (file)
@@ -9,6 +9,7 @@ from direct.gui.DirectGui import DirectButton, DirectFrame, DirectEntry, DirectO
 from direct.gui.DirectGuiGlobals import FLAT, NORMAL
 from direct.gui.OnscreenText import OnscreenText
 from direct.showbase.DirectObject import DirectObject
+from ya2.p3d.gfx import P3dGfxMgr
 from pmachines.items.item import ItemStrategy, FixedStrategy, StillStrategy
 from pmachines.items.box import HitStrategy
 from pmachines.items.domino import DownStrategy, UpStrategy
@@ -270,3 +271,259 @@ class Inspector(DirectObject):
         self._frm.destroy()
         self.ignore('item-rototranslated')
         messenger.send('editor-inspector-destroy')
+
+
+class PixelSpaceInspector(DirectObject):
+
+    def __init__(self, item, all_items):
+        super().__init__()
+        self.__item = item
+        self.__all_items = all_items
+        self._font = base.loader.load_font(
+            'assets/fonts/Hanken-Book.ttf')
+        self._font.clear()
+        self._font.set_pixels_per_unit(60)
+        self._font.set_minfilter(Texture.FTLinearMipmapLinear)
+        self._font.set_outline((0, 0, 0, 1), .8, .2)
+        self._common = {
+            'scale': .046,
+            'text_font': self._font,
+            'text_fg': (.9, .9, .9, 1),
+            'relief': FLAT,
+            'frameColor': (.4, .4, .4, .14),
+            'rolloverSound': loader.load_sfx(
+                'assets/audio/sfx/rollover.ogg'),
+            'clickSound': loader.load_sfx(
+                'assets/audio/sfx/click.ogg')}
+        w, h = .8, .36
+        self._frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+                                frameSize=(0, w, -h, 0),
+                                parent=base.a2dTopRight,
+                                pos=(-w, 0, 0))
+        self.__z = -.08
+        p = self.__item._np.get_pos()
+        _id = ''
+        if 'id' in self.__item.json:
+            _id = self.__item.json['id']
+        t, pos_entry = self.__add_row(_('position'), f'{round(p.x, 3)}, {round(p.z, 3)}', self.on_edit_position)
+        t, id_entry = self.__add_row(_('id'), _id, self.on_edit_id)
+        fields = ['position', 'id']
+        Entries = namedtuple('Entries', fields)
+        self.__entries = Entries(pos_entry, id_entry)
+        def load_images_btn(path, col):
+            colors = {
+                'gray': [
+                    (.6, .6, .6, 1),  # ready
+                    (1, 1, 1, 1), # press
+                    (.8, .8, .8, 1), # rollover
+                    (.4, .4, .4, .4)],
+                'green': [
+                    (.1, .68, .1, 1),
+                    (.1, 1, .1, 1),
+                    (.1, .84, .1, 1),
+                    (.4, .1, .1, .4)]}[col]
+            return [self.__load_img_btn(path, col) for col in colors]
+        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+        DirectButton(
+            image=load_images_btn('exitRight', 'gray'), scale=.05,
+            pos=(.06, 1, -h + .06),
+            parent=self._frm, command=self.destroy, state=NORMAL, relief=FLAT,
+            frameColor=fcols[0],
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        self.accept('item-rototranslated', self.__on_item_rototranslated)
+        DirectButton(
+            image=load_images_btn('trashcan', 'gray'), scale=.05,
+            pos=(.18, 1, -h + .06),
+            parent=self._frm, command=self.__delete_item, state=NORMAL, relief=FLAT,
+            frameColor=fcols[0],
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+
+    def __add_row(self, label, text, callback):
+        tw = 10
+        t = OnscreenText(
+            label,
+            pos=(.03, self.__z), parent=self._frm,
+            font=self._common['text_font'],
+            scale=self._common['scale'],
+            fg=self._common['text_fg'],
+            wordwrap=20, align=TextNode.ALeft)
+        e = DirectEntry(
+            scale=self._common['scale'],
+            pos=(.30, 1, self.__z),
+            entryFont=self._font,
+            width=tw,
+            cursorKeys=True,
+            frameColor=self._common['frameColor'],
+            initialText=text,
+            parent=self._frm,
+            text_fg=self._common['text_fg'],
+            command=callback)
+        self.__z -= .1
+        return t, e
+
+    def __load_img_btn(self, path, col):
+        img = OnscreenImage('assets/images/buttons/%s.dds' % path)
+        img.set_transparency(True)
+        img.set_color(col)
+        img.detach_node()
+        return img
+
+    def __on_item_rototranslated(self, np):
+        pos = P3dGfxMgr.pos2d_p2d(np)
+        self.__entries.position.set('%s %s' % (str(round(pos[0], 3)), str(round(pos[1], 3))))
+        self.__item.json['position'] = list(pos)
+
+    def __delete_item(self):
+        messenger.send('editor-inspector-delete', [self.__item])
+        self.destroy()
+
+    @property
+    def item(self):
+        return self.__item
+
+    def on_edit_position(self, txt):
+        x, z = map(float, txt.split())
+        self.__item.position = [x, 0, z]
+
+    def on_edit_id(self, txt):
+        self.__item.id = txt
+
+    def __actually_close(self, arg):
+        self.__entries.strategy.set('')
+        self.__entries.strategy_args.set('')
+        self.__dialog.cleanup()
+
+    def destroy(self):
+        self._frm.destroy()
+        self.ignore('item-rototranslated')
+        messenger.send('editor-inspector-destroy')
+
+
+class WorldSpaceInspector(DirectObject):
+
+    def __init__(self, item, all_items):
+        super().__init__()
+        self.__item = item
+        self.__all_items = all_items
+        self._font = base.loader.load_font(
+            'assets/fonts/Hanken-Book.ttf')
+        self._font.clear()
+        self._font.set_pixels_per_unit(60)
+        self._font.set_minfilter(Texture.FTLinearMipmapLinear)
+        self._font.set_outline((0, 0, 0, 1), .8, .2)
+        self._common = {
+            'scale': .046,
+            'text_font': self._font,
+            'text_fg': (.9, .9, .9, 1),
+            'relief': FLAT,
+            'frameColor': (.4, .4, .4, .14),
+            'rolloverSound': loader.load_sfx(
+                'assets/audio/sfx/rollover.ogg'),
+            'clickSound': loader.load_sfx(
+                'assets/audio/sfx/click.ogg')}
+        w, h = .8, .36
+        self._frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+                                frameSize=(0, w, -h, 0),
+                                parent=base.a2dTopRight,
+                                pos=(-w, 0, 0))
+        self.__z = -.08
+        p = self.__item._np.get_pos()
+        _id = ''
+        if 'id' in self.__item.json:
+            _id = self.__item.json['id']
+        t, pos_entry = self.__add_row(_('position'), f'{round(p.x, 3)}, {round(p.z, 3)}', self.on_edit_position)
+        t, id_entry = self.__add_row(_('id'), _id, self.on_edit_id)
+        fields = ['position', 'id']
+        Entries = namedtuple('Entries', fields)
+        self.__entries = Entries(pos_entry, id_entry)
+        def load_images_btn(path, col):
+            colors = {
+                'gray': [
+                    (.6, .6, .6, 1),  # ready
+                    (1, 1, 1, 1), # press
+                    (.8, .8, .8, 1), # rollover
+                    (.4, .4, .4, .4)],
+                'green': [
+                    (.1, .68, .1, 1),
+                    (.1, 1, .1, 1),
+                    (.1, .84, .1, 1),
+                    (.4, .1, .1, .4)]}[col]
+            return [self.__load_img_btn(path, col) for col in colors]
+        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+        DirectButton(
+            image=load_images_btn('exitRight', 'gray'), scale=.05,
+            pos=(.06, 1, -h + .06),
+            parent=self._frm, command=self.destroy, state=NORMAL, relief=FLAT,
+            frameColor=fcols[0],
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        self.accept('item-rototranslated', self.__on_item_rototranslated)
+        DirectButton(
+            image=load_images_btn('trashcan', 'gray'), scale=.05,
+            pos=(.18, 1, -h + .06),
+            parent=self._frm, command=self.__delete_item, state=NORMAL, relief=FLAT,
+            frameColor=fcols[0],
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+
+    def __add_row(self, label, text, callback):
+        tw = 10
+        t = OnscreenText(
+            label,
+            pos=(.03, self.__z), parent=self._frm,
+            font=self._common['text_font'],
+            scale=self._common['scale'],
+            fg=self._common['text_fg'],
+            wordwrap=20, align=TextNode.ALeft)
+        e = DirectEntry(
+            scale=self._common['scale'],
+            pos=(.30, 1, self.__z),
+            entryFont=self._font,
+            width=tw,
+            cursorKeys=True,
+            frameColor=self._common['frameColor'],
+            initialText=text,
+            parent=self._frm,
+            text_fg=self._common['text_fg'],
+            command=callback)
+        self.__z -= .1
+        return t, e
+
+    def __load_img_btn(self, path, col):
+        img = OnscreenImage('assets/images/buttons/%s.dds' % path)
+        img.set_transparency(True)
+        img.set_color(col)
+        img.detach_node()
+        return img
+
+    def __on_item_rototranslated(self, np):
+        pos = np.get_pos()
+        self.__entries.position.set('%s %s' % (str(round(pos.x, 3)), str(round(pos.z, 3))))
+        self.__item.json['position'] = list(pos)
+
+    def __delete_item(self):
+        messenger.send('editor-inspector-delete', [self.__item])
+        self.destroy()
+
+    @property
+    def item(self):
+        return self.__item
+
+    def on_edit_position(self, txt):
+        x, z = map(float, txt.split())
+        self.__item.position = [x, 0, z]
+
+    def on_edit_id(self, txt):
+        self.__item.id = txt
+
+    def __actually_close(self, arg):
+        self.__entries.strategy.set('')
+        self.__entries.strategy_args.set('')
+        self.__dialog.cleanup()
+
+    def destroy(self):
+        self._frm.destroy()
+        self.ignore('item-rototranslated')
+        messenger.send('editor-inspector-destroy')
index b2d3795e2677073bfd3dd92dd26e58aa135f0cb5..127cee682688b898e56336761ed1ff5bc6a48e47 100644 (file)
@@ -14,17 +14,21 @@ from direct.gui.DirectGuiGlobals import FLAT, NORMAL
 from direct.gui.OnscreenText import OnscreenText
 from direct.showbase.DirectObject import DirectObject
 from pmachines.items.item import Item
+from pmachines.items.test_item import PixelSpaceTestItem, WorldSpaceTestItem
 from pmachines.editor.scene_list import SceneList
-from pmachines.editor.inspector import Inspector
+from pmachines.editor.inspector import Inspector, PixelSpaceInspector, WorldSpaceInspector
 from pmachines.editor.start_items import StartItems
+from ya2.p3d.gfx import P3dGfxMgr
 
 
 class SceneEditor(DirectObject):
 
-    def __init__(self, json, json_name, context, add_item, items):
+    def __init__(self, json, json_name, context, add_item, items, world, mouse_plane_node):
         super().__init__()
         self.__items = items
         self.__json = json
+        self.__world = world
+        self.__mouse_plane_node = mouse_plane_node
         if not json_name:
             self.__json = json = {
                 'name': '',
@@ -190,17 +194,48 @@ class SceneEditor(DirectObject):
             frameColor=fcols[0],
             rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
             clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        self.__test_items = []
+        self.__set_test_items()
         messenger.send('editor-start')
         self.accept('editor-item-click', self.__on_item_click)
         self.accept('editor-inspector-destroy', self.__on_inspector_destroy)
         self.accept('editor-start-items-save', self.__on_start_items_save)
         self.accept('editor-start-items-destroy', self.__on_start_items_destroy)
 
+    def __set_test_items(self):
+        for pixel_space_item in self.__json['test_items']['pixel_space']:
+            print(pixel_space_item['id'], pixel_space_item['position'])
+            pos_pixel = pixel_space_item['position']
+            win_res = base.win.getXSize(), base.win.getYSize()
+            pos_win = (pos_pixel[0] / win_res[0] * 2 - 1,
+                       1 - pos_pixel[1] / win_res[1] * 2)
+            p_from, p_to = P3dGfxMgr.world_from_to(pos_win)
+            hits = self.__world.ray_test_all(p_from, p_to).get_hits()
+            for hit in self.__world.ray_test_all(p_from, p_to).get_hits():
+                if hit.get_node() == self.__mouse_plane_node:
+                    pos = hit.get_hit_pos()
+            self.__set_test_item(pos, pixel_space_item, PixelSpaceTestItem)
+        for world_space_item in self.__json['test_items']['world_space']:
+            print(world_space_item['id'], world_space_item['position'])
+            self.__set_test_item(world_space_item['position'], world_space_item, WorldSpaceTestItem)
+
+    def __set_test_item(self, pos, json, item_class):
+        test_item = item_class(
+            self.__context.world,
+            self.__context.plane_node,
+            self.__context.cb_inst,
+            self.__context.curr_bottom,
+            self.__context.repos,
+            json,
+            pos=(pos[0], 0, pos[-1]),
+            model_scale=.2)
+        self.__test_items += [test_item]
+
     def __on_close(self):
         self.__json['name'] = self.__name_entry.get()
         self.__json['instructions'] = self.__instructions_entry.get()
         if self.__compute_hash() == self.__json['version']:
-            self._frm.destroy()
+            self.destroy()
         else:
             self.__dialog = YesNoDialog(dialogName='Unsaved changes',
                                         text=_('You have unsaved changes. Really quit?'),
@@ -221,18 +256,29 @@ class SceneEditor(DirectObject):
     def __on_new_item(self, item):
         info(f'new {item}')
         item_json = {}
+        scale = 1
+        if item in ['PixelSpaceTestItem', 'WorldSpaceTestItem']:
+            scale = .2
         _item = self.__new_items[item](
             self.__context.world,
             self.__context.plane_node,
             self.__context.cb_inst,
             self.__context.curr_bottom,
             self.__context.repos,
-            item_json)
+            item_json,
+            model_scale=scale)
         _item._Item__editing = True
         self.__add_item(_item)
         item_json['class'] = _item.__class__.__name__
         item_json['position'] = list(_item._np.get_pos())
-        self.__json['items'] += [item_json]
+        if item == 'PixelSpaceTestItem':
+            del item_json['class']
+            self.__json['test_items']['pixel_space'] += [item_json]
+        elif item == 'WorldSpaceTestItem':
+            del item_json['class']
+            self.__json['test_items']['world_space'] += [item_json]
+        else:
+            self.__json['items'] += [item_json]
 
     def __actually_close(self, arg):
         if arg:
@@ -270,7 +316,12 @@ class SceneEditor(DirectObject):
         if self.__inspector and self.__inspector.item == item: return
         if self.__inspector:
             self.__inspector.destroy()
-        self.__inspector = Inspector(item, self.__items)
+        if item.__class__ == PixelSpaceTestItem:
+            self.__inspector = PixelSpaceInspector(item, self.__items)
+        elif item.__class__ == WorldSpaceTestItem:
+            self.__inspector = WorldSpaceInspector(item, self.__items)
+        else:
+            self.__inspector = Inspector(item, self.__items)
 
     def __on_inspector_destroy(self):
         self.__inspector = None
@@ -290,6 +341,10 @@ class SceneEditor(DirectObject):
     def __on_start_items_destroy(self):
         self.__start_items = None
 
+    @property
+    def test_items(self):
+        return self.__test_items
+
     def destroy(self):
         self._frm.destroy()
         if self.__inspector:
@@ -298,3 +353,5 @@ class SceneEditor(DirectObject):
         self.ignore('editor-inspector-destroy')
         self.ignore('editor-start-items-save')
         self.ignore('editor-start-items-destroy')
+        for t in self.__test_items: t.destroy()
+        self.__test_items = []
diff --git a/pmachines/items/test_item.py b/pmachines/items/test_item.py
new file mode 100644 (file)
index 0000000..760409c
--- /dev/null
@@ -0,0 +1,19 @@
+from panda3d.bullet import BulletBoxShape
+from pmachines.items.item import Item
+
+
+class TestItem(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/test_handle/test_handle.bam', json, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale)
+        self._np.set_bin('fixed', 0)
+        self._np.set_depth_test(False)
+        self._np.set_depth_write(False)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((.5, .5, .5)))
+
+class PixelSpaceTestItem(TestItem): pass
+
+
+class WorldSpaceTestItem(TestItem): pass
index 740a688fee5e3438faeb9ee2358363ea051bbd94..480785d9a67b14a8c7f35becc7b5617c18803f67 100644 (file)
@@ -58,7 +58,7 @@ class Scene(DirectObject):
         self._set_lights()
         self._set_input()
         self._set_mouse_plane()
-        self.items = []
+        self.__items = []
         self._test_items = []
         self.reset()
         self._state = 'init'
@@ -70,7 +70,7 @@ class Scene(DirectObject):
         else:
             self._set_instructions()
         self._bg = Background()
-        self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
+        self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.__items)
         self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
         if auto_start_editor:
             self._set_editor()
@@ -109,9 +109,16 @@ class Scene(DirectObject):
     def __process_json_escape(self, string):
         return bytes(string, 'utf-8').decode('unicode-escape')
 
+    @property
+    def items(self):
+        items = self.__items[:]
+        if self.__scene_editor:
+            items += self.__scene_editor.test_items
+        return items
+
     def _set_items(self):
         if not self.__json_name: return
-        self.items = []
+        self.__items = []
         self._test_items = []
         if not self.json:
             with open(f'assets/scenes/{self.__json_name}.json') as f:
@@ -129,7 +136,7 @@ class Scene(DirectObject):
                 args['mass'] = item['mass']
             if 'friction' in item:
                 args['friction'] = item['friction']
-            self.items += [self.__code2class(item['class'])(**args)]
+            self.__items += [self.__code2class(item['class'])(**args)]
         for item in self.json['items']:
             args = {
                 'world': self._world,
@@ -151,13 +158,13 @@ class Scene(DirectObject):
                 args['restitution'] = item['restitution']
             if 'friction' in item:
                 args['friction'] = item['friction']
-            self.items += [self.__code2class(item['class'])(**args)]
+            self.__items += [self.__code2class(item['class'])(**args)]
             if 'strategy' in item:
                 match item['strategy']:
                     case 'DownStrategy':
-                        self.items[-1].set_strategy(self.__code2class(item['strategy'])(self.items[-1]._np, *item['strategy_args']))
+                        self.__items[-1].set_strategy(self.__code2class(item['strategy'])(self.__items[-1]._np, *item['strategy_args']))
                     case 'HitStrategy':
-                        self.items[-1].set_strategy(self.__code2class(item['strategy'])(self.__item_with_id(item['strategy_args'][0]), self.items[-1].node, self.items[-1]._world))
+                        self.__items[-1].set_strategy(self.__code2class(item['strategy'])(self.__item_with_id(item['strategy_args'][0]), self.items[-1].node, self.__items[-1]._world))
 
     def __code2class(self, code):
         return {
@@ -171,7 +178,7 @@ class Scene(DirectObject):
         }[code]
 
     def __item_with_id(self, id):
-        for item in self.items:
+        for item in self.__items:
             if 'id' in item.json and item.json['id'] == id:
                 return item
 
@@ -205,15 +212,15 @@ class Scene(DirectObject):
 
     def current_bottom(self):
         curr_bottom = 1
-        for item in self.items:
+        for item in self.__items:
             if item.repos_done:
                 curr_bottom = min(curr_bottom, item.get_bottom())
         return curr_bottom
 
     def reset(self):
-        [itm.destroy() for itm in self.items]
+        [itm.destroy() for itm in self.__items]
         [itm.remove_node() for itm in self._test_items]
-        self.items = []
+        self.__items = []
         self._test_items = []
         self._set_items()
         self._set_test_items()
@@ -237,7 +244,7 @@ class Scene(DirectObject):
         self._unset_lights()
         self._unset_input()
         self._unset_mouse_plane()
-        [itm.destroy() for itm in self.items]
+        [itm.destroy() for itm in self.__items]
         [itm.remove_node() for itm in self._test_items]
         self._bg.destroy()
         self._side_panel.destroy()
@@ -433,6 +440,8 @@ class Scene(DirectObject):
             for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
                 if not self._item_active:
                     self._item_active = item
+                if item not in self.__items:
+                    method = 'on_click_l'
                 getattr(item, method)(pos)
                 img = 'move' if method == 'on_click_l' else 'rotate'
                 if not (img == 'rotate' and not item._instantiated):
@@ -456,20 +465,20 @@ class Scene(DirectObject):
             #    self.__next_btn['state'] = DISABLED
             #    self.__next_btn['frameColor'] = fcols[1]
         self._item_active = None
-        [item.on_release() for item in self.items]
+        [item.on_release() for item in self.__items]
         self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
 
     def repos(self):
-        for item in self.items:
+        for item in self.__items:
             item.repos_done = False
-        self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
-        [item.on_aspect_ratio_changed() for item in self.items]
-        self._side_panel.update(self.items)
+        self.__items = sorted(self.__items, key=lambda itm: itm.__class__.__name__)
+        [item.on_aspect_ratio_changed() for item in self.__items]
+        self._side_panel.update(self.__items)
         max_x = -float('inf')
-        for item in self.items:
+        for item in self.__items:
             if not item._instantiated:
                 max_x = max(item._np.get_x(), max_x)
-        for item in self.items:
+        for item in self.__items:
             if not item._instantiated:
                 item.repos_x(max_x)
 
@@ -477,10 +486,10 @@ class Scene(DirectObject):
         self.repos()
 
     def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
+        return all(itm.strategy.win_condition() for itm in self.__items) and not self._paused
 
     def _fail_condition(self):
-        return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
+        return all(itm.fail_condition() for itm in self.__items) and not self._paused and self._state == 'playing'
 
     def on_frame(self, task):
         hits = self._get_hits()
@@ -516,14 +525,14 @@ class Scene(DirectObject):
         return task.cont
 
     def cb_inst(self, item):
-        self.items += [item]
+        self.__items += [item]
 
     def on_play(self):
         self._state = 'playing'
         #self.__prev_btn['state'] = DISABLED
         #self.__next_btn['state'] = DISABLED
         self.__right_btn['state'] = DISABLED
-        [itm.play() for itm in self.items]
+        [itm.play() for itm in self.__items]
         self._start_evt_time = globalClock.getFrameTime()
 
     def on_next(self):
@@ -754,7 +763,7 @@ class Scene(DirectObject):
         self.__btn_state = [btn['state'] for btn in btns]
         for btn in btns:
             btn['state'] = DISABLED
-        [itm.store_state() for itm in self.items]
+        [itm.store_state() for itm in self.__items]
 
     def __restore_state(self):
         btns = [
@@ -763,7 +772,7 @@ class Scene(DirectObject):
         ]
         for btn, state in zip(btns, self.__btn_state):
             btn['state'] = state
-        [itm.restore_state() for itm in self.items]
+        [itm.restore_state() for itm in self.__items]
         self._paused = False
 
     def __on_close_instructions(self, frm):
@@ -792,7 +801,7 @@ class Scene(DirectObject):
         self._test_items[-1].set_pos(pos[0], 0, pos[1])
 
     def add_item(self, item):
-        self.items += [item]
+        self.__items += [item]
 
     def _set_editor(self):
         fields = ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
@@ -804,9 +813,9 @@ class Scene(DirectObject):
             self.current_bottom,
             self.repos,
             {})
-        self.__scene_editor = SceneEditor(self.json, self.__json_name, context, self.add_item, self.items)
+        self.__scene_editor = SceneEditor(self.json, self.__json_name, context, self.add_item, self.__items, self._world, self._mouse_plane_node)
 
     def __on_inspector_delete(self, item):
-        self.items.remove(item)
+        self.__items.remove(item)
         self.json['items'].remove(item.json)
         item.destroy()
diff --git a/prj.org b/prj.org
index 3471bba456605ef60b9d38b5f6703e7ad2e62d4d..28a5ccb9a93bc4929eb021fcde104f10a16503bd 100644 (file)
--- a/prj.org
+++ b/prj.org
@@ -36,7 +36,7 @@
   - [X] new item
   - [X] left/right arrow
   - [X] json
-- [ ] editing of test_items in the editor for functional tests
+- [X] editing of test_items in the editor for functional tests
 - [ ] define functional tests
 * BACKLOG tooltips for buttons
 * BACKLOG teeter-tooter with constraints (real teeter tooter)
index 288016f4e8fd9550edfdc14054d592fa37feaf1d..0af2aab9abe4c36b5d6a6575286e68a6d98519db 100644 (file)
@@ -6,7 +6,7 @@ class Assert:
     @staticmethod
     def assert_render3d():
         preserve = ['camera', 'DIRECT']
-        unexpected_nodes = [c for c in render.children if child.name not in preserve]
+        unexpected_nodes = [c for c in render.children if c.name not in preserve]
         for node in unexpected_nodes: Assert.__process_render3d_exception(node)
 
     @staticmethod