Initial commit: ノーコード画像検査アプリ (MVP + 追加機能一式)
検査レシピ=処理/検査ステップの縦リスト(配線なし)。yfoxlib(yfoximg/yfoxbase)使用。 - ビジュアル・パイプライン(カード+適用後サムネイル+緑/赤 合否) - テンプレート/対話式ウィザードでレシピ生成(CTaskDialog) - パラメータのスライダー・ライブ調整 - レシピ保存/読込(*.itr) + 最近使ったファイル(MRU) - 検査範囲(ROI)を複数マウス指定(ドラッグ追加/右クリック削除) - カードのドラッグ並べ替え - 判定結果画像の保存 + 検査ログCSV(項目拡張) - 処理6種+検査3種(明るさ/白画素率/塊の数) - /selftest(実行+レシピ往復+結果画像+複数ROI塊カウント検証)、/demo Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+148
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user