検査レシピ=処理/検査ステップの縦リスト(配線なし)。yfoxlib(yfoximg/yfoxbase)使用。 - ビジュアル・パイプライン(カード+適用後サムネイル+緑/赤 合否) - テンプレート/対話式ウィザードでレシピ生成(CTaskDialog) - パラメータのスライダー・ライブ調整 - レシピ保存/読込(*.itr) + 最近使ったファイル(MRU) - 検査範囲(ROI)を複数マウス指定(ドラッグ追加/右クリック削除) - カードのドラッグ並べ替え - 判定結果画像の保存 + 検査ログCSV(項目拡張) - 処理6種+検査3種(明るさ/白画素率/塊の数) - /selftest(実行+レシピ往復+結果画像+複数ROI塊カウント検証)、/demo Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
149 lines
5.1 KiB
C++
149 lines
5.1 KiB
C++
//
|
|
// Wizard.cpp - 対話式ウィザード & 目的テンプレート選択(CTaskDialog ベース、.rc 不要)。
|
|
//
|
|
#include "stdafx.h"
|
|
#include "Wizard.h"
|
|
#include "core/Templates.h"
|
|
#include "core/StepCatalog.h"
|
|
|
|
using namespace core;
|
|
|
|
namespace {
|
|
|
|
// コマンドリンク付き質問を1つ出し、選ばれた ID を返す(キャンセル=0)。
|
|
struct Choice { int id; LPCWSTR caption; };
|
|
|
|
int AskChoices(CWnd* parent, LPCWSTR title, LPCWSTR main, LPCWSTR content,
|
|
const Choice* choices, int n)
|
|
{
|
|
if (!CTaskDialog::IsSupported())
|
|
{
|
|
AfxMessageBox(L"この環境ではウィザードを表示できません。");
|
|
return 0;
|
|
}
|
|
CTaskDialog dlg(content, main, title,
|
|
TDCBF_CANCEL_BUTTON,
|
|
TDF_USE_COMMAND_LINKS | TDF_ALLOW_DIALOG_CANCELLATION);
|
|
for (int i = 0; i < n; i++)
|
|
dlg.AddCommandControl(choices[i].id, choices[i].caption);
|
|
INT_PTR r = dlg.DoModal(parent ? parent->GetSafeHwnd() : NULL);
|
|
for (int i = 0; i < n; i++)
|
|
if (r == choices[i].id) return choices[i].id;
|
|
return 0; // キャンセル
|
|
}
|
|
|
|
// 検査ステップ(末尾想定)の min/max を書き換える。
|
|
void SetInspectRange(Recipe& rc, double lo, double hi)
|
|
{
|
|
for (auto it = rc.steps.rbegin(); it != rc.steps.rend(); ++it)
|
|
{
|
|
if (IsInspect(it->kind)) { it->SetParam(L"min", lo); it->SetParam(L"max", hi); return; }
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool wizard::ChooseTemplate(CWnd* parent, Recipe& out)
|
|
{
|
|
auto tps = AllTemplates();
|
|
std::vector<Choice> ch;
|
|
std::vector<CString> caps; caps.reserve(tps.size());
|
|
for (size_t i = 0; i < tps.size(); i++)
|
|
{
|
|
caps.push_back(tps[i].name + L"\n" + tps[i].desc);
|
|
}
|
|
for (size_t i = 0; i < tps.size(); i++)
|
|
ch.push_back({ (int)(2001 + i), (LPCWSTR)caps[i] });
|
|
|
|
int r = AskChoices(parent, L"テンプレートから作る",
|
|
L"検査の目的を選んでください",
|
|
L"選ぶと、その目的に合った検査レシピが自動で入ります。あとでスライダーで微調整できます。",
|
|
ch.data(), (int)ch.size());
|
|
if (r == 0) return false;
|
|
int idx = r - 2001;
|
|
if (idx < 0 || idx >= (int)tps.size()) return false;
|
|
out = tps[idx].recipe;
|
|
return true;
|
|
}
|
|
|
|
bool wizard::BuildWithWizard(CWnd* parent, Recipe& out)
|
|
{
|
|
// Q1: 何を検査?
|
|
Choice q1[] = {
|
|
{ 1, L"キズ・汚れを見つける\n表面に傷や汚れが無いか調べます" },
|
|
{ 2, L"欠け・穴の数を数える\n穴やパターンの数が想定どおりか調べます" },
|
|
{ 3, L"明るさを確認する\n全体の明るさが基準内か調べます" },
|
|
{ 4, L"文字・印字の有無を見る\n印字・マークがあるか(無いか)調べます" },
|
|
};
|
|
int a1 = AskChoices(parent, L"検査ウィザード (1/2)",
|
|
L"何を検査したいですか?", L"目的に合わせて質問します。", q1, 4);
|
|
if (a1 == 0) return false;
|
|
|
|
auto tps = AllTemplates(); // [0]キズ [1]欠け [2]明るさ [3]文字
|
|
|
|
if (a1 == 1) // キズ・汚れ
|
|
{
|
|
out = tps[0].recipe;
|
|
Choice q2[] = {
|
|
{ 10, L"厳しい:キズは1個も許さない(0個で合格)" },
|
|
{ 11, L"ふつう:小さなキズ1個までは許容(1個まで合格)" },
|
|
{ 12, L"ゆるい:3個まで許容(3個まで合格)" },
|
|
};
|
|
int a2 = AskChoices(parent, L"検査ウィザード (2/2)",
|
|
L"どのくらい厳しく判定しますか?", L"許容するキズの数を選びます。", q2, 3);
|
|
if (a2 == 0) return false;
|
|
double mx = (a2 == 10) ? 0 : (a2 == 11) ? 1 : 3;
|
|
SetInspectRange(out, 0, mx);
|
|
return true;
|
|
}
|
|
if (a1 == 2) // 欠け・穴の数
|
|
{
|
|
out = tps[1].recipe;
|
|
Choice q2[] = {
|
|
{ 20, L"ちょうど1個なら合格" },
|
|
{ 21, L"1〜2個なら合格" },
|
|
{ 22, L"1〜4個なら合格" },
|
|
{ 23, L"あとで自分で決める(1〜4個で仮設定)" },
|
|
};
|
|
int a2 = AskChoices(parent, L"検査ウィザード (2/2)",
|
|
L"いくつなら合格にしますか?", L"想定される穴/塊の数を選びます。", q2, 4);
|
|
if (a2 == 0) return false;
|
|
double lo = 1, hi = 4;
|
|
if (a2 == 20) { lo = 1; hi = 1; }
|
|
else if (a2 == 21) { lo = 1; hi = 2; }
|
|
else { lo = 1; hi = 4; }
|
|
SetInspectRange(out, lo, hi);
|
|
return true;
|
|
}
|
|
if (a1 == 3) // 明るさ
|
|
{
|
|
out = tps[2].recipe;
|
|
Choice q2[] = {
|
|
{ 30, L"暗すぎ・明るすぎを弾く(80〜180)" },
|
|
{ 31, L"明るめ重視(120〜220)" },
|
|
{ 32, L"暗め重視(40〜140)" },
|
|
};
|
|
int a2 = AskChoices(parent, L"検査ウィザード (2/2)",
|
|
L"どの明るさなら合格ですか?", L"合格とする平均の明るさの範囲を選びます。", q2, 3);
|
|
if (a2 == 0) return false;
|
|
if (a2 == 30) SetInspectRange(out, 80, 180);
|
|
else if (a2 == 31) SetInspectRange(out, 120, 220);
|
|
else SetInspectRange(out, 40, 140);
|
|
return true;
|
|
}
|
|
// a1 == 4 文字・印字
|
|
{
|
|
out = tps[3].recipe;
|
|
Choice q2[] = {
|
|
{ 40, L"印字が「ある」ことを確認したい(あれば合格)" },
|
|
{ 41, L"印字が「ない」ことを確認したい(無ければ合格)" },
|
|
};
|
|
int a2 = AskChoices(parent, L"検査ウィザード (2/2)",
|
|
L"どちらを合格にしますか?", L"印字の有無どちらを合格とするか選びます。", q2, 2);
|
|
if (a2 == 0) return false;
|
|
if (a2 == 40) SetInspectRange(out, 2, 100); // 2%以上インクあり
|
|
else SetInspectRange(out, 0, 1); // ほぼ無し
|
|
return true;
|
|
}
|
|
}
|