CATIAでテトリス2


CATIAでテトリスGUI版。 まずはPydroidで実験。
import tkinter as tk
from tkinter import ttk, messagebox
import random
import traceback

try:
    import win32com.client
except ImportError:
    win32com = None


TETROMINOS = {
    "I": [[1, 1, 1, 1]],
    "O": [[1, 1], [1, 1]],
    "T": [[0, 1, 0], [1, 1, 1]],
    "S": [[0, 1, 1], [1, 1, 0]],
    "Z": [[1, 1, 0], [0, 1, 1]],
    "J": [[1, 0, 0], [1, 1, 1]],
    "L": [[0, 0, 1], [1, 1, 1]],
}


class CATIAV5R21Tetris3D:
    def __init__(self, logger):
        self.log = logger

        self.catia = None
        self.documents = None
        self.part_document = None
        self.part = None
        self.selection = None

        self.shape_factory = None
        self.hybrid_shape_factory = None

        self.origin_elements = None
        self.xy_plane = None

        self.bodies = None
        self.main_body = None
        self.grid_body = None
        self.locked_body = None
        self.current_body = None

        self.board_width = 10
        self.board_height = 20
        self.block_size = 10.0
        self.block_height = 5.0

        self.board = [[0 for _ in range(self.board_width)] for _ in range(self.board_height)]

        self.current_piece = None
        self.current_shape = None
        self.current_x = 0
        self.current_y = 0

        self.score = 0
        self.lines_cleared_total = 0
        self.game_over = False
        self.reframed_once = False

    # -----------------------------
    # 基本ユーティリティ
    # -----------------------------
    def safe_log(self, msg):
        try:
            self.log(msg)
        except Exception:
            pass

    def connect(self):
        if win32com is None:
            raise RuntimeError("pywin32 が未インストールです。pip install pywin32 を実行してください。")

        self.safe_log("CATIA接続中...")
        self.catia = win32com.client.Dispatch("CATIA.Application")
        self.catia.Visible = True
        self.catia.DisplayFileAlerts = False
        self.documents = self.catia.Documents

        try:
            version = self.catia.SystemConfiguration.Version
            self.safe_log(f"CATIA接続成功: {version}")
        except Exception:
            self.safe_log("CATIA接続成功")

    def create_part(self):
        self.safe_log("新規 Part 作成中...")
        self.part_document = self.documents.Add("Part")
        self.part = self.part_document.Part
        self.selection = self.part_document.Selection

        self.shape_factory = self.part.ShapeFactory
        try:
            self.hybrid_shape_factory = self.part.HybridShapeFactory
        except Exception:
            self.hybrid_shape_factory = None

        self.origin_elements = self.part.OriginElements
        self.xy_plane = self.origin_elements.PlaneXY

        self.bodies = self.part.Bodies
        self.main_body = self.bodies.Item(1)
        self.main_body.Name = "MainBody"

        self.grid_body = self.add_body("GridBody")
        self.locked_body = self.add_body("LockedBlocksBody")
        self.current_body = self.add_body("CurrentPieceBody")

        self.safe_log("Part 作成完了")

    def add_body(self, name):
        body = self.bodies.Add()
        body.Name = name
        return body

    def set_inwork(self, obj):
        try:
            self.part.InWorkObject = obj
        except Exception:
            pass

    def update_part(self):
        self.part.Update()

    def clear_selection(self):
        try:
            self.selection.Clear()
        except Exception:
            pass

    def delete_with_selection(self, obj):
        """
        V5R21向け Selection ベース削除
        """
        try:
            self.clear_selection()
            self.selection.Add(obj)
            self.selection.Delete()
            self.clear_selection()
            return True
        except Exception:
            return False

    def delete_all_shapes_in_body(self, body):
        """
        Body配下のShapesを後ろから消す
        """
        try:
            shapes = body.Shapes
            count = shapes.Count
            deleted = 0
            for i in range(count, 0, -1):
                try:
                    shp = shapes.Item(i)
                    if self.delete_with_selection(shp):
                        deleted += 1
                except Exception:
                    pass
            return deleted
        except Exception:
            return 0

    def delete_all_sketches_in_body(self, body):
        try:
            sketches = body.Sketches
            count = sketches.Count
            deleted = 0
            for i in range(count, 0, -1):
                try:
                    sk = sketches.Item(i)
                    if self.delete_with_selection(sk):
                        deleted += 1
                except Exception:
                    pass
            return deleted
        except Exception:
            return 0

    def cleanup_body_completely(self, body):
        s1 = self.delete_all_shapes_in_body(body)
        s2 = self.delete_all_sketches_in_body(body)
        return s1 + s2

    # -----------------------------
    # ゲーム状態
    # -----------------------------
    def reset_game_state(self):
        self.board = [[0 for _ in range(self.board_width)] for _ in range(self.board_height)]
        self.current_piece = None
        self.current_shape = None
        self.current_x = 0
        self.current_y = 0
        self.score = 0
        self.lines_cleared_total = 0
        self.game_over = False

    def spawn_piece(self):
        self.current_shape = random.choice(list(TETROMINOS.keys()))
        self.current_piece = [row[:] for row in TETROMINOS[self.current_shape]]
        self.current_x = self.board_width // 2 - len(self.current_piece[0]) // 2
        self.current_y = 0

        if self.check_collision():
            self.game_over = True
            return False
        return True

    def check_collision(self, offset_x=0, offset_y=0, piece=None):
        if piece is None:
            piece = self.current_piece

        for y, row in enumerate(piece):
            for x, cell in enumerate(row):
                if not cell:
                    continue

                nx = self.current_x + x + offset_x
                ny = self.current_y + y + offset_y

                if nx < 0 or nx >= self.board_width:
                    return True
                if ny >= self.board_height:
                    return True
                if ny >= 0 and self.board[ny][nx]:
                    return True
        return False

    def move_left(self):
        if not self.check_collision(offset_x=-1):
            self.current_x -= 1
            return True
        return False

    def move_right(self):
        if not self.check_collision(offset_x=1):
            self.current_x += 1
            return True
        return False

    def move_down(self):
        if not self.check_collision(offset_y=1):
            self.current_y += 1
            return True
        return self.lock_piece()

    def hard_drop(self):
        moved = False
        while not self.check_collision(offset_y=1):
            self.current_y += 1
            moved = True
        self.lock_piece()
        return moved

    def rotate(self):
        if not self.current_piece:
            return False

        rotated = [
            [self.current_piece[y][x] for y in range(len(self.current_piece) - 1, -1, -1)]
            for x in range(len(self.current_piece[0]))
        ]

        if not self.check_collision(piece=rotated):
            self.current_piece = rotated
            return True
        return False

    def lock_piece(self):
        for y, row in enumerate(self.current_piece):
            for x, cell in enumerate(row):
                if cell:
                    by = self.current_y + y
                    bx = self.current_x + x
                    if 0 <= by < self.board_height and 0 <= bx < self.board_width:
                        self.board[by][bx] = 1

        self.clear_lines()
        return self.spawn_piece()

    def clear_lines(self):
        lines_cleared = 0
        y = self.board_height - 1
        while y >= 0:
            if all(self.board[y]):
                del self.board[y]
                self.board.insert(0, [0 for _ in range(self.board_width)])
                lines_cleared += 1
            else:
                y -= 1

        if lines_cleared > 0:
            self.lines_cleared_total += lines_cleared
            gained = lines_cleared * lines_cleared * 100
            self.score += gained
            self.safe_log(f"{lines_cleared} ライン消去 / +{gained} 点")

    # -----------------------------
    # CATIA描画系
    # -----------------------------
    def initialize_scene(self):
        self.connect()
        self.create_part()

        self.safe_log("既存表示を初期化中...")
        self.cleanup_body_completely(self.grid_body)
        self.cleanup_body_completely(self.locked_body)
        self.cleanup_body_completely(self.current_body)

        self.reset_game_state()
        self.build_fixed_grid_3d()
        self.spawn_piece()
        self.rebuild_locked_blocks_3d()
        self.rebuild_current_piece_3d()

        self.safe_reframe_once()
        self.update_part()
        self.safe_log("初期化完了")

    def safe_reframe_once(self):
        if self.reframed_once:
            return
        try:
            viewer = self.catia.ActiveWindow.ActiveViewer
            viewer.Reframe()
            self.reframed_once = True
            self.safe_log("初回のみ Reframe 実行")
        except Exception:
            self.safe_log("Reframe はスキップ")

    def build_fixed_grid_3d(self):
        """
        グリッドは固定スケッチ + 細いPadで3D風の床面表示
        """
        self.safe_log("固定グリッド作成中...")
        self.cleanup_body_completely(self.grid_body)
        self.set_inwork(self.grid_body)

        sketch = self.grid_body.Sketches.Add(self.xy_plane)
        sketch.Name = "GridSketch"
        factory2d = sketch.OpenEdition()

        total_w = self.board_width * self.block_size
        total_h = self.board_height * self.block_size

        # 外枠
        factory2d.CreateLine(0, 0, total_w, 0)
        factory2d.CreateLine(total_w, 0, total_w, total_h)
        factory2d.CreateLine(total_w, total_h, 0, total_h)
        factory2d.CreateLine(0, total_h, 0, 0)

        # グリッド線
        for i in range(1, self.board_width):
            x = i * self.block_size
            factory2d.CreateLine(x, 0, x, total_h)

        for i in range(1, self.board_height):
            y = i * self.block_size
            factory2d.CreateLine(0, y, total_w, y)

        sketch.CloseEdition()
        self.set_inwork(sketch)

        try:
            pad = self.shape_factory.AddNewPad(sketch, 0.5)
            pad.Name = "GridPad"
        except Exception:
            # V5R21で失敗時はスケッチだけ残す
            self.safe_log("GridPad 作成に失敗したためスケッチのみ表示")

        self.update_part()

    def create_block_pad(self, body, grid_x, grid_y, name_prefix):
        """
        単一セルを3Dブロック化
        """
        self.set_inwork(body)
        sketch = body.Sketches.Add(self.xy_plane)

        x1 = grid_x * self.block_size
        y1 = grid_y * self.block_size
        x2 = x1 + self.block_size
        y2 = y1 + self.block_size

        factory2d = sketch.OpenEdition()
        factory2d.CreateLine(x1, y1, x2, y1)
        factory2d.CreateLine(x2, y1, x2, y2)
        factory2d.CreateLine(x2, y2, x1, y2)
        factory2d.CreateLine(x1, y2, x1, y1)
        sketch.CloseEdition()

        self.set_inwork(sketch)
        pad = self.shape_factory.AddNewPad(sketch, self.block_height)
        try:
            pad.Name = f"{name_prefix}_{grid_x}_{grid_y}"
        except Exception:
            pass
        return sketch, pad

    def rebuild_locked_blocks_3d(self):
        """
        固定ブロックはライン消去時に全再構築
        """
        self.cleanup_body_completely(self.locked_body)
        self.set_inwork(self.locked_body)

        for y in range(self.board_height):
            for x in range(self.board_width):
                if self.board[y][x]:
                    self.create_block_pad(self.locked_body, x, y, "Locked")

        self.update_part()

    def rebuild_current_piece_3d(self):
        """
        現在ピースは毎回完全再作成
        """
        self.cleanup_body_completely(self.current_body)
        self.set_inwork(self.current_body)

        if not self.current_piece:
            self.update_part()
            return

        for py, row in enumerate(self.current_piece):
            for px, cell in enumerate(row):
                if cell:
                    gx = self.current_x + px
                    gy = self.current_y + py
                    if gy >= 0:
                        self.create_block_pad(self.current_body, gx, gy, "Current")

        self.update_part()

    def refresh_view(self):
        self.rebuild_current_piece_3d()

    def after_piece_locked_refresh(self):
        self.rebuild_locked_blocks_3d()
        self.rebuild_current_piece_3d()

    # -----------------------------
    # 表示レイヤー管理風
    # -----------------------------
    def show_body_only(self, show_grid=True, show_locked=True, show_current=True):
        """
        CATIAの本当の画層APIを厳密運用するとV5R21で環境差が大きいので、
        ここでは Selection の可視/不可視でグループ管理的に扱う。
        """
        try:
            self._set_visibility(self.grid_body, show_grid)
            self._set_visibility(self.locked_body, show_locked)
            self._set_visibility(self.current_body, show_current)
            self.update_part()
        except Exception as e:
            self.safe_log(f"表示切替警告: {e}")

    def _set_visibility(self, obj, visible=True):
        try:
            self.clear_selection()
            self.selection.Add(obj)
            vis = self.selection.VisProperties
            if visible:
                vis.SetShow(1)
            else:
                vis.SetShow(0)
            self.clear_selection()
        except Exception:
            pass

    # -----------------------------
    # ゲーム実行
    # -----------------------------
    def step_auto(self):
        if self.game_over:
            return False

        action = random.choices(
            ["left", "right", "rotate", "down"],
            weights=[1, 1, 1, 6]
        )[0]

        locked_before = [row[:] for row in self.board]

        if action == "left":
            self.move_left()
        elif action == "right":
            self.move_right()
        elif action == "rotate":
            self.rotate()
        else:
            self.move_down()

        if self.game_over:
            self.after_piece_locked_refresh()
            return False

        if locked_before != self.board:
            self.after_piece_locked_refresh()
        else:
            self.refresh_view()

        return True

    def manual_action(self, action):
        if self.game_over:
            return False

        locked_before = [row[:] for row in self.board]

        if action == "left":
            self.move_left()
        elif action == "right":
            self.move_right()
        elif action == "down":
            self.move_down()
        elif action == "rotate":
            self.rotate()
        elif action == "drop":
            self.hard_drop()

        if self.game_over:
            self.after_piece_locked_refresh()
            return False

        if locked_before != self.board:
            self.after_piece_locked_refresh()
        else:
            self.refresh_view()

        return True


class CATIATetrisManualGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("CATIA V5R21 手動操作GUI版 3Dテトリス")
        self.root.geometry("980x720")

        self.engine = None
        self.running = False
        self.after_id = None
        self.auto_step_count = 0

        self._build_ui()
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

    def _build_ui(self):
        top = ttk.Frame(self.root, padding=8)
        top.pack(fill="x")

        cfg = ttk.LabelFrame(top, text="設定", padding=8)
        cfg.pack(fill="x", pady=4)

        ttk.Label(cfg, text="自動速度(ms)").grid(row=0, column=0, sticky="w")
        self.speed_var = tk.StringVar(value="300")
        ttk.Entry(cfg, textvariable=self.speed_var, width=10).grid(row=0, column=1, padx=5, sticky="w")

        ttk.Label(cfg, text="自動最大手数").grid(row=0, column=2, sticky="w", padx=(20, 0))
        self.max_steps_var = tk.StringVar(value="300")
        ttk.Entry(cfg, textvariable=self.max_steps_var, width=10).grid(row=0, column=3, padx=5, sticky="w")

        btns = ttk.LabelFrame(top, text="制御", padding=8)
        btns.pack(fill="x", pady=4)

        ttk.Button(btns, text="CATIA初期化", command=self.init_catia).pack(side="left", padx=4)
        ttk.Button(btns, text="開始", command=self.start_auto).pack(side="left", padx=4)
        ttk.Button(btns, text="停止", command=self.stop_auto).pack(side="left", padx=4)
        ttk.Button(btns, text="リセット", command=self.reset_game).pack(side="left", padx=4)
        ttk.Button(btns, text="終了", command=self.on_close).pack(side="right", padx=4)

        manual = ttk.LabelFrame(top, text="手動操作", padding=8)
        manual.pack(fill="x", pady=4)

        ttk.Button(manual, text="← 左", command=lambda: self.manual("left"), width=12).pack(side="left", padx=4)
        ttk.Button(manual, text="→ 右", command=lambda: self.manual("right"), width=12).pack(side="left", padx=4)
        ttk.Button(manual, text="↓ 下", command=lambda: self.manual("down"), width=12).pack(side="left", padx=4)
        ttk.Button(manual, text="↻ 回転", command=lambda: self.manual("rotate"), width=12).pack(side="left", padx=4)
        ttk.Button(manual, text="⤓ 一気に落下", command=lambda: self.manual("drop"), width=14).pack(side="left", padx=4)

        vis = ttk.LabelFrame(top, text="表示切替", padding=8)
        vis.pack(fill="x", pady=4)

        self.show_grid_var = tk.BooleanVar(value=True)
        self.show_locked_var = tk.BooleanVar(value=True)
        self.show_current_var = tk.BooleanVar(value=True)

        ttk.Checkbutton(vis, text="Grid", variable=self.show_grid_var, command=self.apply_visibility).pack(side="left", padx=8)
        ttk.Checkbutton(vis, text="Locked", variable=self.show_locked_var, command=self.apply_visibility).pack(side="left", padx=8)
        ttk.Checkbutton(vis, text="Current", variable=self.show_current_var, command=self.apply_visibility).pack(side="left", padx=8)

        status = ttk.LabelFrame(self.root, text="状態", padding=8)
        status.pack(fill="x", padx=8, pady=6)

        self.status_var = tk.StringVar(value="未初期化")
        self.score_var = tk.StringVar(value="Score: 0")
        self.lines_var = tk.StringVar(value="Lines: 0")
        self.steps_var = tk.StringVar(value="Auto Steps: 0")

        ttk.Label(status, textvariable=self.status_var, font=("", 11, "bold")).pack(anchor="w")
        row = ttk.Frame(status)
        row.pack(fill="x", pady=4)
        ttk.Label(row, textvariable=self.score_var).pack(side="left", padx=10)
        ttk.Label(row, textvariable=self.lines_var).pack(side="left", padx=10)
        ttk.Label(row, textvariable=self.steps_var).pack(side="left", padx=10)

        logframe = ttk.LabelFrame(self.root, text="ログ", padding=8)
        logframe.pack(fill="both", expand=True, padx=8, pady=6)

        self.log_text = tk.Text(logframe, wrap="word")
        self.log_text.pack(side="left", fill="both", expand=True)

        sy = ttk.Scrollbar(logframe, orient="vertical", command=self.log_text.yview)
        sy.pack(side="right", fill="y")
        self.log_text.configure(yscrollcommand=sy.set)

        self.root.bind("", lambda e: self.manual("left"))
        self.root.bind("", lambda e: self.manual("right"))
        self.root.bind("", lambda e: self.manual("down"))
        self.root.bind("", lambda e: self.manual("rotate"))
        self.root.bind("", lambda e: self.manual("drop"))

    def log(self, msg):
        self.log_text.insert("end", msg + "\n")
        self.log_text.see("end")
        self.root.update_idletasks()

    def update_status_labels(self):
        if self.engine is None:
            self.score_var.set("Score: 0")
            self.lines_var.set("Lines: 0")
            self.steps_var.set("Auto Steps: 0")
            return

        self.score_var.set(f"Score: {self.engine.score}")
        self.lines_var.set(f"Lines: {self.engine.lines_cleared_total}")
        self.steps_var.set(f"Auto Steps: {self.auto_step_count}")

    def init_catia(self):
        self.stop_auto()
        try:
            self.engine = CATIAV5R21Tetris3D(self.log)
            self.engine.initialize_scene()
            self.auto_step_count = 0
            self.update_status_labels()
            self.status_var.set("CATIA初期化完了")
            self.apply_visibility()
        except Exception as e:
            self.status_var.set("初期化失敗")
            self.log(f"初期化エラー: {e}")
            self.log(traceback.format_exc())
            messagebox.showerror("エラー", f"初期化に失敗しました。\n\n{e}")

    def apply_visibility(self):
        if self.engine is None:
            return
        try:
            self.engine.show_body_only(
                show_grid=self.show_grid_var.get(),
                show_locked=self.show_locked_var.get(),
                show_current=self.show_current_var.get()
            )
        except Exception as e:
            self.log(f"表示切替エラー: {e}")

    def reset_game(self):
        self.stop_auto()
        if self.engine is None:
            return
        try:
            self.engine.reset_game_state()
            self.engine.rebuild_locked_blocks_3d()
            self.engine.spawn_piece()
            self.engine.rebuild_current_piece_3d()
            self.auto_step_count = 0
            self.update_status_labels()
            self.apply_visibility()
            self.status_var.set("リセット完了")
            self.log("ゲームをリセットしました")
        except Exception as e:
            self.status_var.set("リセット失敗")
            self.log(f"リセットエラー: {e}")
            self.log(traceback.format_exc())

    def manual(self, action):
        if self.engine is None:
            messagebox.showwarning("未初期化", "先に CATIA初期化 を押してください。")
            return

        self.stop_auto()

        try:
            alive = self.engine.manual_action(action)
            self.update_status_labels()
            self.apply_visibility()

            if not alive:
                self.status_var.set("ゲームオーバー")
                self.log("ゲームオーバー")
            else:
                self.status_var.set(f"手動操作: {action}")
        except Exception as e:
            self.status_var.set("手動操作エラー")
            self.log(f"手動操作エラー: {e}")
            self.log(traceback.format_exc())

    def start_auto(self):
        if self.engine is None:
            messagebox.showwarning("未初期化", "先に CATIA初期化 を押してください。")
            return
        self.running = True
        self.status_var.set("自動実行中")
        self.schedule_next()

    def stop_auto(self):
        self.running = False
        if self.after_id is not None:
            try:
                self.root.after_cancel(self.after_id)
            except Exception:
                pass
            self.after_id = None

    def schedule_next(self):
        if not self.running:
            return
        try:
            delay = int(float(self.speed_var.get().strip()))
        except Exception:
            delay = 300
        self.after_id = self.root.after(delay, self.run_auto_loop)

    def run_auto_loop(self):
        if not self.running or self.engine is None:
            return

        try:
            try:
                max_steps = int(self.max_steps_var.get().strip())
            except Exception:
                max_steps = 300

            if self.auto_step_count >= max_steps:
                self.running = False
                self.status_var.set("指定手数で終了")
                self.log("指定手数に到達しました")
                return

            alive = self.engine.step_auto()
            self.auto_step_count += 1
            self.update_status_labels()
            self.apply_visibility()

            if not alive:
                self.running = False
                self.status_var.set("ゲームオーバー")
                self.log("ゲームオーバー")
                return

            self.schedule_next()

        except Exception as e:
            self.running = False
            self.status_var.set("自動実行エラー")
            self.log(f"自動実行エラー: {e}")
            self.log(traceback.format_exc())
            messagebox.showerror("実行エラー", str(e))

    def on_close(self):
        self.stop_auto()
        self.root.destroy()


def main():
    root = tk.Tk()
    try:
        style = ttk.Style()
        if "vista" in style.theme_names():
            style.theme_use("vista")
    except Exception:
        pass

    app = CATIATetrisManualGUI(root)
    root.mainloop()


if __name__ == "__main

コメント

このブログの人気の投稿

ミライアイ内服薬は薬事法違反で、ほとんど効果がない詐欺ですか?

最高裁での上告理由書受理・却下の判断基準について

裁判官の忌避申立書の作成例