743 lines
32 KiB
C#
743 lines
32 KiB
C#
using System.Text;
|
|
using System.Text.Encodings.Web;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
Console.OutputEncoding = Encoding.UTF8;
|
|
|
|
if (args.Length == 0 || IsHelp(args[0]))
|
|
{
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
|
|
try
|
|
{
|
|
return args[0].Trim().ToLowerInvariant() switch
|
|
{
|
|
"generate" => Generate(args.Skip(1).ToArray()),
|
|
"validate" => Validate(args.Skip(1).ToArray()),
|
|
"sample-sleeve" => SampleSleeve(args.Skip(1).ToArray()),
|
|
_ => Fail($"Unknown command: {args[0]}")
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine(ex.Message);
|
|
return 1;
|
|
}
|
|
|
|
static int Generate(string[] args)
|
|
{
|
|
var parsed = Args.Parse(args);
|
|
var imagePath = parsed.Required("--image");
|
|
var outputPath = parsed.Required("--out");
|
|
var textPath = parsed.Optional("--text");
|
|
|
|
var extraction = TeachingMaterialExtractor.Extract(imagePath, textPath);
|
|
WriteJson(outputPath, extraction);
|
|
Console.WriteLine(Path.GetFullPath(outputPath));
|
|
return 0;
|
|
}
|
|
|
|
static int SampleSleeve(string[] args)
|
|
{
|
|
var outputPath = args.Length > 0
|
|
? args[0]
|
|
: Path.Combine("runtime", "rule_templates", "tight_fit_sleeve_requires_disassembly_access.json");
|
|
var extraction = TeachingMaterialExtractor.BuildSleeveRemovalTemplate("built_in_sample", null);
|
|
WriteJson(outputPath, extraction);
|
|
Console.WriteLine(Path.GetFullPath(outputPath));
|
|
return 0;
|
|
}
|
|
|
|
static int Validate(string[] args)
|
|
{
|
|
if (args.Length < 1)
|
|
return Fail("validate requires a template path.");
|
|
|
|
var template = JsonSerializer.Deserialize<RuleTemplatePackage>(File.ReadAllText(args[0], Encoding.UTF8), JsonOptions.Default)
|
|
?? throw new InvalidOperationException("Cannot parse rule template package.");
|
|
var errors = TemplateValidator.Validate(template);
|
|
if (errors.Count > 0)
|
|
{
|
|
foreach (var error in errors)
|
|
Console.Error.WriteLine(error);
|
|
return 2;
|
|
}
|
|
|
|
Console.WriteLine("valid");
|
|
return 0;
|
|
}
|
|
|
|
static void WriteJson<T>(string path, T value)
|
|
{
|
|
var fullPath = Path.GetFullPath(path);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath) ?? ".");
|
|
File.WriteAllText(fullPath, JsonSerializer.Serialize(value, JsonOptions.Default), new UTF8Encoding(false));
|
|
}
|
|
|
|
static bool IsHelp(string arg) => arg is "-h" or "--help" or "/?" or "help";
|
|
|
|
static int Fail(string message)
|
|
{
|
|
Console.Error.WriteLine(message);
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
|
|
static void PrintUsage()
|
|
{
|
|
Console.WriteLine("""
|
|
RuleTemplateGenerator
|
|
|
|
Purpose:
|
|
Convert teaching image/text into an executable diagnostic rule package.
|
|
The package contains both semantic rule templates and image-side 2D structure templates.
|
|
|
|
Usage:
|
|
RuleTemplateGenerator generate --image <teaching.png> --out <rule_template.json> [--text <teaching.txt>]
|
|
RuleTemplateGenerator sample-sleeve [out.json]
|
|
RuleTemplateGenerator validate <rule_template.json>
|
|
|
|
Notes:
|
|
In this version, the extractor is deterministic and only contains the first built-in
|
|
teaching pattern: tight-fit sleeve disassembly. Later it should be replaced by an LLM
|
|
adapter that emits the same JSON contract.
|
|
""");
|
|
}
|
|
|
|
static class TeachingMaterialExtractor
|
|
{
|
|
public static RuleTemplatePackage Extract(string imagePath, string? textPath)
|
|
{
|
|
var text = textPath != null && File.Exists(textPath)
|
|
? File.ReadAllText(textPath, Encoding.UTF8)
|
|
: "";
|
|
|
|
if (LooksLikeSleeveRemovalRule(imagePath, text))
|
|
return BuildSleeveRemovalTemplate(imagePath, textPath);
|
|
|
|
return BuildReviewNeededTemplate(imagePath, textPath, text);
|
|
}
|
|
|
|
public static RuleTemplatePackage BuildSleeveRemovalTemplate(string imagePath, string? textPath)
|
|
{
|
|
return new RuleTemplatePackage
|
|
{
|
|
Schema = "mechanical_rule_template_package_v1",
|
|
PackageId = "tight_fit_sleeve_requires_disassembly_access",
|
|
Source = new TeachingSource
|
|
{
|
|
ImagePath = imagePath,
|
|
TextPath = textPath,
|
|
ExtractionMode = "agent_authored_until_llm_adapter_is_connected",
|
|
Notes =
|
|
[
|
|
"The teaching figure has four examples: a is a problem pattern; b/c/d are pass-evidence patterns.",
|
|
"Hatch lines and printed scale are annotations and must not become contour evidence."
|
|
]
|
|
},
|
|
ExtractionContract = RuleExtractionContract.Default(),
|
|
BrepFactCapabilities = BrepFactCapabilityCatalog.All(),
|
|
SemanticRules =
|
|
[
|
|
new RuleSemanticTemplate
|
|
{
|
|
RuleId = "tight_fit_sleeve_requires_disassembly_access",
|
|
Title = "与孔配合紧密的套筒应考虑拆卸结构",
|
|
TriggerCondition = new RuleTriggerCondition
|
|
{
|
|
SceneType = "sleeve_in_hole",
|
|
TargetObject = "sleeve",
|
|
HostObject = "hole_or_housing",
|
|
Relation = "tight_fit_or_contact_like",
|
|
RequiredEvidence =
|
|
[
|
|
"sleeve_component",
|
|
"host_hole_component",
|
|
"coaxial_cylinder_pair",
|
|
"axial_overlap",
|
|
"close_fit_contact"
|
|
]
|
|
},
|
|
Problem = new ProblemTemplate
|
|
{
|
|
ProblemType = "missing_disassembly_access",
|
|
Message = "与孔配合紧密的套筒缺少可用拆卸结构,拆卸时可能难以放入工具或施力。",
|
|
SourceExample = "a"
|
|
},
|
|
PassEvidenceTypes =
|
|
[
|
|
new PassEvidenceTemplate
|
|
{
|
|
EvidenceType = "external_push_access",
|
|
SourceExample = "b",
|
|
Anchor = "m",
|
|
Required2DTemplateId = "b_external_push_access",
|
|
RequiredModelEvidence =
|
|
[
|
|
"outside_accessible_push_path",
|
|
"push_path_reaches_sleeve_push_area",
|
|
"push_direction_has_removal_component"
|
|
]
|
|
},
|
|
new PassEvidenceTemplate
|
|
{
|
|
EvidenceType = "tool_insertion_gap",
|
|
SourceExample = "c",
|
|
Anchor = "h",
|
|
Required2DTemplateId = "c_tool_insertion_gap",
|
|
RequiredModelEvidence =
|
|
[
|
|
"outside_connected_void_gap",
|
|
"gap_path_reaches_sleeve_push_area",
|
|
"gap_width_sufficient",
|
|
"tool_force_direction_valid"
|
|
]
|
|
},
|
|
new PassEvidenceTemplate
|
|
{
|
|
EvidenceType = "ejector_screw_hole",
|
|
SourceExample = "d",
|
|
Anchor = "t",
|
|
Required2DTemplateId = "d_ejector_screw_hole",
|
|
RequiredModelEvidence =
|
|
[
|
|
"outside_connected_cylindrical_port",
|
|
"port_axis_intersects_sleeve_push_area",
|
|
"port_diameter_sufficient",
|
|
"port_push_direction_valid"
|
|
],
|
|
OptionalModelEvidence = ["thread_or_screw_feature"]
|
|
}
|
|
],
|
|
DecisionLogic = new RuleDecisionLogic
|
|
{
|
|
IfTriggerMissing = "not_applicable",
|
|
IfTriggerUnknown = "needs_review",
|
|
IfTriggeredAndAnyPassEvidence = "pass",
|
|
IfTriggeredAndNoPassEvidence = "problem"
|
|
}
|
|
}
|
|
],
|
|
ImageStructureTemplates =
|
|
[
|
|
SleeveCommonBaseTemplate(),
|
|
new ImageStructureTemplate2D
|
|
{
|
|
TemplateId = "a_no_removal_access",
|
|
Role = "problem",
|
|
SourceExample = "a",
|
|
Entities =
|
|
[
|
|
Entity("sleeve_material", "region", "sleeve"),
|
|
Entity("host_material", "region", "housing_or_hole"),
|
|
Entity("fit_contact", "edge_pair", "contact"),
|
|
Entity("outside", "region", "outside_air")
|
|
],
|
|
RequiredRelations =
|
|
[
|
|
Rel("contact", "sleeve_material", "host_material"),
|
|
Rel("blocks_axial_removal", "host_material", "sleeve_material"),
|
|
Rel("missing", "external_push_access", "sleeve_material"),
|
|
Rel("missing", "tool_insertion_gap", "sleeve_material"),
|
|
Rel("missing", "ejector_screw_hole", "sleeve_material")
|
|
],
|
|
IgnoredVisualElements = DefaultIgnoredVisualElements()
|
|
},
|
|
new ImageStructureTemplate2D
|
|
{
|
|
TemplateId = "b_external_push_access",
|
|
Role = "pass",
|
|
SourceExample = "b",
|
|
Anchor = "m",
|
|
Entities =
|
|
[
|
|
Entity("outside", "region", "outside_air"),
|
|
Entity("push_access_path", "path", "outside_accessible_force_path"),
|
|
Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
|
|
Entity("removal_axis", "axis", "sleeve_removal_direction")
|
|
],
|
|
RequiredRelations =
|
|
[
|
|
Rel("connected_to_outside", "push_access_path", "outside"),
|
|
Rel("reaches", "push_access_path", "sleeve_push_area"),
|
|
Rel("direction_has_component", "push_access_path", "removal_axis")
|
|
],
|
|
IgnoredVisualElements = DefaultIgnoredVisualElements()
|
|
},
|
|
new ImageStructureTemplate2D
|
|
{
|
|
TemplateId = "c_tool_insertion_gap",
|
|
Role = "pass",
|
|
SourceExample = "c",
|
|
Anchor = "h",
|
|
Entities =
|
|
[
|
|
Entity("sleeve_material", "region", "sleeve"),
|
|
Entity("host_material", "region", "housing_or_hole"),
|
|
Entity("gap_region", "region", "void_gap"),
|
|
Entity("outside", "region", "outside_air"),
|
|
Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
|
|
Entity("removal_axis", "axis", "sleeve_removal_direction"),
|
|
Entity("h", "dimension", "minimum_tool_insertion_clearance")
|
|
],
|
|
RequiredRelations =
|
|
[
|
|
Rel("connected_to_outside", "gap_region", "outside"),
|
|
Rel("reaches", "gap_region", "sleeve_push_area"),
|
|
Rel("direction_has_component", "gap_region", "removal_axis"),
|
|
Rel("gap_width_sufficient", "gap_region", "h")
|
|
],
|
|
IgnoredVisualElements = DefaultIgnoredVisualElements()
|
|
},
|
|
new ImageStructureTemplate2D
|
|
{
|
|
TemplateId = "d_ejector_screw_hole",
|
|
Role = "pass",
|
|
SourceExample = "d",
|
|
Anchor = "t",
|
|
Entities =
|
|
[
|
|
Entity("sleeve_material", "region", "sleeve"),
|
|
Entity("host_material", "region", "housing_or_hole"),
|
|
Entity("port_region", "region", "cylindrical_ejector_port"),
|
|
Entity("outside", "region", "outside_air"),
|
|
Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
|
|
Entity("port_axis", "axis", "ejector_screw_axis"),
|
|
Entity("removal_axis", "axis", "sleeve_removal_direction"),
|
|
Entity("t", "dimension", "ejector_screw_hole_size")
|
|
],
|
|
RequiredRelations =
|
|
[
|
|
Rel("connected_to_outside", "port_region", "outside"),
|
|
Rel("axis_intersects", "port_axis", "sleeve_push_area"),
|
|
Rel("direction_has_component", "port_axis", "removal_axis"),
|
|
Rel("port_diameter_sufficient", "port_region", "t")
|
|
],
|
|
IgnoredVisualElements = DefaultIgnoredVisualElements()
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
static RuleTemplatePackage BuildReviewNeededTemplate(string imagePath, string? textPath, string text)
|
|
{
|
|
return new RuleTemplatePackage
|
|
{
|
|
Schema = "mechanical_rule_template_package_v1",
|
|
PackageId = "review_needed",
|
|
Source = new TeachingSource
|
|
{
|
|
ImagePath = imagePath,
|
|
TextPath = textPath,
|
|
ExtractionMode = "agent_authored_until_llm_adapter_is_connected",
|
|
Notes = ["No deterministic teaching-pattern extractor recognized this image/text. LLM extraction is required."]
|
|
},
|
|
ExtractionContract = RuleExtractionContract.Default(),
|
|
BrepFactCapabilities = BrepFactCapabilityCatalog.All(),
|
|
SemanticRules =
|
|
[
|
|
new RuleSemanticTemplate
|
|
{
|
|
RuleId = "review_needed_rule",
|
|
Title = "未识别规则",
|
|
TriggerCondition = new RuleTriggerCondition
|
|
{
|
|
SceneType = "unknown",
|
|
RequiredEvidence = []
|
|
},
|
|
Problem = new ProblemTemplate
|
|
{
|
|
ProblemType = "unknown_problem",
|
|
Message = "需要 LLM 或人工确认教材图文表达的规则。"
|
|
},
|
|
DecisionLogic = new RuleDecisionLogic
|
|
{
|
|
IfTriggerMissing = "needs_review",
|
|
IfTriggerUnknown = "needs_review",
|
|
IfTriggeredAndAnyPassEvidence = "needs_review",
|
|
IfTriggeredAndNoPassEvidence = "needs_review"
|
|
}
|
|
}
|
|
],
|
|
ImageStructureTemplates = []
|
|
};
|
|
}
|
|
|
|
static bool LooksLikeSleeveRemovalRule(string imagePath, string text)
|
|
{
|
|
var combined = $"{imagePath} {text}";
|
|
if (ContainsAny(combined, "套筒", "轴套", "衬套", "sleeve", "bushing") &&
|
|
ContainsAny(combined, "孔", "hole", "bore") &&
|
|
ContainsAny(combined, "拆卸", "推", "打", "间隙", "螺钉", "disassembly", "removal", "gap", "screw"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// The current built-in teaching figure is often provided only as an image.
|
|
// Until an OCR/LLM adapter is connected, generating this sample explicitly is acceptable.
|
|
return true;
|
|
}
|
|
|
|
static ImageStructureTemplate2D SleeveCommonBaseTemplate()
|
|
{
|
|
return new ImageStructureTemplate2D
|
|
{
|
|
TemplateId = "common_sleeve_in_hole_close_fit",
|
|
Role = "trigger",
|
|
SourceExample = "a_b_c_d_common",
|
|
Entities =
|
|
[
|
|
Entity("sleeve_material", "region", "sleeve"),
|
|
Entity("host_material", "region", "housing_or_hole"),
|
|
Entity("sleeve_axis", "axis", "main_axis"),
|
|
Entity("fit_contact", "edge_pair", "coincident_or_close_fit_edges")
|
|
],
|
|
RequiredRelations =
|
|
[
|
|
Rel("coaxial", "sleeve_material", "host_material"),
|
|
Rel("contact", "sleeve_material", "host_material"),
|
|
Rel("axial_overlap", "sleeve_material", "host_material")
|
|
],
|
|
IgnoredVisualElements = DefaultIgnoredVisualElements()
|
|
};
|
|
}
|
|
|
|
static TemplateEntity Entity(string id, string kind, string semanticRole)
|
|
{
|
|
return new TemplateEntity { Id = id, Kind = kind, SemanticRole = semanticRole };
|
|
}
|
|
|
|
static TemplateRelation Rel(string type, string a, string b)
|
|
{
|
|
return new TemplateRelation { Type = type, A = a, B = b };
|
|
}
|
|
|
|
static List<string> DefaultIgnoredVisualElements()
|
|
{
|
|
return ["hatch_lines", "line_thickness", "absolute_scale", "printed_dimension_size", "table_border", "text_border"];
|
|
}
|
|
|
|
static bool ContainsAny(string text, params string[] tokens)
|
|
{
|
|
return tokens.Any(t => text.Contains(t, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
|
|
static class TemplateValidator
|
|
{
|
|
public static List<string> Validate(RuleTemplatePackage package)
|
|
{
|
|
var errors = new List<string>();
|
|
if (package.Schema != "mechanical_rule_template_package_v1")
|
|
errors.Add("schema must be mechanical_rule_template_package_v1");
|
|
if (string.IsNullOrWhiteSpace(package.PackageId))
|
|
errors.Add("packageId is required");
|
|
if (package.SemanticRules.Count == 0)
|
|
errors.Add("at least one semantic rule is required");
|
|
|
|
var structureIds = package.ImageStructureTemplates.Select(t => t.TemplateId).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var rule in package.SemanticRules)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(rule.RuleId))
|
|
errors.Add("semantic rule ruleId is required");
|
|
if (string.IsNullOrWhiteSpace(rule.TriggerCondition.SceneType))
|
|
errors.Add($"{rule.RuleId}: triggerCondition.sceneType is required");
|
|
foreach (var evidence in rule.TriggerCondition.RequiredEvidence)
|
|
{
|
|
ValidateRequiredFact(errors, rule.RuleId, "trigger evidence", evidence);
|
|
}
|
|
foreach (var pass in rule.PassEvidenceTypes)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(pass.Required2DTemplateId) && !structureIds.Contains(pass.Required2DTemplateId))
|
|
errors.Add($"{rule.RuleId}: pass evidence {pass.EvidenceType} references missing structure template {pass.Required2DTemplateId}");
|
|
foreach (var evidence in pass.RequiredModelEvidence)
|
|
{
|
|
ValidateRequiredFact(errors, rule.RuleId, $"pass evidence {pass.EvidenceType}", evidence);
|
|
}
|
|
foreach (var evidence in pass.OptionalModelEvidence)
|
|
{
|
|
ValidateOptionalFact(errors, rule.RuleId, $"pass evidence {pass.EvidenceType}", evidence);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var template in package.ImageStructureTemplates)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(template.TemplateId))
|
|
errors.Add("structure template templateId is required");
|
|
if (template.RequiredRelations.Count == 0)
|
|
errors.Add($"{template.TemplateId}: requiredRelations cannot be empty");
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
static void ValidateRequiredFact(List<string> errors, string ruleId, string context, string fact)
|
|
{
|
|
var capability = BrepFactCapabilityCatalog.Find(fact);
|
|
if (capability == null)
|
|
{
|
|
errors.Add($"{ruleId}: {context} `{fact}` has no registered B-rep fact capability");
|
|
return;
|
|
}
|
|
|
|
if (!capability.AuthoringAllowed)
|
|
errors.Add($"{ruleId}: {context} `{fact}` is not safe for required rules: {capability.MissingWhenAbsent}");
|
|
}
|
|
|
|
static void ValidateOptionalFact(List<string> errors, string ruleId, string context, string fact)
|
|
{
|
|
var capability = BrepFactCapabilityCatalog.Find(fact);
|
|
if (capability == null)
|
|
errors.Add($"{ruleId}: {context} lists optional evidence `{fact}` with no registered B-rep fact capability");
|
|
}
|
|
}
|
|
|
|
static class BrepFactCapabilityCatalog
|
|
{
|
|
static readonly List<BrepFactCapability> Items =
|
|
[
|
|
Fact("sleeve_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "component category/name plus cylindrical section/contact evidence", "No sleeve-like component candidate was recognized."),
|
|
Fact("sleeve_like_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "component category/name plus cylindrical section/contact evidence", "No sleeve-like component candidate was recognized."),
|
|
Fact("host_hole_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "host/housing component and coaxial/contact evidence", "No host or hole component candidate was recognized."),
|
|
Fact("coaxial_cylinder_pair", "2d_section_brep", "implemented", "SectionStructureSynthesizer/SketchGraph.Relations", "coaxial sleeve/host section geometry", "No coaxial sleeve-host pair was detected."),
|
|
Fact("axial_overlap", "2d_section_brep", "implemented", "SectionStructureSynthesizer/SketchGraph.Relations", "overlapping axial ranges in section", "No axial overlap between sleeve and host was detected."),
|
|
Fact("close_fit_contact", "2d_section_brep", "implemented", "SketchGraph.Relations contact", "contact or near-contact section edges", "No close-fit contact relation was detected."),
|
|
Fact("close_fit_contact_or_small_clearance", "2d_section_brep", "implemented", "SketchGraph.Relations contact/clearance", "contact or small clearance section edges", "No close-fit contact or small clearance relation was detected."),
|
|
Fact("outside_accessible_push_path", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "exposed sleeve end edge and outside clearance", "No exposed sleeve push face was detected."),
|
|
Fact("push_path_reaches_sleeve_push_area", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "exposed sleeve end edge", "No push path reaches a sleeve push area."),
|
|
Fact("push_direction_has_removal_component", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "push face orientation against sleeve axis", "No push direction with removal-axis component was detected."),
|
|
Fact("outside_connected_void_gap", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "outside-connected free-space cells between sleeve and host", "No outside-connected sleeve/host gap candidate was detected."),
|
|
Fact("gap_path_reaches_sleeve_push_area", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "gap cluster adjacent to sleeve push area", "No detected gap cluster reached the sleeve push area."),
|
|
Fact("gap_width_sufficient", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "estimated minimum sleeve-host free-space width", "No detected gap satisfied the minimum tool-width threshold."),
|
|
Fact("tool_force_direction_valid", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "gap cluster with axial extent along removal direction", "No detected gap had a sufficient removal-direction extent."),
|
|
Fact("outside_connected_cylindrical_port", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "small host/end-cap cylindrical face treated as port candidate", "No small host/end-cap cylindrical port candidate was detected."),
|
|
Fact("port_axis_intersects_sleeve_push_area", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "port axis close enough to sleeve push area", "No port axis was found to reach the sleeve push area."),
|
|
Fact("port_diameter_sufficient", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "cylindrical port radius/diameter", "No detected port diameter satisfied the minimum threshold."),
|
|
Fact("port_push_direction_valid", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "port axis with removal-axis force component", "No detected port axis had a sufficient removal-direction component."),
|
|
Fact("thread_or_screw_feature", "feature_metadata_or_modeled_thread", "optional_only", "optional SolidWorks feature/thread metadata or modeled helical B-rep", "thread feature metadata or real thread geometry", "Pure B-rep often does not contain reliable thread semantics; keep this optional.")
|
|
];
|
|
|
|
public static List<BrepFactCapability> All() => Items.Select(i => i.Clone()).ToList();
|
|
|
|
public static BrepFactCapability? Find(string fact)
|
|
{
|
|
var normalized = NormalizeToken(fact);
|
|
return Items.FirstOrDefault(i => i.Fact.Equals(normalized, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
static BrepFactCapability Fact(string fact, string source, string status, string extractor, string primitive, string missingWhenAbsent)
|
|
{
|
|
return new BrepFactCapability
|
|
{
|
|
Fact = fact,
|
|
SourceLayer = source,
|
|
ExtractorStatus = status,
|
|
Extractor = extractor,
|
|
RequiredPrimitive = primitive,
|
|
MissingWhenAbsent = missingWhenAbsent,
|
|
AuthoringAllowed = status is "implemented" or "partial" or "planned"
|
|
};
|
|
}
|
|
|
|
static string NormalizeToken(string value) => value.Trim().Replace(' ', '_').ToLowerInvariant();
|
|
}
|
|
|
|
sealed class Args
|
|
{
|
|
readonly Dictionary<string, string> _values;
|
|
|
|
Args(Dictionary<string, string> values)
|
|
{
|
|
_values = values;
|
|
}
|
|
|
|
public static Args Parse(string[] args)
|
|
{
|
|
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
for (int i = 0; i < args.Length; i++)
|
|
{
|
|
var key = args[i];
|
|
if (!key.StartsWith("--", StringComparison.Ordinal))
|
|
throw new ArgumentException($"Unexpected argument: {key}");
|
|
if (i + 1 >= args.Length)
|
|
throw new ArgumentException($"Missing value for {key}");
|
|
values[key] = args[++i];
|
|
}
|
|
return new Args(values);
|
|
}
|
|
|
|
public string Required(string key)
|
|
{
|
|
if (!_values.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
|
|
throw new ArgumentException($"{key} is required.");
|
|
return value;
|
|
}
|
|
|
|
public string? Optional(string key)
|
|
{
|
|
return _values.TryGetValue(key, out var value) ? value : null;
|
|
}
|
|
}
|
|
|
|
sealed class RuleTemplatePackage
|
|
{
|
|
public string Schema { get; set; } = "mechanical_rule_template_package_v1";
|
|
public string PackageId { get; set; } = "";
|
|
public TeachingSource Source { get; set; } = new();
|
|
public RuleExtractionContract ExtractionContract { get; set; } = new();
|
|
public List<BrepFactCapability> BrepFactCapabilities { get; set; } = [];
|
|
public List<RuleSemanticTemplate> SemanticRules { get; set; } = [];
|
|
public List<ImageStructureTemplate2D> ImageStructureTemplates { get; set; } = [];
|
|
}
|
|
|
|
sealed class BrepFactCapability
|
|
{
|
|
public string Fact { get; set; } = "";
|
|
public string SourceLayer { get; set; } = "";
|
|
public string ExtractorStatus { get; set; } = "";
|
|
public string Extractor { get; set; } = "";
|
|
public string RequiredPrimitive { get; set; } = "";
|
|
public string MissingWhenAbsent { get; set; } = "";
|
|
public bool AuthoringAllowed { get; set; }
|
|
|
|
public BrepFactCapability Clone()
|
|
{
|
|
return new BrepFactCapability
|
|
{
|
|
Fact = Fact,
|
|
SourceLayer = SourceLayer,
|
|
ExtractorStatus = ExtractorStatus,
|
|
Extractor = Extractor,
|
|
RequiredPrimitive = RequiredPrimitive,
|
|
MissingWhenAbsent = MissingWhenAbsent,
|
|
AuthoringAllowed = AuthoringAllowed
|
|
};
|
|
}
|
|
}
|
|
|
|
sealed class RuleExtractionContract
|
|
{
|
|
public string LlmResponsibility { get; set; } = "";
|
|
public string ProgramResponsibility { get; set; } = "";
|
|
public List<string> RequiredLlmOutputs { get; set; } = [];
|
|
public List<string> RequiredProgramEvidence { get; set; } = [];
|
|
|
|
public static RuleExtractionContract Default()
|
|
{
|
|
return new RuleExtractionContract
|
|
{
|
|
LlmResponsibility = "Extract rule semantics and image-side 2D structure templates from teaching image/text; do not judge product models.",
|
|
ProgramResponsibility = "Extract model B-rep evidence, generate model-side 2D section structures, match templates, and make final rule decisions.",
|
|
RequiredLlmOutputs =
|
|
[
|
|
"rule object and trigger condition",
|
|
"problem type and problem evidence",
|
|
"pass evidence types",
|
|
"image 2D structure templates",
|
|
"visual anchors such as m/h/t",
|
|
"ignored visual annotations such as hatches and scale"
|
|
],
|
|
RequiredProgramEvidence =
|
|
[
|
|
"components and inferred categories",
|
|
"faces, cylinders, planes, axes, and contact-like relations",
|
|
"scene candidates",
|
|
"section 2D structure instances",
|
|
"real dimensions and contact validation evidence"
|
|
]
|
|
};
|
|
}
|
|
}
|
|
|
|
sealed class TeachingSource
|
|
{
|
|
public string ImagePath { get; set; } = "";
|
|
public string? TextPath { get; set; }
|
|
public string ExtractionMode { get; set; } = "";
|
|
public List<string> Notes { get; set; } = [];
|
|
}
|
|
|
|
sealed class RuleSemanticTemplate
|
|
{
|
|
public string RuleId { get; set; } = "";
|
|
public string Title { get; set; } = "";
|
|
public RuleTriggerCondition TriggerCondition { get; set; } = new();
|
|
public ProblemTemplate Problem { get; set; } = new();
|
|
public List<PassEvidenceTemplate> PassEvidenceTypes { get; set; } = [];
|
|
public RuleDecisionLogic DecisionLogic { get; set; } = new();
|
|
}
|
|
|
|
sealed class RuleTriggerCondition
|
|
{
|
|
public string SceneType { get; set; } = "";
|
|
public string TargetObject { get; set; } = "";
|
|
public string HostObject { get; set; } = "";
|
|
public string Relation { get; set; } = "";
|
|
public List<string> RequiredEvidence { get; set; } = [];
|
|
}
|
|
|
|
sealed class ProblemTemplate
|
|
{
|
|
public string ProblemType { get; set; } = "";
|
|
public string Message { get; set; } = "";
|
|
public string SourceExample { get; set; } = "";
|
|
}
|
|
|
|
sealed class PassEvidenceTemplate
|
|
{
|
|
public string EvidenceType { get; set; } = "";
|
|
public string SourceExample { get; set; } = "";
|
|
public string Anchor { get; set; } = "";
|
|
public string Required2DTemplateId { get; set; } = "";
|
|
public List<string> RequiredModelEvidence { get; set; } = [];
|
|
public List<string> OptionalModelEvidence { get; set; } = [];
|
|
}
|
|
|
|
sealed class RuleDecisionLogic
|
|
{
|
|
public string IfTriggerMissing { get; set; } = "not_applicable";
|
|
public string IfTriggerUnknown { get; set; } = "needs_review";
|
|
public string IfTriggeredAndAnyPassEvidence { get; set; } = "pass";
|
|
public string IfTriggeredAndNoPassEvidence { get; set; } = "problem";
|
|
}
|
|
|
|
sealed class ImageStructureTemplate2D
|
|
{
|
|
public string TemplateId { get; set; } = "";
|
|
public string Role { get; set; } = "";
|
|
public string SourceExample { get; set; } = "";
|
|
public string Anchor { get; set; } = "";
|
|
public List<TemplateEntity> Entities { get; set; } = [];
|
|
public List<TemplateRelation> RequiredRelations { get; set; } = [];
|
|
public List<string> IgnoredVisualElements { get; set; } = [];
|
|
}
|
|
|
|
sealed class TemplateEntity
|
|
{
|
|
public string Id { get; set; } = "";
|
|
public string Kind { get; set; } = "";
|
|
public string SemanticRole { get; set; } = "";
|
|
}
|
|
|
|
sealed class TemplateRelation
|
|
{
|
|
public string Type { get; set; } = "";
|
|
public string A { get; set; } = "";
|
|
public string B { get; set; } = "";
|
|
}
|
|
|
|
static class JsonOptions
|
|
{
|
|
public static readonly JsonSerializerOptions Default = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
WriteIndented = true,
|
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
|
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
|
|
};
|
|
}
|