Execute the Revit-side half of the /GM_import → /GM_inject revit two-step flow: take a Set that has already been aligned into a plan, and actually create the Material(s)/Type(s)/parameters in the live Revit document. This step does mutate the user's Revit model — always confirm the concrete plan with the user before calling any Revit-mutating tool.
Lessons Reference
- L-031:Type 層級寫入工具(如
assign_existing_material)回報成功不代表值真的生效——部分族群把該參數設計成 Instance 範疇或關聯到公式,Set()不拋例外但讀回值沒變。Scenario 8 已依此原則要求驗證失敗就停下來,不繼續往下寫共享參數。詳見domain/lessons.md。
Which Set does this act on?
revitalone (no Set name) → pick the Set to act on:- If a
/GM_importwas already run earlier in this conversation, use that Set — you already know its name from context. - Otherwise, read
exported_material_sets.jsonand find entries whoseplanStatusis"已對齊 Agent 計畫"(aligned but not yet injected). If exactly one, use it. If more than one, list them (name + items) and ask the user which one. If none, tell the user to run/GM_importfirst.
- If a
revit <SetName>→ use that Set explicitly (look it up inexported_material_sets.json).- Exception: if the user is explicitly asking for Window/Door/loadable-family RFA injection (Scenario 7 below), there's no Set to look up — skip straight to the Scope check.
Mandatory pre-write gate — expired green-material licenses
Run this before the Scope check, on every path that writes to Revit, including Scenario 7 (RFA) and Scenario 8 (structural material). A TABC label has an end date (period, e.g. 115/07/09 ~ 119/07/08). Writing an already-expired certificate number into GreenMaterial_Mat*_CertNo / _ValidUntil puts a dead license into the delivered model, its schedules, and any submission documents produced from them. The plan engine detects this; this skill is what stops it.
- Regenerate (or read) the plan for this Set as the scenario section tells you to, then check
plan['hasExpiredLicense']. - If it is
false, continue to the Scope check — say nothing further about expiry. - If it is
true, stop your turn before calling any Revit-mutating tool and:- List every entry in
plan['expiredLicenses']: licno, title, company,period, and the parsedvalidUntilend date. - State plainly which fields would carry the expired certificate, and that the model, schedules, and submission documents inherit it.
- Note the database's age from
plan['databaseFreshness']— if it isstaleormissing, the expiry may simply be a stale local snapshot, and/GM_updateis the right first move rather than approving the write. - Ask for explicit approval to write the expired license(s) anyway. Silence, "go ahead" for an earlier question, or a general confirmation from before this list was shown do not count. Do not offer to skip just the expired material unless the user asks — dropping a material from a Set silently is its own failure mode.
- List every entry in
- If the user approves, proceed — but the write is no longer silent: in your final report, state which Type(s) and which
Mat*slot(s) carry an expired license, with the licno and its end date. Repeat it in the Set status update text you write toexported_material_sets.json(plannedActions), so the record survives this conversation. - If the user declines, stop. Do not write anything, and tell them the two ways forward: run
/GM_updatethen/GM_importagain (if the local snapshot is stale), or replace the material in the Set on the showcase page and re-align.
Never treat an expired license as a warning to mention in passing while continuing to write. The method-layer rule is in domain/GM_parameter-schema.md; if this section and that file ever disagree, the domain file wins.
Scope check — which scenario?
Read the Set's purpose field in exported_material_sets.json (e.g. "組合方式: 單一組合 | 品類: Wall | 補充條件: 無").
品類: Wall+組合方式: 單一組合, exactly 2 materials (one board/structure + one paint/finish) → Scenario 1, go to that section below.組合方式: 各別建立(any品類: Floor/Wall/Ceiling) → Scenario 2, go to that section below.組合方式: 單一組合with anything else — non-Wall category (Floor/Ceiling), or more than 2 materials, or materials that needneedsManualReview/Set-category-override resolution → Scenario 3 (general multi-layer), go to that section below.品類: Material(pure/non-geometric material — caulk, adhesive, waterproofing) and the Set has apureMaterialTargetentry inexported_material_sets.json→ Scenario 5 (TASK-005.5), go to that section below.品類: Materialbut nopureMaterialTargetyet → stop. Tell the user to run/GM_importfirst — it will show them a numbered table of candidate Types and wait for their pick beforepureMaterialTargetgets written.- User explicitly asks to inject a green material into a Window/Door/loadable-family Type (this path isn't wired into the
/GM_importalignment flow yet — there's no品類: Window/Doorconvention inexported_material_sets.json) → Scenario 7 (TASK-005.7 /domain/GM_rfa-family-injection.md), go to that section below. Only take this path when the user names a specific base Type themselves; don't infer it from a Set'spurposefield. 品類: Columnor品類: Beam→ Scenario 8 (structural material assignment, not a CompoundStructure build —組合方式is ignored for this category), go to that section below.- Anything else → stop. Tell the user this scenario has no wired Revit tool yet and that building it out is a separate task, not something to improvise on the spot.
Scenario 1 — Wall / 單一組合 (combined wall + paint into one Type)
This mirrors .agents/skills/combined-wall-set-import/SKILL.md — read that file too if anything here is ambiguous.
Re-anchor the live document: call
get_project_infoto confirm a real Revit connection this turn (per CLAUDE.md's MCP Connection Status protocol). If it fails, retry once; if it still fails, stop and report the limitation.Get the plan's two materials: re-run the match (don't reuse a stale
Revit_Injection_Plan.jsonfrom a different Set) —python -c " import sys sys.path.insert(0, 'tools/green-material') import GM_generate_revit_injection_plan as g, json plan = g.generate_injection_plan('<SetName>', <items_list_from_json>, '') print(json.dumps(plan, ensure_ascii=False, indent=2)) "From
plan['materialsMapping'], identify which item is the board/structure material (mappingDetails.layercontainsStructure) and which is the paint/finish material (layercontainsFinish). If there aren't exactly one of each, stop and ask the user — this flow assumes exactly one board + one paint material per domain.md's rule.Pick the source WallType: call
get_wall_types. Prefer a type whose name contains加粉刷or粉刷(per.agents/skills/combined-wall-set-import/domain.md— duplicate from a type that already has a plaster/finish layer, not a bare structural wall). If more than one plausible candidate, show them and ask the user to pick; if exactly one obvious match, propose it and ask for a quick confirm rather than assuming.Confirm before writing anything: show the user a summary and get explicit go-ahead —
- Source WallType (name + ID)
- New type name:
TABC_<SetName>(no square brackets, per domain.md) - Board material name:
<licno>_<title>(full licno, keep any(續)/(增)suffix) →Structure [1], thickness 150mm (or the plan'sdefaultThicknessif it differs) - Paint material name:
<licno>_<title>→Finish 1 [4]/Finish 2 [5], thickness 20mm (or plan'sdefaultThickness) - TASK-005.6: if the board material's
mappingDetails.wallUsageUnspecifiedistrue, explicitly flag it here — "no wall usage (外牆/分戶牆/輕隔間) was specified, using the conservative 150mm default — confirm or tell me the real thickness". IfwallUsageHintis set (Exterior/PartyWall/LightPartition), state which one was detected and its matrix thickness. Do not proceed past this point without the user confirming.
Create the type + materials: call
duplicate_element_typewithsourceTypeId,newTypeName,finishMaterialName(paint),structureMaterialName(board), and thickness overrides if the plan specified non-default ones.Verify materials exist (mandatory — do not skip): call
get_all_materials(searchKeyword: "<the Set's licno prefix or GBM>")and confirm both new materials appear with the IDsduplicate_element_typereturned.Bind shared parameters if needed: call
load_shared_parameterswithfilePathpointing toGreenMaterial_SharedParams.txt(absolute path,tools/green-material/) andcategories: ["Walls"],bindToInstance: false. Safe to call even if already bound (idempotent — reports已存在相符綁定,跳過).Write the 31 shared parameters: call
set_green_material_type_parameterson the newtypeIdwith:certified: truemat1= the board material's data from the plan/database record:name(title),certNo(full licno with suffix),category,subCategory,applicant(company),validUntil(period),cnsSpec,testItems,qualifiedItems. Only includetvoc/formaldehydeif you have real per-material numeric values — do not invent numbers from the prose intestItems.mat2= the paint material's data, same shape. Report anyMissingParametersin the response — that meansload_shared_parametersdidn't actually bind them; don't silently ignore it.
Verify the written values: call
get_element_infoon the newtypeIdand confirm theGreenMaterial_Mat1_*/GreenMaterial_Mat2_*values match what you intended to write.Update the Set's status: call
python -c " import sys sys.path.insert(0, 'tools/green-material') import GM_generate_revit_injection_plan as g g.write_back_to_set_manager('<SetName>', plan_dict, planned_actions_override='已建立 Element ID <NewTypeId> 與材質 Element ID <finishMaterialId>/<structureMaterialId>') "(the
'Element ID'substring inplanned_actions_overrideis what flipsplanStatusto已完成 Revit 牆體元件注入— seewrite_back_to_set_managerinGM_generate_revit_injection_plan.py).Report: new TypeId + TypeName, both MaterialIds + names, which 31-field values were written vs missing, and (optionally) offer to
select_element+zoom_to_elementon an existing instance of that type if one exists in the model.
Scenario 2 — 各別建立 (each material gets its own independent Type)
Each material in the Set becomes its own new ElementType (Floor/Wall/Ceiling — whatever the Set's 品類 says), with one material filling every layer of that Type's compound structure. Unlike Scenario 1, there's no board/paint pairing here — just N materials → N Types. Type name and Material name are the same string (<licno>_<title>, no TABC_ prefix — that prefix is reserved for Scenario 1's combined Type naming).
Re-anchor the live document: call
get_project_infoto confirm a real Revit connection this turn. Retry once on failure; if it still fails, stop and report the limitation.Get the plan's materials: re-run the match for this Set —
python -c " import sys sys.path.insert(0, 'tools/green-material') import GM_generate_revit_injection_plan as g, json plan = g.generate_injection_plan('<SetName>', <items_list_from_json>, '') print(json.dumps(plan, ensure_ascii=False, indent=2)) "Each item in
plan['materialsMapping']becomes one new Type. Note each item'stargetRevitCategory(e.g.OST_Floors) — they should all match the Set's品類; if one doesn't, flag it rather than silently forcing it into the same category.Pick a source Type per category: call
get_types_by_category(category: "Floors")(orWalls/Ceilingsmatching the Set's品類). This lists existing Types with their current materials — pick one plain/basic Type as the duplication source (all new Types can share the same source, or you can ask the user for a per-material source if they want different base builds). Show the candidates and confirm with the user rather than silently guessing.Confirm before writing anything: show the user the full list —
- Source TypeId (shared across all, or per-material)
- For each material: new Type name = new Material name =
<licno>_<title>(full licno, keep any(續)/(增)suffix) Do not proceed past this point without the user confirming.
Create each Type + material: for each material, call
create_single_material_typewithsourceTypeIdandmaterialName(<licno>_<title>). This duplicates the source Type, creates the material, and assigns it to every compound-structure layer of the new Type in one step.
5b. Floor materials only — apply Surface Pattern (TASK-005.2): if the Set's 品類 is Floor and the material is a finish/wear layer (tile, stone, or wood flooring — not a soundproof buffer), call set_material_surface_pattern with materialId = the material ID create_single_material_type just returned:
- Tile/stone material (title contains
磚/石材etc.) →patternType: "Grid"(spacingMmdefaults to 600 for a 600×600 grid; override if the product spec states a different module size). - Wood flooring (title contains
木地板/木質地板etc.) →patternType: "Wood". - Soundproof buffer / non-visible substrate materials → skip this step, no pattern needed. This tool dedups by pattern name, so calling it again for another material of the same spacing reuses the existing FillPatternElement rather than creating a duplicate.
Verify materials exist (mandatory): call
get_all_materials(searchKeyword: "<Set's GBM prefix>")and confirm all N new materials appear with the IDs eachcreate_single_material_typecall returned.Bind shared parameters if needed: call
load_shared_parameterswithcategoriesmatching the Set's品類(e.g.["Floors"]),bindToInstance: false. Idempotent — safe to call even if already bound.Write shared parameters per Type: for each new Type, call
set_green_material_type_parameterswithtypeId= that Type's new ID andmat1= that one material's data (name,certNofull licno,category,subCategory,applicant,validUntil,cnsSpec,testItems,qualifiedItems— only includetvoc/formaldehydeif real per-material numbers exist). Leavemat2/mat3empty — there's only one material per Type in this scenario. Report anyMissingParameters.Verify the written values: call
get_element_infoon each newtypeIdand spot-check theGreenMaterial_Mat1_*values.Update the Set's status: call
write_back_to_set_manager('<SetName>', plan_dict, planned_actions_override='已建立 Element ID <id1>, <id2>, ... 與對應材質')— list every new Element ID so the'Element ID'substring check flipsplanStatusto done.Report: a table of material → new TypeId → new MaterialId, and which shared-parameter fields were written vs missing for each.
Scenario 3 — General multi-layer 單一組合 (2+ materials, any category)
Use create_multi_layer_type — it takes an ordered layers array ({materialName, layerFunction, thicknessMm}) instead of hardcoding 2 materials, so it covers Floor/Wall/Ceiling combined builds with any number of materials (e.g. a Floor with finish tile + soundproof buffer + structural concrete).
Re-anchor the live document: call
get_project_info. Retry once on failure; otherwise stop and report the limitation.Get the plan's materials: re-run the match for this Set. Materials with
mappingDetails.needsManualReview(e.g. concrete that could be Wall or Floor) must already have been resolved — either by aresolvedBySetCategoryOverridein the plan, or by asking the user directly which layer/role each such material plays. Never silently guess a layer assignment for an unresolved material.Get the layer order and function: ⚠️ Two completely independent orderings exist — do not conflate them:
- Physical CompoundStructure layer order (what goes in the
layersarray for step 6, top-to-bottom / exterior-to-interior): if the Set haslayerComposition.sequence,plan['materialsMapping']is already reordered to match it — just build thelayersarray by iteratingplan['materialsMapping']in the order it comes back, using each item'stargetLayer/mappingDetailsforlayerFunction. Skip any item withmappingDetails.isAuxiliary: true(adhesive/sealant/waterproofing, routed vialayerComposition.auxiliaryin the showcase page's "🧴 輔助材料" drop zone, or via keyword detection) — it has nolayerFunction/thickness and does not belong in thelayersarray at all; it still gets amatNslot in step 9, just not a physical layer. Never re-sort the remaining items byassignedSlot/Mat-number —mat1→mat2→mat3... is a shared-parameter metadata slot number (step 9), not a construction position, and sorting the physical layers by it silently corrupts the layer order even though the shared-parameter write still looks successful. - If the Set has no
layerComposition(no sequence to inherit), Scenario 3 has no fixed convention — ask the user which material goes in whichlayerFunctionand in what order, unless they already told you in this conversation. Do not assume order from the Set'sitemslist order.
- Physical CompoundStructure layer order (what goes in the
Pick the source Type: call
get_types_by_categoryfor the Set's品類(Walls/Floors/Ceilings). Show candidates and confirm with the user — same as Scenario 2 step 3.Confirm before writing anything: show the full layer stack in the physical order from step 3 —
- Source TypeId
- New type name (ask the user for a naming convention if the Set doesn't imply one — e.g.
TABC_<SetName>for a genuinely combined build) - Each layer, in construction order: material name (
<licno>_<title>, full licno with any suffix) →layerFunction→ thickness - TASK-005.6: for any Wall
Structurelayer whosemappingDetails.wallUsageUnspecifiedistrue, flag it and state the conservative default (150mm) is being used — ask the user to confirm or override. IfwallUsageHintis set, state which wall usage (外牆/分戶牆/輕隔間) was detected and its matrix thickness instead of a bare number. Do not proceed without explicit confirmation.
Create the type: call
create_multi_layer_typewithsourceTypeId,newTypeName, and the confirmedlayersarray (same physical order as steps 3 and 5 — do not reorder by Mat-slot number). Sanity-check the response'sExteriorShellLayers/InteriorShellLayers: if the Set'slayerCompositionhas Finish-role material(s) at one or both ends of the sequence and the response comes back with0shell layers on that side, thelayersarray order was probably wrong — stop and re-check before writing shared parameters.
6b. Floor Finish layer only — apply Surface Pattern (TASK-005.2, e.g. a Floor combining a Finish1 tile layer over a Substrate 打底 layer): for each layer in the response's Layers list whose LayerFunction is Finish1/Finish2 and whose category is Floors, call set_material_surface_pattern with materialId = that layer's MaterialId:
- Tile/stone finish (title contains
磚/石材etc.) →patternType: "Grid"(spacingMm600 default = 600×600 grid; override per product spec if stated). - Wood flooring finish (title contains
木地板/木質地板etc.) →patternType: "Wood". SkipStructure/Substrate/Insulationlayers (e.g. the 打底/緩衝 layer) — no pattern needed there. The tool dedups patterns by name, so reuse across materials/Sets is automatic.
Verify materials exist (mandatory): call
get_all_materials(searchKeyword: "<Set's GBM prefix>")and confirm every material in the response'sLayerslist appears. Auxiliary materials (skipped fromlayersin step 3/6) will not appear here — by design they never get a RevitMaterialelement, only a text record in the Parent Type's Identity Data (step 9) — so don't treat their absence fromget_all_materialsas a failure.Bind shared parameters if needed: call
load_shared_parameterswithcategoriesmatching the Set's品類.Write shared parameters: the schema has 6 slots (
Mat1~`Mat6— seedomain/GM_parameter-schema.md), so slot count normally equals material count (auxiliary materials included — see below); a Set only overflows if it has more than 6 materials total. **Do not decide the slot assignment yourself** — the plan JSON'smaterialSlotAssignmentfield (and eachmaterialsMapping[i].assignedSlot) already contains the deterministic result, computed by_assign_material_slots()inGM_generate_revit_injection_plan.py(priority: Structure > Finish > Substrate > Other, tie-broken by construction order — auxiliary materials fall intoOther, same as any material whose role can't be determined). Readplan['materialSlotAssignment']['assignment']['mat1'..'mat6']for which material goes in each slot, build the correspondingmat1..mat6objects from each material's full record, callset_green_material_type_parameters, and **tell the user explicitly which materials are inplan['materialSlotAssignment']['unassigned']** if any — don't silently drop them. NoteMat3is the one slot with a lighter field shape (no TVOC/Formaldehyde/CNS — seedomain/GM_parameter-schema.md` §1.3); whichever material lands there loses that data even though it still gets a real CompoundStructure layer.- Auxiliary materials (
mappingDetails.isAuxiliary: true) still need amatNobject — Mat1~Mat6 is a manifest of every green material the component uses, not just the ones with a physical layer, so skipping them here would make the component's material inventory incomplete even thoughcreate_multi_layer_typecorrectly left them out of the CompoundStructure (step 3/6). In addition to theirmatNslot, pass the top-leveladhesive/sealant/waterproofingstring parameter (whichever matchesmappingDetails.auxiliaryParam, i.e.GreenMaterial_Adhesive→adhesive,GreenMaterial_Sealant→sealant,GreenMaterial_Waterproofing→waterproofing) using the exact string already computed insharedParameters[mappingDetails.auxiliaryParam](format"產品名稱 (標章編號)") — don't reformat it yourself. A Set can have more than one auxiliary material of different types (e.g. one sealant + one waterproofing); pass each as its own top-level param in the sameset_green_material_type_parameterscall.
- Auxiliary materials (
Verify the written values: call
get_element_infoon the newtypeId.Update the Set's status: call
write_back_to_set_managerwithplanned_actions_overridecontaining'Element ID <id>'plus all material IDs.Report: the full layer stack with material IDs, the new TypeId, which shared-parameter fields were written vs missing, and which materials (if any) exceeded the 6-slot schema.
Scenario 5 — Material 純材料附掛既有 Type (TASK-005.5)
A single non-geometric material (caulk/adhesive/waterproofing — no physical CompoundStructure layer) gets attached to an existing Wall/Floor/Ceiling Type the user picked during /GM_import, either by duplicating that Type (Path A, default, non-destructive) or by overwriting it directly (Path B, mutates every instance of that Type).
Re-anchor the live document: call
get_project_info. Retry once on failure; otherwise stop and report the limitation.Get the material's classification: re-run the match —
python -c " import sys sys.path.insert(0, 'tools/green-material') import GM_generate_revit_injection_plan as g, json plan = g.generate_injection_plan('<SetName>', <items_list_from_json>, '') print(json.dumps(plan, ensure_ascii=False, indent=2)) "There should be exactly one material with
mappingDetails.isAuxiliary: true. ItsmappingDetails.auxiliaryParam(GreenMaterial_Adhesive/Sealant/Waterproofing) tells you which top-levelset_green_material_type_parametersfield to write (adhesive/sealant/waterproofing), andsharedParameters[auxiliaryParam]has the exact"產品名稱 (標章編號)"string already formatted — don't reformat it. If there's more than oneisAuxiliarymaterial or none, stop and clarify with the user — this scenario assumes exactly one.Read the target Type the user already picked: load
exported_material_sets.json, find this Set, readpureMaterialTarget(category,typeId,typeName,instanceCount). If missing, this shouldn't happen given the Scope check above — stop and tell the user to run/GM_importagain.Ask the user: Path A or Path B? Default/recommend Path A. Never pick Path B without an explicit, separate confirmation from the user (per CLAUDE.md's action-care guidance — Path B mutates every existing instance of that Type).
Path A — new Type, existing model untouched (default): a. Propose a new type name (e.g.
<pureMaterialTarget.typeName>_TABC_<licno>) and confirm it with the user. b. Callduplicate_type_only(sourceTypeId: <pureMaterialTarget.typeId>, newTypeName: <confirmed name>)— this only duplicates the Type; it does not touch CompoundStructure or create any Material. Note the returnedNewTypeId. c.load_shared_parameterswithcategories: ["<pureMaterialTarget.category>s"](e.g.["Walls"]),bindToInstance: false— idempotent, safe even if already bound. d.set_green_material_type_parameters(typeId: <NewTypeId>, mat1: <the material's data>, <adhesive|sealant|waterproofing>: <sharedParameters[auxiliaryParam]>). e. Affected scope to report: 0 existing instances — it's a brand-new Type; the originalpureMaterialTarget.typeIdis untouched.Path B — overwrite the existing Type (requires explicit confirmation): a. Show the user
pureMaterialTarget.typeNameandpureMaterialTarget.instanceCount— i.e. exactly how many placed instances in the model will be affected — and get an explicit go-ahead before writing anything. b. Snapshot "old" values first (TASK-005.11): callget_element_info(elementId: <pureMaterialTarget.typeId>)before writing anything and keep whateverGreenMaterial_*fields are currently present (usually none/empty on a Type that's never been tagged). This is the "old value" half of the required change summary — don't skip it, you can't reconstruct it after overwriting. c.load_shared_parameterssame as Path A step c. d.set_green_material_type_parameters(typeId: <pureMaterialTarget.typeId>, mat1: <the material's data>, <adhesive|sealant|waterproofing>: <sharedParameters[auxiliaryParam]>)— writes directly onto the existing Type. e. Affected scope to report:pureMaterialTarget.instanceCountexisting instances now carry this material's data.
Verify the written values: call
get_element_infoon the writtentypeId(new or existing depending on path) and confirm theGreenMaterial_Mat1_*and theGreenMaterial_Adhesive/Sealant/Waterproofingfield match what you intended to write. Path B only: this is also the "new value" half of the change summary — pair it against the "old value" snapshot from step 4b when reporting.Update the Set's status: call
python -c " import sys sys.path.insert(0, 'tools/green-material') import GM_generate_revit_injection_plan as g g.write_back_to_set_manager('<SetName>', plan_dict, planned_actions_override='已建立/覆蓋 Element ID <typeId>(<Path A 新建|Path B 覆蓋>)') "Report: target TypeId + TypeName, which path was taken, which shared-parameter fields were written (including the top-level
adhesive/sealant/waterproofingfield), and the affected element scope (0 for Path A,instanceCountfor Path B). Path B only: also report the old-value → new-value diff for every written field (TASK-005.11) — for a Type that's never been tagged before, "old" is typically "not set" for everyGreenMaterial_*field, which is itself worth stating explicitly rather than omitting.
Every write in this scenario (Path A's duplicate_type_only + set_green_material_type_parameters, or Path B's set_green_material_type_parameters alone) runs inside the C# tool's own single Transaction, so it's atomic and revertible via Revit's normal Undo — no extra transaction-grouping work needed here.
Scenario 7 — Window/Door/loadable-family RFA injection (TASK-005.7)
Read domain/GM_rfa-family-injection.md in full before running this scenario — it defines four hard rules (no-generation-from-scratch, backup-before-any-edit, Identity Data + dedicated shading/acoustic param placement, new-family-name to dodge LoadFamily overwrite ambiguity) that this section only summarizes. This scenario is not entered from a /GM_import-aligned Set — there's no exported_material_sets.json convention for Window/Door yet, so everything here is driven directly by what the user tells you in the conversation.
Re-anchor the live document: call
get_project_info. Retry once on failure; otherwise stop and report the limitation.Get the user to name a base Type — never pick one yourself (domain rule 1): ask which existing Window/Door/loadable-family Type is the closest match to the product being injected. Help them find candidates with
list_family_symbols(filter: "<keyword>")(orget_selected_elementsif they've selected a placed instance in Revit) and show the list — but the final pick is theirs, not an inference from a product spec sheet.Gather the material data: the green material's
name/certNo/category/subCategory/applicant/validUntil/cnsSpec/testItems/qualifiedItems(only includetvoc/formaldehydeif real per-material numbers exist — same rule as every other scenario). If the user gives a 遮陽係數 (shading coefficient) number, that'sshadingCoefficient— Window/Curtain Wall cases only, leave it out entirely for a Door case. If they give a 隔音 Rw number, that'sacousticRw— valid for both Window and Door cases.Confirm before writing anything: show the user —
- Base FamilySymbol (name + ID) they picked in step 2
- New Type name (ask them, or propose
<base type name>_TABC_<licno>) - Backup folder that will be used (default: the project file's folder +
_rfa_backup/) - The material data,
certified: true, and shading/acoustic values about to be written Do not proceed without explicit confirmation — this scenario opens and saves a separate Revit family document, which is a heavier operation than the Type-duplication scenarios above.
Run the injection: call
inject_green_material_into_familywithsourceTypeId,newTypeName,sharedParamFilePath(absolute path toGreenMaterial_SharedParams.txtintools/green-material/),mat1,certified: true, andshadingCoefficient/acousticRwas applicable. Always passcertified: true— a 2026-08-13 fix added this field to the tool (it was previously missing from the Window/Door path only). Known limitation (2026-08-13, confirmed on a real family): this field is best-effort for the RFA path only — on the tested case (雙開落地窗- (2)_TABC_GBM0104092), Revit rejected adding this specific YESNO shared parameter to the family with a genericShared parameter creation failed.error, while every other field (Mat1, AcousticRw) wrote fine. Still passcertified: true(it may succeed on other families, and failure at least surfaces a clear diagnostic), but do not treat a missingGreenMaterial_Certifiedas a failed run — see step 6. This single call covers the whole family-document lifecycle (EditFamily → backup → new Type → write params → SaveAs under a new family name → LoadFamily back into the project) — it can't be split into smaller steps because the family document can't stay open across separate MCP calls.Read the response carefully:
BackupPath— confirm this file path was actually reported; that's the "可復原備份" the domain file requires.MissingParameters— for a Door case,GreenMaterial_Window_ShadingCoefficientlegitimately not existing is expected (you didn't passshadingCoefficient), not a failure.GreenMaterial_Certifiedfailing with theShared parameter creation failed.diagnostic is the known limitation above — report it to the user as "Mat1 data written successfully, Certified could not be added to this family (known Revit-side limitation)", not as a broken run. Anything else missing meansload_shared_parameters-equivalent binding failed inside the family document — report it plainly.SiblingTypesBeforeLoad/SiblingTypesAfterLoad/AffectedExistingTypes— this is the domain rule 4 verification;AffectedExistingTypesshould always be0. If the tool call itself errored with "LoadFamily 會異動非目標 Type,已整批回滾" the whole operation was rolled back automatically — nothing was written, tell the user and stop rather than retrying blindly.- Don't retry with a new
newTypeNamejust becauseCertifiedfailed — retrying reproduces the exact same Revit-side rejection and only leaves behind an extra near-duplicate Type with no Certified either (confirmed by direct testing: a_v2and_v3retry both failed identically). IfCertifiedis missing for this reason, stop after one attempt and report the limitation instead of looping.
Verify: call
get_element_infoon the returnedNewTypeIdand confirm theGreenMaterial_Mat1_*fields (andGreenMaterial_Window_ShadingCoefficient/GreenMaterial_AcousticRwwhere applicable) match what you intended to write. CheckGreenMaterial_Certifiedtoo, but its absence is expected given the known limitation — don't treat it as something to fix by re-running.Report: new Type ID + name, new family file path, backup file path, which parameters were written vs. missing, and confirm 0 existing Types were affected. There is no
exported_material_sets.json/kanban write-back for this scenario yet — say so if the user asks about Set status.
Validation requirement (per domain file and TASK-005.7's acceptance criteria): at least one Window case and one Door case must each go through this full flow before the scenario is considered proven out — don't treat a single Window run as covering Door too, since the Identity Data field set and shading/acoustic applicability genuinely differ between them.
Scenario 8 — Column / Beam 結構材質指派 (2026-08-12)
Columns and structural framing (beams) don't have a CompoundStructure — a Column/Beam Type has exactly one structural material, set via a single Structural Material parameter on the FamilySymbol, not layered like Wall/Floor/Ceiling. 組合方式 (Q1: 單一組合/各別建立) is meaningless here and should be ignored — there's no layering to combine. This scenario writes the material straight onto that one parameter, then tags the new Type with GreenMaterial_Mat1_*.
Re-anchor the live document: call
get_project_info. Retry once on failure; otherwise stop and report the limitation.Get the plan's material(s): re-run the match for this Set —
python -c " import sys sys.path.insert(0, 'tools/green-material') import GM_generate_revit_injection_plan as g, json plan = g.generate_injection_plan('<SetName>', <items_list_from_json>, '') print(json.dumps(plan, ensure_ascii=False, indent=2)) "Every material in
plan['materialsMapping']becomes its own new Type (same one-material-per-Type shape as Scenario 2 — a single structural material parameter can't hold more than one material, so there's no combining even with 2+ materials in the Set).Pick a source Type: call
get_types_by_category(category: "Columns")for品類: Column(this covers both architecturalOST_Columnsand structuralOST_StructuralColumns— show the user which kind each candidate is if it's not obvious from the name) orget_types_by_category(category: "StructuralFraming")for品類: Beam. Show the candidates and confirm a source Type with the user — same as Scenario 2 step 3, don't guess.Confirm before writing anything: show the full list —
- Source TypeId (shared across all materials, or per-material if the user wants different base sections)
- For each material: new Type name = new Material name =
<licno>_<title>(same convention as Scenario 2 — full licno, keep any(續)/(增)suffix) Do not proceed past this point without the user confirming.
Create each Type: for each material, call
duplicate_type_only(sourceTypeId: <source>, newTypeName: <licno>_<title>). This only duplicates the Type (geometry/family parameters untouched) — it does not create or assign any material, unlikeduplicate_element_type/create_single_material_type.Create the material: call
create_material(materialName: <same licno>_<title> string>)— creates a standaloneOST_Materialsentry, noAppearanceAssetElementsharing with anything else.Assign the material to the Structural Material parameter: call
assign_existing_material(typeIds: [<new TypeId from step 5>], materialName: <same string as step 6>). The C# side dispatches on the element's runtime type — for aFamilySymbol(Column/Beam Types areFamilySymbol, notWallType/FloorType/CeilingType) it callsSetStructuralMaterial, writing theStructural Materialparameter directly rather than touching aCompoundStructure. If this reports a non-zeroErrorCountfor a Type, stop for that Type before step 9/10 (don't writeGreenMaterial_*shared parameters onto a Type whose material assignment didn't actually take — see the Error Handling entry onassign_existing_materialfailures below).Verify materials exist (mandatory): call
get_all_materials(searchKeyword: "<Set's GBM prefix>")and confirm every new material from step 6 appears with the IDcreate_materialreturned.Bind shared parameters if needed: call
load_shared_parameterswithcategories: ["Columns"]or["StructuralFraming"](matching the Set's品類),bindToInstance: false. Idempotent — safe even if already bound. Columns binds to bothOST_ColumnsandOST_StructuralColumnsin one call — don't call it twice per category.Write shared parameters per Type: for each new Type, call
set_green_material_type_parameters(typeId: <new TypeId>, certified: true, mat1: <that material's data>)— same field shape as every other scenario (name/certNo/category/subCategory/applicant/validUntil/cnsSpec/testItems/qualifiedItems; only includetvoc/formaldehydeif real per-material numbers exist). Always passcertified: trueexplicitly — Columns/StructuralFraming get the same globalGreenMaterial_Certifiedfield as every other category once step 9's binding covers them, don't skip it just because this scenario's material data is otherwise mat1-only. Onlymat1for the material slot — one material per Type, same as Scenario 2. Report anyMissingParameters; if it's everyGreenMaterial_*field includingCertified, step 9'sload_shared_parameterscall likely targeted the wrong category string.Verify the written values: call
get_element_infoon each newtypeIdand spot-checkGreenMaterial_Certified= Yes and theGreenMaterial_Mat1_*values, plus confirm theStructural Materialparameter (or equivalent material field in the response) now points at the new material from step 6.Update the Set's status: call
write_back_to_set_manager('<SetName>', plan_dict, planned_actions_override='已建立 Element ID <id1>, <id2>, ... 與對應材質').Report: a table of material → new TypeId → new MaterialId, which shared-parameter fields were written vs mis
…(truncated)