【MATLAB/Simulink】.mファイルで定義した変数をSimulink上のどこで使用しているか、検索できるようにしてみた

# あらすじ

Simulinkで使用するブロックの中で、ConstantやCompareToConstantなど、別途.mファイルにおいて定義した変数を使用できるブロックがあります。

下図のようなイメージです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/b2ea7bac-15f5-b990-36ad-29dbebe20ab8.png)

この機能を使用する上で、困ったことが一点あります。

それは、定義された変数がSimulink上のどこで何箇所使われているか、分からないというものです。Simulinkの検索機能でも、調べることができませんでした。

そこで、ツールを作成して解決を試みます。

# 対象のSimulinkモデル

今回、検証用として以下のSimulinkモデルを使用します。

三階層に分かれており、Constant/CompareToConstantブロックが複数使われています。
また、そのConstant/CompareToConstantブロックも.mファイルで定義している変数を使用するものと、単に入力された数値を使っているものが混在しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/a24dcb5c-77c1-1351-d091-95b8d60f5335.png)

# ツールの使い方

作成したツールを用いて、検証してみます。

まず、ツールを実行すると以下のようなUIが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/c00e1e74-6bbe-e3d2-7913-495a687cabfd.png)

検索ワードを入力して「検索開始」ボタンを押すと…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/fc752860-c338-4b08-030e-ac86dd348800.png)

検索結果と、「前を選択」「次を選択」ボタンが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/bdb4881b-4a8a-2197-68d5-c1731039a7fd.png)

検索結果をクリックするか、「前を選択」「次を選択」ボタンを押すと…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/a1566e8c-ab68-7f87-ae84-111d74485092.png)

選択されたブロックのある階層まで自動で移動し、そのブロックがSimulink上で強調表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/5677d784-46aa-42aa-46e2-56b6557cd27d.png)


以上のように、作成したツールで目的を達成することができました。


# ソース

```MATLAB:detect_varname_defined_in_dotm.m
function detect_varname_defined_in_dotm

    % 選択したブロックの強調及び前回強調したブロックの強調解除
    function go_to_defined_variable(~, ~)
            
        if prvar  % 前回強調したブロックの強調を解除
            hilite_system(tgtbox.String(prvar), 'default');
        end
        
        hilite_system(tgtbox.String(tgtbox.Value), 'user1');

        prvar = tgtbox.Value; % 今回強調したブロックを、前回値として保存
    end
    
    % 検索において [完全一致 or 部分一致] を選択
    function change_search_mode(~, event)
        
        switch event.NewValue.String
            
            case '完全一致'
                semode = 'perfect_match';
            case '部分一致'
                semode = 'part_match';
        end
    end

    % 検索条件に一致したブロックをリストボックスに表示
    function search_tgtword_in_Simulink(~, ~)
        
        if prvar  % 前回強調したブロックの強調を解除
            hilite_system(tgtbox.String(prvar), 'default');
        end
        
        % 前とは別の条件で検索に一致したブロックが前回よりも多い場合、
        % もし前の検索の時に強調していたブロックによっては新しい検索結果で
        % 範囲外になる可能性があるため、前回強調ブロックは初期化しておく
        prvar = 0;
        
        % 一つ前のキーワードで検索した時に選択していたブロックの番号が
        % 新しいキーワードで検索した際に範囲外でエラーにならないように、
        % 選択しているブロックの番号を1（リストの一番上）に指定しておく
        tgtbox.Value = 1;
        
        % 選択したモードに合わせて条件に一致するブロックを検索
        if strcmp(semode, 'perfect_match')
            
            findstat = strcmp(varlist(:, 1), edit_word.String);
            msgtxt = {'変数「', '」を使用するブロックは下記', 'ヶ所です。'};
        else
            % ↓cell配列で結果を返したくないため、cellfunとanyを使用
            findstat = find(cellfun(@any, strfind(varlist(:, 1), edit_word.String)));
            msgtxt = {'文字列「', '」を名前に含む変数を使用するブロックは下記', 'ヶ所です。'};
        end
        
        matchnum = sum(sum(logical(findstat(:, 1)))); % 条件に一致するブロックの数を算出
        
        % 検索結果をテキストとして表示
        txt_result.String = horzcat(cell2mat(msgtxt(1)), edit_word.String, cell2mat(msgtxt(2)), num2str(matchnum), cell2mat(msgtxt(3)));
                
        if strcmp(edit_word.String, '')
            
            warndlg('検索ワードを入力してから開始ボタンを押して下さい。', '！警告！');
            
        elseif matchnum == 0;
            
            % リストボックス、選択ボタン等を非表示（中身がない状態なのに選択されてしまうと面倒なため）
            tgtbox.Visible    = 'off';
            bttn_prev.Visible = 'off';
            bttn_next.Visible = 'off';
            
            warndlg(horzcat('「', edit_word.String, '」を名前に含む変数は使用されていません。'), '！警告！');
        else
            % 検索条件に一致したブロックをリストボックスに格納
            tgtwordlist = varlist(findstat, :);
            tgtbox.String = tgtwordlist(:, 2);
            
            % リストボックス、選択ボタン等を表示
            txt_result.Visible = 'on';
            tgtbox.Visible     = 'on';
            bttn_prev.Visible  = 'on';
            bttn_next.Visible  = 'on';
        end
    end

    %「次を選択」or「前を選択」ボタン押下時に強調するブロックを変更
    function focus_next_or_previous(source, ~)
        
        % リストボックスに記載のブロック数を取得
        listnum = size(tgtbox.String, 1);
        
        if strcmp(source.UserData, 'next') % 次(↓)のブロックを選択
            
            if tgtbox.Value < listnum 
                
                tgtbox.Value = tgtbox.Value + 1;
            else
                % 一番下のブロックを選択していた場合、一番上に戻る
                tgtbox.Value = 1;
            end
            
        elseif strcmp(source.UserData, 'prev') % 前(↑)のブロックを選択

            if tgtbox.Value > 1
                
                tgtbox.Value = tgtbox.Value - 1;
            else
                % 一番上のブロックを選択していた場合、一番下に戻る
                tgtbox.Value = listnum;
            end
        end
        
        % 今回選択したブロックを新たに強調
        go_to_defined_variable();
    end

    % Simulinkモデルを読み込み
    load_system('test');

    figure('Name', '.mファイルで定義された変数の探索', 'Position', [300, 150, 640, 400]);

    % Constant/CompareToConstantブロックのみの使用変数名・パスを取得
    blockname = find_system('test', 'regexp', 'on', 'BlockType', 'Constant|SubSystem');
    varlist = get_defined_const_list(blockname);
    
    prvar = 0; % 前回強調していたブロック/サブシステムの番号
    semode = 'perfect_match'; % 語句の検索モード（デフォルトでは完全一致）
    
    % ブロックの強調/強調解除の設定
    set_param(0, 'HiliteAncestorsData',struct('HiliteType', 'user1', ... 
                           'ForegroundColor', 'darkGreen', ... 
                           'BackgroundColor', 'lightBlue'));
    set_param(0, 'HiliteAncestorsData', ... 
                    struct('HiliteType', 'default', ... 
                           'ForegroundColor', 'black', ... 
                           'BackgroundColor', 'white'));
                       
    uicontrol('Style', 'text', 'String', '検索したい変数の名前を入力して下さい。', 'Position', [210, 370, 200, 20]);
    uicontrol('Visible', 'on', 'Style', 'pushbutton', 'String', '検索開始', 'Position', [270, 315, 70, 25], 'Callback', @search_tgtword_in_Simulink);
    txt_result = uicontrol('Style', 'text', 'Visible', 'off', 'String', '', 'Position', [55, 270, 350, 20]);
    edit_word = uicontrol('Style', 'edit', 'String', '', 'Position', [240, 352, 130, 20]);
    tgtbox = uicontrol('Style', 'listbox', 'Visible', 'off', 'String', '', 'Position', [75, 25, 500, 250], 'Callback', @go_to_defined_variable);
    bttn_prev = uicontrol('Visible', 'off', 'Style', 'pushbutton', 'String', '前を選択', 'UserData', 'prev', 'Position', [12, 155, 55, 25], 'Callback', @focus_next_or_previous);
    bttn_next = uicontrol('Visible', 'off', 'Style', 'pushbutton', 'String', '次を選択', 'UserData', 'next', 'Position', [12, 120, 55, 25], 'Callback', @focus_next_or_previous);
    
    rdopp = uibuttongroup('Visible','on', 'Position', [.72, .78, .18, .19], 'SelectionChangedFcn', @change_search_mode);
    uicontrol(rdopp, 'Style', 'radiobutton', 'String', '完全一致', 'Position', [5, 30, 100, 15], 'HandleVisibility', 'on');
    uicontrol(rdopp, 'Style', 'radiobutton', 'String', '部分一致', 'Position', [5,  7, 100, 15], 'HandleVisibility', 'on');
    uicontrol(rdopp, 'Style', 'text', 'String', '[ 検索モードを選択 ]', 'Position', [2, 55, 100, 15]);
end

% .mファイルで定義している変数を使用するブロック（Constant か CompareToConstant）を探索
function constlist = get_defined_const_list(blocks)
    
    % 高速化のために、forループ内でセル配列のサイズは増やさずに事前に初期化
    constlist = cell(length(blocks), 2);
    constnum = 1;

    for i = 1 : length(blocks)

        blocktype = cell2mat(get_param(blocks(i), 'BlockType'));

        switch blocktype % 'BlockType'パラメータからConsantとCompareToConstantのみを取得

            case 'Constant'

                varname = get_param(blocks(i), 'Value');
                
                % .mファイルで定義される変数を使用するブロックのみ、リストに登録
                [constlist, constnum] = register_new_variable_if_itis_defined(varname, constlist, constnum, blocks(i));
                                
            case 'SubSystem' % CompareToConstantブロックは、BlockTypeが何故か"Subsystm"のため（本当のSubsystem も Subsystem）

                try
                    varname = get_param(blocks(i), 'const'); % ここでCompareToConstantを取得
                    
                catch % 本当の「SubSystem」は'const'というパラメータが存在しないため、例外処理に入る
                    
                    continue % 本当のSubsystemはただスルーしたいので、ループの処理を次に回すだけ
                end
                % .mファイルで定義される変数を使用するブロックのみ、リストに登録
                [constlist, constnum] = register_new_variable_if_itis_defined(varname, constlist, constnum, blocks(i));
        end
    end
    % 使用しなかった（余った）セル配列の要素を削除
    empidxall = find(cellfun('isempty', constlist));
    empidxrow = empidxall(1 : length(empidxall)/2);
    constlist(empidxrow, :) = [];
end

% .mファイルにて定義されている変数を使用するブロックのみの情報をリスト化
function [list, num] = register_new_variable_if_itis_defined(name, list, num, path)
    
     % ブロックで使用するのが定義されている変数なのか、ただの数字なのか判定
    if isnan(str2double(cell2mat(name)))

        list(num, 1) = name; % 変数の名前を登録
        list(num, 2) = path; % ブロックのパスを登録
        num = num + 1;       % 登録したブロックの数をカウント
    end
end
```
# 困ったこと

　CompareToConstantブロックのBlockTypeパラメータが何故かSubsystemになっています。本当のSubSystemもBlockTypeがSubSystemのため、両者の判別に多少難儀しました。幸い、本当のSubsytemの方には'const'パラメータがないため、ブロックのconstパラメータを取得する処理にてエラーが出たらCompareToConstantではなくSubSystemと見做し、例外処理に移行してから次のループに回すという処理で落ち着きました。
　本当はexist関数とかで'const'パラメータの有無判定が出来れば、と色々試みましたが、流石にパラメータ有無の判別まではできず。。

# 参考にさせていただいた情報

本記事執筆のために、前述のもの以外にも下記情報を参考にさせていただきました。

|内容|リンク先|
|---|---|
|find_system関数で複数のBlock Typeを指定する方法|https://jp.mathworks.com/matlabcentral/answers/431961-how-to-search-multiple-block-types-using-find_system|
|セル配列の空の要素を削除する方法|https://jp.mathworks.com/matlabcentral/answers/101752-|
|UIに配置したオブジェクト内での改行方法|https://aresei-note.com/8261|


# 最後に
ご指摘・改善案・間違い指摘、大歓迎です。是非是非是非是非遠慮せずに下さい。
（特に、こんな手間かけなくても本目的を達成できるぞ！などありましたら是非！)

# 2021/3/22 追記：Simulinkの検索機能でも実施可能

本件、ツールをわざわざ作らなくても目的が達成できることがコメントでのご指摘により判明しました。

やはり、ツールを作ろうとする前によく調べるべきですね。。反省です。

以下、その方法です。

Simulinkのモデル上にてCtrl + Fを押し、表示されたダイアログの「詳細設定」タブより各ブロックのプロパティ内容まで検索することができます。

今回で言えばConstantブロックとCompareToConstantブロックのプロパティについて調べたいので、「プロパティ」に”Value”もしくは”const”と入力し、「値」に検索したい変数名を入力して検索すればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/aa0d88df-8af9-b83a-a2c7-c2984c939b6a.png)

ただ、面倒なことにConstantブロックとCompareToConstantブロックのプロパティ名がそれぞれ異なるため、両方のブロックを一度に選択できない（「選択」にあるチェックボックスのどちらかだけONした状態で検索する必要がある）のが玉にキズですが、これでも十分に使えます。

詳細は下記の「モデル ブラウザーを使用した詳細検索の実行」に記載
https://jp.mathworks.com/help/simulink/slref/simulinkeditor.html