2021年2月8日月曜日

Python+Kivy 備忘録 kivy の描画関数は描画関数じゃない?編

 実はまだ作っています MO スケルトン。予想通り緊急事態宣言が延長されましたが、別に休みが増えている訳でも無く、タイムリミットが迫っています。
当初サーバー側を作ろうかと思っていましたが、もう少しクライアント側を作らないと、サーバーが失敗しているか?の判断も付かない為、クライアント側を重視で制作中。
…その間に見つかったちょっと残念な kivy の仕様を書いてみます。(割と今更な内容らしいですけど、日本語でコードを載せている方が見当たらなかったので)

さて、kivy は強力なUIを持つライブラリですし、UIとゲーム画面の混在するアプリを作りたいものです。
こんな画面を作る場合、
ゲーム画面(描画領域)は空の BoxLayout を置いてココに色々描きこむ事になります。
…まあ、それは良いのですが、 kivy のゲーム系サンプルコードを読むと描画の開始時に
self.canvas.clear()
を行いなさいというコードを良くみかけるのですが…。
21年2月現在でUI混ざりのアプリで self.canvas.clear() を行うと全てのUIが消えてしまいます。
しかも、表示されないボタンの当たり判定だけ残っていてクリックできるという状態になります。
…多分、この怪し過ぎる挙動は改善されると思うのですが。

で、次善策として BoxLayout の描画領域だけ Rectangle で塗りつぶして描画するコードを書いてみたのがこちら

コード

from kivy.app import App
from kivy.uix.widget import Widget

from kivy.graphics import Color, Ellipse, Line, Rectangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock

class BtRev(Button):
    def on_press(self):
        self.parent.Rev()

class BoxMain(BoxLayout):
    orientation='vertical'
    def __init__(self,**kwargs):
        super(BoxMain,self).__init__(**kwargs)
        self.speed = 0.1
        self.ball_x = 0.0
        self.boxDraw = BoxLayout(size_hint_y=4)
        self.add_widget(self.boxDraw)
        self.b1 = BtRev(text='Rev',size_hint_y=1)
        self.add_widget(self.b1)

    def  Rev(self):
        self.speed = self.speed * -1.0

    def update(self,dt):
        with self.canvas.before:
            w = self.boxDraw.size[0]
            h = self.boxDraw.size[1]

            x = self.ball_x * w - w/20.0
            y = self.boxDraw.top-h/2.0-h/20.0

            self.ball_x = self.ball_x + self.speed
            if self.ball_x > 1.0:
                self.ball_x = 0.0
            elif self.ball_x < 0.0:
                self.ball_x = 1.0

            Color(0,0,0,1)
            Rectangle(pos=(self.boxDraw.right-w,self.boxDraw.top-h),size=(w,h))

            Color(1,1,1,1)
            Ellipse(pos=(x,y),size=(w/10.0,h/10.0))

class BoundBall_miss(App):
    def build(self):
        bxmain = BoxMain()
        Clock.schedule_interval(bxmain.update, 1.0/12.0)
        return bxmain

if __name__ == "__main__":
    BoundBall_miss().run()


…あらかじめ書いておきますが、コレは失敗コードです。
実行すると始めのうちは良いのですが、だんだん処理が重くなっていきます。
原因を探る為「kivy Ellipse 遅くなる」等で検索すると…出るわ出るわ、同様の事例が山ほど出ます。

結論から書いてしまえば、kivy の Rectangle や Ellipse 関数はただの描画関数ではありません。
試しに返り値を print で出力してみると一目瞭然。
オブジェクトを作ってらっしゃる!?
つまりコレは四角や丸を描画するオブジェクトを生成する関数であり、生成されたオブジェクトはフレーム毎にそれぞれが描画を行っています。
こんな事をしていればどんどん処理が重くなっていくのは当然です。
ちなみに、何処に積み重なっているのかと言えば、描画先の self.canvas や self.canvas.before 等であり、割り当てられた描画オブジェクトを全開放する関数が前述の self.canvas.clear() 等のようです。
私の古い常識からすれば Ellipse 関数は CreateEllipseObject とでも名乗るべきで、返り値でハンドラを返してユーザーに開放させるべきですが(というかこの関数自体、オブジェクトの実体参照先を返してくる)今の時代の感性と合わないのかもしれません。
又、python はメモリ管理をガーベジコレクションで行っており、コードを書く人間はメモリ開放を明記しない事が多いのでこういう書き方になったのかもしれないのですが…逆に python しか言語を知らない人は大ハマりするのでは?

で、改善版コードはこちら

from kivy.app import App
from kivy.uix.widget import Widget

from kivy.graphics import Color, Ellipse, Line, Rectangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock

class BtRev(Button):
    def on_press(self):
        self.parent.Rev()

class BoxMain(BoxLayout):
    orientation='vertical'
    def __init__(self,**kwargs):
        super(BoxMain,self).__init__(**kwargs)
        self.speed = 0.1
        self.ball_x = 0.0
        self.list_el = []

        self.boxDraw = BoxLayout(size_hint_y=4)
        self.add_widget(self.boxDraw)

        self.b1 = BtRev(text='Rev',size_hint_y=1)
        self.add_widget(self.b1)

    def  Rev(self):
        self.speed = self.speed * -1.0

    def update(self,dt):
        with self.canvas.before:
            w = self.boxDraw.size[0]
            h = self.boxDraw.size[1]

            x = self.ball_x * w - w/20.0
            y = self.boxDraw.top-h/2.0-h/20.0

            self.ball_x = self.ball_x + self.speed
            if self.ball_x > 1.0:
                self.ball_x = 0.0
            elif self.ball_x < 0.0:
                self.ball_x = 1.0
            # remove old ball
            for old in self.list_el:
                self.canvas.before.children.remove(old)
                self.list_el.remove(old)

            Color(1,1,1,1)
            self.list_el.append(Ellipse(pos=(x,y),size=(w/10.0,h/10.0)))

class BoundBall(App):
    def build(self):
        bxmain = BoxMain()
        Clock.schedule_interval(bxmain.update, 1.0/12.0)

        return bxmain

if __name__ == "__main__":
    BoundBall().run()


※21年2/12 更新。以前は self.canvas.before.remove としていましたが、self.canvas.before.children.remove に変更しました。
自分で生成した Ellipse オブジェクトをリスト化しておいて、描画の開始時に古いオブジェクトを開放しています。
うーん kivy ライブラリってなんというか、妙な所で設計が雑な気がするのは気のせいでしょうか?

2021年1月26日火曜日

Blender 2.82 用アドオンセットができてしまった

 昨年の12月26日に到着していながら未だに使用していない VivoBookFlip14 。
主たる要因はこれまで日常的に使っていた自作の Blender2.7 用プラグインの 2.8 以降への移植ができていない為でした。
で、長い間悩んだのですが、パネルにボタンを追加して機能を追加する…という記事を見つけまして。アレ、1個パネルを作って、そこに今まで作った機能を全部ボタンとして追加すれば良くね?
と、思い立って2日…完成してしまった。

数か月悩んだものが数日で解決する事もあるのが開発の怖い所。ただ、「すべからく努力~」と言わないまでも、悩み続けないと辿り着けないんですよねえ。
そして、コレまで Blender 用に作ったスクリプトの内、主要な6個の機能をまとめたのですが…総ソースコード300行以下。
print 文の削除等整理をしたのも有るけど、圧縮の必要も無いコンパクトサイズになりました。

公開に当たって一応面倒だけど注意を書かねば。
1.使用してどんな不利益が生じても責任は負いません。
2.改造、再配布はしても良いけどこのソースコードで直接商売をする事は禁止します。(当アドオンを使ってモデリングデータ等の商品を作る事は構わないけど、出版物に直接コードを載せる等は禁止という事です。居ないとは思いますが)
3.バグ等を見つけたら下にコメントを貰えると助かります。

では、コレで3回目の説明になる機能もありますが、各機能の説明おば。

まず、インストールの仕方は省略させて頂くとして、インストール後は3DViewに通常ショートカットNキーで呼び出せるサイドバーの「100H_Addon」タブとして実装されます。
パネルの下に各機能がボタンとして実装されるのですが、現在は「オブジェクトモード」と「アーマチュア編集モード」で表示される内容が異なります。

  • Set Bone Roll Zero
アーマチュア編集モード時に表示されるボタンです。
選択したボーンのみロール値を0にします。※現状ではオブジェクトモード時にアーマチュアが選択されている必要があります
これが
こうなる。
ロール値が0じゃないと不都合がある場合に使います。通常は全選択して全て0という使い方が主になります。

  • Sort VG by the Name & Sort VG by Hierarchy
それぞれ、オブジェクトモード時に表示されるボタンです。
頂点グループ(以後VG)を設定してあるオブジェクトを選択後に(220306更新時に意味が無かったのでこの制限を削除しました)ボタンを押すと、
それぞれ名前順/ボーン継承順位順にVGの並べ直しを行います。
これが…
名前順の場合

継承順の場合

尚、継承順については私も深く理解できておらず、ボーンに関連付けされていないVGの順位等がどう並ぶのか?等不透明な部分があります。

  • Pruning Idle VG
オブジェクトモード時に表示されるボタンです。
VG設定済みのメッシュオブジェクトを選択してこのボタンを押すと、全頂点を検索し、未使用VGを削除します。
今回は Dummy~ と命名したVGは頂点で使用していないので…
これが

こうなる


以降は順を追って説明します。
そもそも、私のモデリング/リギング方法の都合で、左半身のみ編集/リギングを行う→左右リギング済みのモデルを作る→編集用に左半身のみの状態に戻す。という作業の自動化の為にアドオンを作成していました。
標準機能で上手く行かなかった部分をアドオンで補っていたので、順を追って説明します。

  • Append LR to Bonenames
アーマチュア編集モード時に表示されるボタンです。
選択したボーンのみ名前の最後に左右を示す.L .R を付加します。※現状ではオブジェクトモード時にアーマチュアが選択されている必要があります
標準機能であるアーマチュアコンテクストメニュー(右クリック)→名前→自動ネーム(左右)でも挙動は同じなのですが、Xの中心位置の取り方がシビアだった為作りました。
本アドオンでは+-0.0001の範囲内のボーンはリネームしません。又、範囲内に入ったボーンは改めてXの座標を0にセットし直します。
又、あらかじめリネームする前に .L .R が語尾についていた場合一旦削除してからリネームを行う為、連続して使用しても~.L.L といった名前にはなりませんが、故意にそういった名前を作りたい場合や、最初から~.L と~.R が混在した場合は挙動があやしくなる事をご了承ください。

  • Pair VG Maker
オブジェクトモード時に表示されるボタンです。
~.L ~.R の左右を追加した名前のVG設定済みのメッシュオブジェクトを選択してこのボタンを押すと、対になるVGを追加します。
これが

こうなる

その後は標準機能で左右リギング済みオブジェクトを作る
まず、編集モードでアーマチュアを選択し、アーマチュアコンテクストメニュー(右クリック)→対称化
次にオブジェクトモードでリギング済みメッシュオブジェクトを選択し→モディファイアーを追加→ミラー
そしてミラーをX軸にチェックを入れて(デフォルトでは入っている)「適用」

これで、リギング済み左右モデルができるハズ。

  • Halving Object
オブジェクトモード時に表示されるボタンです。
左右対称にリギングしたメッシュオブジェクトに影響を与えるアーマチュアを選択し、このボタンを押すと、アーマチュア/影響を受けたメッシュオブジェクトが半分の状態に戻ります。
又、ボーン名称、VG名称も左右を示す接尾語が抜けます。

これが

こうなる


ふう、コーディングよりはるかにココのブログページの更新の方が時間がかかりました。
今回初めてグーグルドライブを使ったファイル共有を使ってみましたが…、モロに本名が出てますね…グーグルのユーザー登録はお金が絡む事案があるので偽名を使わなかったのですが、メールアドレスで登録している意味がないというか、グーグルにユーザーを守る気が無さ過ぎるというか…せめてニックネームのみ表示が無いのかよ…

…ちなみに、今回のプラグイン、ほとんど枕元で作りました。寝る前に思い立ってパパっと…そう、アノ中華PCで。
存外、幸運を呼ぶPCなのかもしれないですね。

※以下、22年3月6日追加分
  • Sort Bone group
ポーズモード時に表示されるボタンです。


これが

こうなる
…見たまんま名前順に並べ直します。アクティブなボーングループがインデックスを注視したままなので変わってしまう事が有る点に注意。※22年6月6日修正しました

  • Copy Pose Mode hide flag to Edit Mode
アーマチュア編集モード時に表示されるボタンです。


エディットモードがこの状態

ポーズモードのボーン隠蔽がこの状態で

こうなる
オブジェクトモードで選択されているアーマチュアのポーズモードでのボーン隠蔽状態を編集モードへコピーします。

※以下、22年3月24日追加分
※26年8月29日更新。細かいバグフィックスと、ボタンの日本語化が主な更新点です
※22年6月6日同機能のバグを修正してアップデートしました
  • Show the active gloup bones
ポーズモード時に表示されるボタンです。
オブジェクトモードでボーンオブジェクトを選択した上で、ポーズモードにして
こういったボーンを隠した状態で…

ボーングループをアクティブにしてボタンを押すと

こうなる(例だと僧帽筋のボーングループだったのでこうなりました)。
ちなみに、アクティブなグループのボーンを表示するだけで他のグループには影響が無い為、表示されていたボーンが隠される…といった事はありません。

2021年1月20日水曜日

Vivo Book Flip14 TM420IA を開けてみた。

いや、流石に9万円以上もする新品を開けるのは怖いですね。保障から外れちゃうし。壊れたら泣くに泣けないし。とは言え、開けなくてはいけないのです。ジャンクバカの魂的な話…も3割くらいは有るんですけどね。
Vivo Book Flip14 には要らない物が一つある。そう、Windows10 です。
なら Linux で上書きすれば良いだろって話なのですが、後々必要になっても困るので SSD を載せ替える事にしました。ちょうど、以前組んだゲーム用PCで認識できなかった NVMe SSD があったので、こいつに換装して Linux Mint をインストールします…

  • まずは背面。
備忘録ですから、コレは覚えておかなくては…
水色で示した部分が短いネジ、黄色で示した部分が長いネジだったハズ。全部が一緒ではなかったんですよね。
ちなみに底板はプラスチックというかポリカーボネートのようです。※公式でアルミ製とされているようです。

  • 開けてみる。
まず目につくのはでっかい冷却ファン。
Ryzen を冷やすならこれくらいデカいファンは必須です。若干ヒートパイプが長すぎる気がしないでもないですが。
またこの排気ファンは左側面(向かっては右側)に排熱する為、左側面のインターフェースがUSB2.0の1個のみになってしまいました。
逆に右側には3.0等もついているのですが…前回書いた怪しい激安中華ノートのように左に3.0、右に2.0の方が正解です。
右に繋げたいのは低速でも構わないマウスやゲームパットであり、左手側には高速アクセスが必要な記憶デバイス系を繋ぎたいですからね。
又、右にHDMIや電源端子もありますが、マウスの邪魔になるのでコレも左が正解。
という訳で、ファンの配置の為に相当操作性を犠牲にしてしまった感じです。ここらの設計は見直して欲しい所。…というか、他のメーカーは当然と言うか後方に排熱してますよね。

  • バッテリーは中国製
いや、ほとんどのパーツは中国製なのですが、中国語、英語、日本語、ハングルが並びますが、とどのつまり中国製です。
まあ、いつも書きますが爆発しなければ良いデス。

  • SSDはキングストン
上の全体像でバッテリーの上にあった銀色の四角い一角。これは基盤についているクリップに留められている放熱シールドでして、これを外すと…
中から目当てのSSDと更にシールドされたメモリが出てきます。

あと、ネジや端子がちゃんと組み付けてあるかを赤マジックで人の手でチェックしているのは偉いと思うのですが、端子にインクが載ってしまっているのは大丈夫なんでしょうか…

  • メモリはサムスン製
さて、メモリのシールドをとっ外すとなんとDIMMスロットにメモリが刺さっていました。
刺さっていたのは4GBのサムスン製メモリ。裏面にさらにスロットがあるのか、ボード直付けかは解らなかった(これ以上分解するのは流石に怖い)のですが、コレを8Gや16Gに交換する事はできそうです。ただ、そうしたとしてもデュアルチャンネルで動作するのは8GBまでで、余剰分はシングルチャンネル動作をする為、設定できるのならばシングルチャンネル分はメモリディスク等に割り当てたいですね。

さて、現在はSSDの組み換えも済み、画像は載せていませんが LinuxMint20.1 のインストールも完了しました。起動させると8コア8スレッド中なんのアプリも起動しなければ4スレッドが休眠に入るほどパワーに余裕があります。

又、取り外す時にうっかり元のSSDに傷を入れてしまいました…多分動くと思うけど…まあ今後 Windows を使う事はあるまい。

未だに Blender のアドオン移植が上手く行っていない関係でほとんど使用できていないのですよね。本当は正月休みに使う為に値下がりを年末ぎりぎりまで待って購入したのに。早くモデリングに使いたいものです。

2021年1月13日水曜日

Shenzhen Grezoo Electronic 製ノートパソコンを分解してみた

 以前ハードオフで購入した怪しげな中華PC。結局 Linux Mint20.1 Mate の正式版をインストールしたのですが、魔が差して分解。

…衝撃的な内容でしたよ
はい。
この下部の左右、ちょうどキーを叩いていると手首が乗るあたりのさび付いたこのパーツ…
重りなんです。
たしかにね、画面開くとモニターとの重量のバランスが悪くてひっくり返っちゃうしね。
…ってヲイ。
モバイルPCに重りを載せるという逆転の発想。思考力が柔軟過ぎてビックリですよ。
ちなみに重りの更に下にある丸い銀のパーツは、強力マグネット。通りでロックも無いのにしっかり閉まるわけですよ。…冷蔵庫のドアかな?
…セロハンテープでくっつけてあるのが手作り感出てますよね。

で、底版に貼っているのが良心を感じさせる放熱板。
指紋が付いていますが、私ではありません。
両面テープか何かで直接貼り付けたようです。

…なにが凄いって、こんな原始的な方法ながら当面の問題は解決出来ている所なんですよね。最もチープな手段を迷わず選択できる強さみたいな物を感じましたよ。

2021年1月10日日曜日

甲乙甲乙にて終了


7日に甲第3ゲージを6割ほど削ったのですがどう見ても鋼材が足り無さそうだったので乙へ。
ゲージは9割ほどに回復してしまったものの第3ゲージはそのままだったのでその日のうちに乙のラスダンへ。
翌日にはギミックも解除し涙目状態にしたのですが…
この状態からクリアできるイベントが有るんですか!
ねぇよ

その後も細々デイリークリアしつつ出撃してみるものの、1回ボス戦をすれば鋼材2000は飛んでしまう為どうしましょうかと。
結局、5000円ほど課金し鋼材を1万近く積み増して出撃…の1回目。
うーん。
ちなみにコレ、夜戦開始時。
この後、我が第二艦隊は敵艦隊に対して手も足も出ずA勝利にてイベントが終了しました。

…いや、頭を使う最終海域ではありましたが…楽しくは無かったですね。

まず、この艦隊。
乙にした事で投入可能になったアークロイヤルを含め、由良、加賀、足柄を入れています。
この状態で強援軍を要請するとやってくるのは迅鯨率いる潜水艦隊一択。
ちなみに、日進、由良、加賀、アークは水戦/艦戦/彩雲しか積んでおらず戦力外。その代わり、全艦の拡張にボフォーズもしくは三連装機銃集中MAXも積んでボス戦で航空確保をとっています。
米空母だけカットイン搭載をしているのですが…まあ、正直姫級相手にまともなダメージを出したことは無かったですね。連合艦隊戦の最初は第一艦隊vs第一艦隊かつ相手の第一艦隊全員が姫級なんですけどね。

曲者は日進で、高速水母かつ4スロットなので採用しましたが、射程が中の為、米空母組は彗星二二型で射程を長にしないと日進の攻撃が先行→米空母の攻撃番が回ってくる前に中破→棒立ち、が多発します。

第二艦隊の仕事は敵の第二艦隊を蹴散らす事。本来はその後の夜戦でボスをCIで倒す事なんですが前述の通り…

航空部隊は…Wikiによればボスに集中して、練度が下がると命中率が著しく下がる為に出撃後はつけ直せとありますが…
航空隊の練度上げは実は簡単で、E4なら基地航空隊をFマスに出撃、潜水艦単艦(まるゆが一番軽い)の通常艦隊でDからEマスに進みFマス終了後に帰投し続ければ良いだけです。
…が、赤疲労になると潜水艦はともかく基地航空隊は30分程「休息」にしておかないと疲労が抜けない為、ボス戦→練度のつけ直しで1時間程時間がかかります。
道中撤退率は低いとは言え、1時間に1回のボス戦の為に1隻の潜水艦を使ってひたすら練度上げ作業を行うという苦行…時間の無駄感が半端ないです。

という訳で、1部隊は基地防空、それ以外はX2とY3に1隊ずつを集中し、無傷の艦隊をボスマスへ送る事にしました。ボス支援は砲撃のみですが、稀に2隻くらい駆逐艦を減らしてくれます。
あとは前述の通りで、ひたすら友軍支援艦隊のCI攻撃がボスに刺さる事を祈るのみ…2回刺さったら倒せました。

…うん、ちなみにウチの時雨さん約20出撃で1回もボスに攻撃を放った事がありません。
というか、夜戦で攻撃出来た事があまりにも無かったので6番から3番に上げていましたが最後も攻撃できませんでしたね。
…アニメ版の次期主人公らしいですよ

というわけで、運営に用意して頂いた迅鯨潜水艦隊という主人公をひたすらお膳立てするという、ある意味いつものイベントでした。

感想は以前書いたのですが、運営的には1年の内5ヶ月はイベントをやらせたいという考えなんでしょうかね?20年を振り返ると無駄に長いイベントばかりでしたし、そんな気がします。
あと、今回重爆を配り損ねている気がするのですが、今後この装備の重要度はどのタイミングで増すのでしょうか?次のイベントから…なんて事があったら暴動が起きそうなんですが。

とにかく、疲れるイベントでした。



2021年1月9日土曜日

Python+Kivy 備忘録 Canvas 座標系とマウスクリック編

 先日、今年の抱負で「緊急事態宣言中にMOのスケルトンを作るぞー」とは書いたのですが…
思ったよりも早く宣言しちゃいましたね。
そんなわけで、急ピッチで基礎固めをしないとならないのですよ…というわけで。

本プログラムは、クリックを「押した」場所に赤い楕円、「放した」場所に青い楕円を描画するプログラムです。
コード

from kivy.app import App
from kivy.uix.widget import Widget

from kivy.graphics import Color, Ellipse, Line, Rectangle
from kivy.core.window import Window

class ClickInput(Widget):
    def draw_ellipse(self,x,y,r,g,b):
        with self.canvas:
            Color(r,g,b)
            w=self.size[0]
            h=self.size[1]
            Ellipse(pos=(x-w/20,y-h/20),size=(w/10,h/10))

    def on_touch_down(self,touch):
        self.draw_ellipse(touch.pos[0],touch.pos[1],1,0,0)
        print("D:",touch)

    def on_touch_up(self,touch):
        self.draw_ellipse(touch.pos[0],touch.pos[1],0,0,1)
        print("U:",touch)

class InitKivy(App):
    def build(self):
        return ClickInput()

if __name__ == "__main__":
    InitKivy().run()


まず kivy のクライアント領域…というか描画領域、windows で言う所の DC (デバイスコンテキスト:因みにプリンターとかも持っているのでモニターとは限らない)みたいな物は Canvas と言うらしいですね。
この座標系ですが、左下が基点で(0,0)になるそうです。
on_touch_down と on_touch_up はマウスクリックの押下、開放イベントのオーバーライド関数…マウスクリックの押下、開放時についでで呼んでもらう関数です。
で、ここで print している内容ですが
spos は相対座標で、左下を(0.0、0.0)右上を(1.0,1.0)とした実数型。
pos は実際のドット数を示す環境依存でやっぱり左下が0で右上が幅と高さのドット数となります(稀に小数点以下に9999…と続く事がありますが)。異なる環境間でやりとりをするなら相対座標、クライアントに描画するなら実座標を使用する事になるでしょう。

draw_ellipse 関数は楕円を描く処理をひとまとめにしたものです。
画面全体の幅と高さに対して10分の1のサイズで描画しているのですが…。
この Ellipse 関数は楕円であるにも関わらず、基本座標が左下なので、クリックした場所を中心にしたい場合は座標に対して、半径分を引いてやらないといけません。恐らく他の描画命令と歩調を合わせた結果でしょうけど、少し面倒ですね。
尚、機種依存の Canvas の幅と高さのドット数は self.size[0] self.size[1] から取得しています。

描画する楕円の大きさは Canvas の幅と高さ依存なので、ウィンドウの大きさを変えながらクリックする事で、いろんな大きさの円を描きます。
というわけで、クライアントの方の描画処理の練習をしてみました。
次はサーバー側かなあ。あと Python を使ったルート計算や時間の算出など細かい所も解らない事があるので、完成が「1か月後」と言われる宣言解除に間に合うかは微妙ですねえ。