Files
yoshikazuandClaude Opus 4.8 e4eca6b303 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>
2026-07-23 09:26:10 +09:00

135 lines
3.4 KiB
C++

//
// RecipeStore.cpp - レシピの保存/読込。行ベースの UTF-8 テキスト。
//
// ImgTestSystem Recipe v1
// ROI <enabled> <l> <t> <r> <b>
// STEP <kindId>
// PARAM <key> <value>
// ...
//
#include "stdafx.h"
#include "RecipeStore.h"
#include "StepCatalog.h"
namespace core {
namespace {
void WriteUtf8(CFile& f, const CString& s)
{
int n = WideCharToMultiByte(CP_UTF8, 0, s, s.GetLength(), NULL, 0, NULL, NULL);
if (n <= 0) return;
CStringA a;
WideCharToMultiByte(CP_UTF8, 0, s, s.GetLength(), a.GetBuffer(n), n, NULL, NULL);
a.ReleaseBuffer(n);
f.Write((LPCSTR)a, a.GetLength());
}
CString ReadAllUtf8(LPCWSTR path)
{
CFile f;
if (!f.Open(path, CFile::modeRead | CFile::shareDenyWrite)) return L"";
ULONGLONG len = f.GetLength();
if (len == 0 || len > (50 * 1024 * 1024)) return L"";
CStringA a;
UINT got = f.Read(a.GetBuffer((int)len), (UINT)len);
a.ReleaseBuffer(got);
// BOM 除去
if (a.GetLength() >= 3 && (BYTE)a[0] == 0xEF && (BYTE)a[1] == 0xBB && (BYTE)a[2] == 0xBF)
a = a.Mid(3);
int wn = MultiByteToWideChar(CP_UTF8, 0, a, a.GetLength(), NULL, 0);
CString w;
MultiByteToWideChar(CP_UTF8, 0, a, a.GetLength(), w.GetBuffer(wn), wn);
w.ReleaseBuffer(wn);
return w;
}
} // namespace
bool SaveRecipe(LPCWSTR path, const Recipe& r)
{
CFile f;
if (!f.Open(path, CFile::modeCreate | CFile::modeWrite)) return false;
// UTF-8 BOM
const unsigned char bom[3] = { 0xEF, 0xBB, 0xBF };
f.Write(bom, 3);
WriteUtf8(f, L"ImgTestSystem Recipe v1\r\n");
for (const CRect& roi : r.rois)
{
CString line;
line.Format(L"ROI %d %d %d %d\r\n", (int)roi.left, (int)roi.top, (int)roi.right, (int)roi.bottom);
WriteUtf8(f, line);
}
for (const Step& s : r.steps)
{
WriteUtf8(f, L"STEP " + StepKindId(s.kind) + L"\r\n");
for (const StepParam& p : s.params)
{
CString pl;
pl.Format(L"PARAM %s %.6g\r\n", (LPCWSTR)p.key, p.value);
WriteUtf8(f, pl);
}
}
f.Close();
return true;
}
bool LoadRecipe(LPCWSTR path, Recipe& out)
{
CString text = ReadAllUtf8(path);
if (text.IsEmpty()) return false;
Recipe r;
bool sawHeader = false;
Step* cur = nullptr;
int pos = 0;
while (pos < text.GetLength())
{
int nl = text.Find(L'\n', pos);
CString line = (nl < 0) ? text.Mid(pos) : text.Mid(pos, nl - pos);
pos = (nl < 0) ? text.GetLength() : nl + 1;
line.TrimRight(L"\r");
line.TrimLeft();
if (line.IsEmpty()) continue;
if (line.Find(L"ImgTestSystem Recipe") == 0) { sawHeader = true; continue; }
if (line.Left(4) == L"ROI ")
{
// 新形式: ROI l t r b(4値) / 旧形式: ROI enabled l t r b(5値)
int a = 0, bb = 0, c = 0, d = 0, e = 0;
int got = swscanf_s((LPCWSTR)line, L"ROI %d %d %d %d %d", &a, &bb, &c, &d, &e);
if (got == 5) { if (a != 0) r.rois.push_back(CRect(bb, c, d, e)); }
else if (got == 4) { r.rois.push_back(CRect(a, bb, c, d)); }
continue;
}
if (line.Left(5) == L"STEP ")
{
CString id = line.Mid(5); id.Trim();
StepKind k;
if (StepKindFromId(id, k)) { r.steps.push_back(MakeStep(k)); cur = &r.steps.back(); }
else cur = nullptr;
continue;
}
if (line.Left(6) == L"PARAM " && cur)
{
CString rest = line.Mid(6); rest.Trim();
int sp = rest.Find(L' ');
if (sp <= 0) continue;
CString key = rest.Left(sp);
double val = _wtof(rest.Mid(sp + 1));
cur->SetParam(key, val); // 既定パラメータのうち一致キーのみ上書き
continue;
}
}
if (r.steps.empty() && !sawHeader) return false;
out = r;
return true;
}
} // namespace core