やはりわからなかった。

なので再び、filelist.pyとfilelist.kvを簡略化したソースを作り、teratailにて質問し、回答いたしました。

(「kivyで、親のインスタンスの値を、子に渡す方法」https://teratail.com/questions/146585

ちなみにその時のソースがこれ。


from kivy.app import App
from kivy.lang import Builder

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock

from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.resources import resource_add_path

Builder.load_string(''' 
<ResultParts>
    ls_p: ls_v
    Label:
        id: ls_v

<AllSCN>:
    name: 'all'
    us_p: us_v

    BoxLayout:
        orientation: 'vertical'

        BoxLayout: #OpnSCN
            Label:
                id: us_v
        BoxLayout: #ResultSCN
            size_hint_y: 0.9
            ResultParts:
''')

sm = ScreenManager()

class ResultParts(BoxLayout):
    ls_p = ObjectProperty()

    def __init__(self, **kwargs):
        super(ResultParts, self).__init__(**kwargs)


class AllSCN(Screen):
    us_p = ObjectProperty()

    def __init__(self, **kwargs):
        super(AllSCN, self).__init__(**kwargs)

    def begin_display(self, dt):
        self.us_p.text = 'AAA'
        ResultParts.ls_p.text = 'BBB'



class TestApp(App):
    def build(self):
        allscn = AllSCN()

        Clock.schedule_once(allscn.begin_display, 0)

        sm.add_widget(allscn)

        return sm


if __name__ == '__main__':
    TestApp().run()

 

こんな画面を出したかった。

だが、


   File "/home/******/MyApp/01_filelist/kv19f3before.py", line 50, in begin_display
     ResultParts.ls_p.text = 'BBB'
 AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'

と、エラーメッセージだ出てきてしまう問題。

回答いただきました。ありがとうございます。

 

こうして修正した内容がこれ。

kv19f3.py


from kivy.app import App
from kivy.lang import Builder

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock

from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.resources import resource_add_path

Builder.load_string(''' 
<ResultParts>
    ls_p: ls_v
    Label:
        id: ls_v
        text: 'XXX'

<AllSCN>:
    name: 'all'
    us_p: us_v

    BoxLayout:
        orientation: 'vertical'

        BoxLayout: #OpnSCN
            Label:
                id: us_v
        BoxLayout: #ResultSCN
            size_hint_y: 0.9
            ResultParts:
                id: resultparts
''')

sm = ScreenManager()

class ResultParts(BoxLayout):
    ls_p = ObjectProperty()

    def __init__(self, **kwargs):
        super(ResultParts, self).__init__(**kwargs)
        #Clock.schedule_once(self._once_after_init)

    def _once_after_init(self, dt):
        #self.ls_p.text = 'before'
        pass

    def display(self):
        print('ResultParts display:',self.linked)
        #self.ls_p.text = 'koteichi'


class AllSCN(Screen):
    us_p = ObjectProperty()

    def __init__(self, **kwargs):
        super(AllSCN, self).__init__(**kwargs)

    def on_pre_enter(self):
        self.us_p.text = 'AAA'

        rp = self.ids.resultparts
        rp.ls_p.text = 'BBB'
        rp.linked = 'QQQ'
        rp.display()
 

class TestApp(App):
    def build(self):
        #allscn = AllSCN()

        #Clock.schedule_once(allscn.begin_display, 0)

        sm.add_widget(AllSCN())

        return sm


if __name__ == '__main__':
    TestApp().run()

それで出力結果が、

こうなって、正常に動いた。

 

kv言語部分の


<ResultParts>
    ls_p: ls_v
    Label:
        id: ls_v
        text: 'XXX'

<AllSCN>:
    name: 'all'
    us_p: us_v

    BoxLayout:
        orientation: 'vertical'

        BoxLayout: #OpnSCN
            Label:
                id: us_v
        BoxLayout: #ResultSCN
            size_hint_y: 0.9
            ResultParts:
                id: resultparts

<AllSCN>の中でResultParts:全体にidを指定する。

こうすると、ResultPartsのインスタンスを使えるようになるので、AllSCN中で、こんな使い方ができる。


class AllSCN(Screen):
    us_p = ObjectProperty()

    def on_pre_enter(self):
        self.us_p.text = 'AAA'

        rp = self.ids.resultparts

あとは、こうインスタンスを利用すれば、表示ができるようになる。


        rp = self.ids.resultparts
        rp.ls_p.text = 'BBB'
        rp.linked = 'QQQ'
        rp.display()

実際には、rp.ls_p.text = 'BBB'だけで、下段に'BBB'が出るようになる。

 

つまり、だいぶすっきりとしたロジックになる。

 

なお、このメソッドをdef on_pre_enter(self):としたのは、ここではdef begin_display(self, dt):の代替にできるからである。


と、いうわけでfilelist.pyとfilelist.kyに反映してみる。

 

filelist.py


import os
import sys
import json

from kivy.app import App
from kivy.lang import Builder
from kivy.utils import platform

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.treeview import TreeViewLabel
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock

from pprint import pprint

from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.resources import resource_add_path

# 日本語フォント設定
resource_add_path('./fonts')
LabelBase.register(DEFAULT_FONT, 'ipaexg.ttf')

sm = ScreenManager()
path_inf = 'path_inf.json'


class PathRead:

    def __init__(self, **kwargs):
        self.path_json = None
        self.notfound = False
        self.err = False

        try:
            #raise BaseException('やっちまったな')
            with open(path_inf, 'r') as fd:

                self.path_json = json.load(fd)

        except FileNotFoundError:
            self.notfound = True

        except PermissionError as err:
            self.msg = '保存データを読み込もうとしましたが、読み込み許可がありません。\n'
            self.msg += '読みこもうとしたパス・ファイルは、' + path_inf + 'です。\n'
            self.msg += '保存データの読み込み許可を確認するか、パス・ファイルが適当かを確認してください。\n\n'
            self.msg += str(err)

            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='読み込み許可エラー', content=content)
            self.popup.open()

            self.err = True

        except IOError as err:
            self.msg = '保存データを読み込もうとしましたが、読み込めません。\n'
            self.msg += '読み込み先機器に問題があるようです。\n'
            self.msg += '読み込み先機器の状態を確認してください。\n\n'
            self.msg += str(err)

            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='I/Oエラー', content=content)
            self.popup.open()

            self.err = True

        except BaseException as err:
            self.msg = '保存データを読み込もうとしましたが、何らかのエラーが起きました。\n\n'
            self.msg += str(err)

            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='その他のエラー', content=content)
            self.popup.open()

            self.err = True

    def btn_ok(self):
        self.popup.dismiss()


class PathWrite:
    def __init__(self, **kwargs):

        self.err = True

        path_inf_dict = kwargs['path_inf_dict']
        self.pd = {}
        self.pd['begin_path'] = path_inf_dict['begin_path']

        try:
            #raise BaseException('やっちまったな')
            with open(path_inf, 'w') as fd:
                json.dump(self.pd, fd)
                self.err = False

        except PermissionError as err:
            self.msg = '保存データを書き込もうとしましたが、書き込み許可がありません。\n'
            self.msg += 'パスは、' + self.pd['begin_path'] + 'です。\n'
            self.msg += '保存先の書き込み許可を確認するか、パスが適当かを確認してください。\n\n'
            self.msg += str(err)

            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='書き込み許可エラー', content=content)
            self.popup.open()

        except IOError as err:
            self.msg = '保存データを書き込もうとしましたが、書き込めません。\n'
            self.msg += '書き込み先機器に問題があるようです。\n'
            self.msg += '書き込み先機器の状態を確認してください。\n\n'
            self.msg += str(err)

            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='I/Oエラー', content=content)
            self.popup.open()


        except BaseException as err:
            self.msg = '保存データを書き込もうとしましたが、何らかのエラーが起きました。\n\n'
            self.msg += str(err)

            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='その他のエラー', content=content)
            self.popup.open()

    def btn_ok(self):
        self.popup.dismiss()


class MsgboxOk(BoxLayout):
    message_text = StringProperty()
    btn_ok = ObjectProperty(None)


class MsgboxOkWithPost(BoxLayout):
    message_text = StringProperty()
    btn_ok = ObjectProperty(None)
    btn_ok_post = ObjectProperty(None)


class SetSCN(Screen):
    begin_path_dsp = ObjectProperty(None)

    def __init__(self, **kwargs):
        super(SetSCN, self).__init__(**kwargs)

    def setBeginPathCheck(self):
        if os.path.isdir(self.begin_path_dsp.text) == False:

            return False

        return True

    def setSelect(self):
        begin_path_result = self.setBeginPathCheck()

        if begin_path_result == True:
            self.popup = LstPOP(load=self.setSelectLoad, \
                                 cancel=self.setSelectCancel, \
                                 start_path=self.begin_path_dsp.text)
            self.popup.open()

        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.begin_path_dsp.text

            content = MsgboxOk (\
                 btn_ok=self.msgTestOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()

            self.begin_path_dsp.text = sm.begin_path_previous

    def setSelectLoad (self, path, filename):
        self.begin_path_dsp.text = path
        sm.begin_path_previous = path
        self.popup.dismiss()

    def setSelectCancel(self):
        self.popup.dismiss()

    def msgTestOk(self):
        self.popup.dismiss()

    def msgTestCancel(self):
        self.popup.dismiss()

    def selfOk(self):
        begin_path_result = self.setBeginPathCheck()

        if begin_path_result == True:

            self.pd = {}
            self.pd['begin_path'] = self.begin_path_dsp.text

            pw = PathWrite( \
                path_inf_dict=self.pd, \
                btn_ok_post=self.pathwhite_ng)
            if pw.err == False:
                self.manager.get_screen('all').file_name.text = self.begin_path_dsp.text
                sm.current = 'all'

        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.begin_path_dsp.text

            content = MsgboxOk (\
                 btn_ok=self.msgTestOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()

            self.begin_path_dsp.text = sm.begin_path_previous

    def selfCancel(self):
        sm.current = 'all'

    def pathwhite_ng(self):
        #
        # PathWhite()で異常だった時の処理
        #
        sys.exit()


class LstPOPDriveBtn(BoxLayout):
    filechooser = ObjectProperty(None)

    def on_select_button(self, button):
        sm.current_screen.popup.start_path = button.text
        sm.current_screen.popup.title = '読み込み中 ' +  button.text


class LstPOP(Popup):
    #current_dir = os.path.dirname(os.path.abspath(__file__))

    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    start_path = ObjectProperty(None)

    def __init__(self, **kwargs):
        super(LstPOP, self).__init__(**kwargs)
        self.title ='読み込み中 ' + self.start_path

        if platform == 'win':
            import win32api

            dv = win32api.GetLogicalDriveStrings()

            self.drive_list.data = []
            btn_list = dv.split('\000')[:-1]   #ドライブ名をリストに、最後につく余分な空白は削除
            for btn_list_idv in btn_list:
                self.drive_list.data.append({'value': btn_list_idv})

        else:
            self.drive_list.width = 0
            self.drive_list.size_hint_x = 0

    def is_dir(self, dirname, filename):
        # ディスレクトリならTrueを返す
        return os.path.isdir(os.path.join(dirname, filename))

    def lstChooser(self, path, selection):
        if 0 < len(selection):
            self.title = '読み込み中 ' + selection[0]


class ResultParts(BoxLayout):
    start_path = StringProperty(None)
    tv_p = ObjectProperty(None)

    def __init__(self, **kwargs):
        super(ResultParts, self).__init__(**kwargs)
        #Clock.schedule_once(self._after_kv_applied)

    def tree_display(self):
        self.init_path = ''
        self.tv_p.bind(minimum_height=self.tv_p.setter('height'))
        self.tree_height = self.idv_dir(self.start_path, None)

    def idv_dir(self, this_dir, parent):
        try:
            #raise BaseException('やっちまったな')

            print('this_dir:', this_dir)
            ls = os.listdir(path=this_dir)

            # 指定ディスレクトリ内のファイル・ディレクトリを一度フルパスにしてから、ディレクトリであればリストに入れる
            ls_dir = [ls_idv for ls_idv in ls if os.path.isdir(os.path.join(this_dir, ls_idv))]
            ls_dir.sort()

            # 指定ディスレクトリ内のファイル・ディレクトリを一度フルパスにしてから、ファイルであればリストに入れる
            ls_file = [ls_idv for ls_idv in ls if os.path.isfile(os.path.join(this_dir, ls_idv))]
            ls_file.sort()

            for ls_dir_idv in ls_dir:
                tree_node = self.tv_p.add_node(TreeViewLabel(text=ls_dir_idv, is_open=False), parent)
                self.idv_dir(os.path.join(this_dir, ls_dir_idv), tree_node)

            if ls_file:
                for ls_file_idv in ls_file:
                    self.tv_p.add_node(TreeViewLabel(text=ls_file_idv, is_open=False), parent)
            else:
                # ディレクトリの中にファイルが1つも入っていない時の処理
                self.tv_p.add_node(TreeViewLabel(text='(empty)', is_open=False), parent)

        except PermissionError as err:
            pass

        except IOError as err:
            self.msg = 'ファイルデータを読み込もうとしましたが、読み込めません。\n'
            self.msg += '読み込み先機器に問題があるようです。\n'
            self.msg += '読み込み先機器の状態を確認してください。\n\n'
            self.msg += str(err)

            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=sm.result_parts_ng, \
                message_text=self.msg)
            self.popup = Popup(title='読み込み許可エラー', content=content)
            self.popup.open()

        except BaseException as err:
            self.msg = 'ファイルデータを読み込もうとしましたが、何らかのエラーが起きました。\n\n'
            self.msg += str(err)

            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=sm.result_parts_ng, \
                message_text=self.msg)
            self.popup = Popup(title='I/Oエラー', content=content)
            self.popup.open()

    def fileread_ng(self):
        #
        # ファイル読み込み処理で異常だった時の処理
        #
        sys.exit()

class AllSCN(Screen):
    tv = ObjectProperty(None)
    file_name = ObjectProperty(None)
    loadfile = ObjectProperty(None)
    text_input = ObjectProperty(None)

    def msgOsErrorOk(self):
        self.popup.dismiss()
        sys.exit()

    def msgFileNameInputErrorOk(self):
        self.popup.dismiss()

    def begin_display(self, dt):
        #
        # SetSCNの最初期内容
        #
        self.init_path = ''

        thisos = os.name
        if thisos == 'posix':
            self.init_path = os.environ.get('HOME')
        elif thisos == 'nt':
            self.init_path = os.environ.get('HOMEDRIVE') + os.environ.get('HOMEPATH')
        else:
            msg = '内部OS判定(os.name)が、"' + thisos + '”でした。\n'
            msg += 'サポートしているOSは、UNIX系統と、Windowsです。\n'
            msg += 'したがってこのOSはサポート外です。すみません。'

            content = MsgboxOk (\
                btn_ok=self.msgOsErrorOk, \
                message_text=msg)
            self.popup = Popup(title='OSサポートエラー', content=content)
            self.popup.open()
            return

        #
        # AllSCN用の初期設定 ファイルパス読み込み
        #
        pr = PathRead(btn_ok_post=self.fileread_ng)

        if pr.path_json != None:
            #
            # PathRead()で正常だった時の処理
            #
            path_inf_begin = pr.path_json['begin_path']
            self.manager.get_screen('set').begin_path_dsp.text = path_inf_begin
            self.file_name.text = path_inf_begin
            sm.file_name_previous = path_inf_begin
            sm.begin_path_previous = path_inf_begin

        else:
            if pr.notfound == True:
                #
                # PathRead()で見つからなからった時の処理
                #
                self.manager.get_screen('set').begin_path_dsp.text = self.init_path
                self.file_name.text = self.init_path
                sm.file_name_previous = self.init_path
                sm.begin_path_previous = self.init_path

        if pr.err == False:
            #
            # AllSCNのtree処理
            #
            rp = self.ids.resultparts   # kv言語上で書いたResultPartsを取得
            rp.start_path = self.file_name.text
            rp.tree_display()

    def setupButtonClicked(self):
        #
        # SetSCNへの画面遷移時の内容
        #
        sm.current = 'set'

    def allSelect(self):
        self.popup = LstPOP(load=self.allSelectLoad, \
                             cancel=self.allSelectCancel, \
                             start_path=self.file_name.text)
        self.popup.open()

    def allSelectLoad(self, path, filename):
        self.file_name.text = path
        sm.file_name_previous = path
        self.popup.dismiss()

    def allSelectCancel(self):
        self.popup.dismiss()

    def allFileNameCheck(self):
        if os.path.isdir(self.file_name.text) == False:

            return False

        return True

    def allExec(self):
        file_name_result = self.allFileNameCheck()

        if file_name_result == True:
            sm.file_name_previous = self.file_name.text

        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.file_name.text

            content = MsgboxOk (\
                 btn_ok=self.msgFileNameInputErrorOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()

            self.file_name.text = sm.file_name_previous

    def msgTestOk(self):
        self.popup.dismiss()

    def msgTestCancel(self):
        self.popup.dismiss()

    def fileread_ng(self):
        #
        # ファイル読み込み処理で異常だった時の処理
        #
        sys.exit()


class FilelistApp(App):
    def build(self):
        allscn = AllSCN()
        setscn = SetSCN()

        Clock.schedule_once(allscn.begin_display, 0)

        sm.add_widget(allscn)
        sm.add_widget(setscn)

        return sm


if __name__ == '__main__':
    FilelistApp().run()

 

filelist.ky


<SetSCN>:
    name: 'set'
    begin_path_dsp: begin_path_dsp

    BoxLayout:
        orientation: 'vertical'

        BoxLayout:
            size_hint_y: 0.1
            orientation: 'horizontal'

            BoxLayout:
                Label:
                    size_hint_x: 0.1
                    text: '開始パス'
                TextInput:
                    id: begin_path_dsp
                    size_hint_x: 0.8
                Button:
                    size_hint_x: 0.1
                    text: '…'
                    on_release: root.setSelect()

        BoxLayout:
            size_hint_y: 0.8

        BoxLayout:
            size_hint_y: 0.1
            Button:
                size_hint_x: 0.5
                text: 'OK'
                on_release: root.selfOk()
            Button:
                size_hint_x: 0.5
                text: 'キャンセル'
                on_release: root.selfCancel()

<ResultParts>
    tv_p: tv_v
    ScrollView:
        #size: self.size
        do_scroll_x: False
        TreeView:
            id: tv_v
            root_options: {'text':'ホーム', 'font_size': 32}
            hide_root: False
            size_hint_y: None

<AllSCN>:
    name: 'all'
    file_name: file_name

    BoxLayout:
        orientation: 'vertical'

        BoxLayout: #OpnSCN
            size_hint_y: 0.1
            orientation: 'horizontal'

            BoxLayout:
                Button:
                    size_hint_x: 0.1
                    text: '設定'
                    on_release: root.setupButtonClicked()
                TextInput:
                    id: file_name
                    size_hint_x: 0.7
                    text: 'ファイルパス'
                Button:
                    size_hint_x: 0.1
                    text: '…'
                    on_release: root.allSelect()
                Button:
                    size_hint_x: 0.1
                    text: '実行'
                    on_release: root.allExec()

        BoxLayout: #ResultSCN
            size_hint_y: 0.9
            ResultParts:
                id: resultparts


<MsgboxOk>
    orientation: 'vertical'

    Label:
        size_hint_y: 0.9
        text: root.message_text

    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'

        Button:
            size_hint_x: 10
            text: '了解'
            on_release:
                root.btn_ok()


<MsgboxOkWithPost>
    orientation: 'vertical'

    Label:
        size_hint_y: 0.9
        text: root.message_text

    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'

        Button:
            size_hint_x: 10
            text: 'OK'
            on_release:
                root.btn_ok()
                root.btn_ok_post()


<MsgboxOkCancel>
    orientation: 'vertical'

    Label:
        size_hint_y: 0.9
        text: root.message_text

    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'

        Button:
            size_hint_x: 10
            text: 'OK'
            on_release: root.ok_button()

        Button:
            size_hint_x: 10
            text: 'キャンセル'
            on_release: root.cancel_button()


<LstPOPDriveBtn>:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            size: self.size
            pos: self.pos
    value: ''
    Button:
        text: root.value
        background_normal: ''
        background_color: 0.5, 0.5, 0.75, 1
        color: 1, 1 ,1 ,1
        on_press: root.on_select_button(self)


<LstPOP>:
    id:lst_pop
    size_hint: 0.9, 0.9
    drive_list: drive_list
    auto_dismiss: False
        
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: 'vertical'
        BoxLayout:
            orientation: 'horizontal'

            RecycleView:
                id: drive_list
                size_hint_x: 0.3
                scroll_type: ['bars', 'content']
                scroll_wheel_distance: sp(30) #スクロール速度
                bar_width: sp(10)
                viewclass: 'LstPOPDriveBtn'
                RecycleBoxLayout:
                    default_size: None, sp(40)
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    spacing: sp(2)

            FileChooserListView:
                id: filechooser
                size_hint_x: 0.7
                dirselect: True
                path: root.start_path
                filters: [root.is_dir]
                on_touch_down: lst_pop.lstChooser(filechooser.path, filechooser.selection)

        BoxLayout:
            size_hint_y : None
            height : 30
            Button:
                text: 'キャンセル'
                on_release: root.cancel()

            Button:
                text: '読み込み'
                on_release: root.load(filechooser.path, filechooser.selection)

AllSCN:

だいたい、kv19f3.pyなんですが、AllSCN(Screen):begin_display(self, dt):def on_pre_enter(self):などで代替というのは、うまくいかなかったので今回は従来のままとしました。

 

そして起動すると、

おお、うまくいった!

 

実はこれ、少しテストするとボロが出ちゃうのですが、ここまでできたことで完成への道筋ができたと思う。

あともう少しだ。