mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-27 04:59:55 +08:00
# Description This PR fixes bounded ManagedSession reconnect cleanup and reorganizes the sample applications into domain-focused folders. ## Reconnect cleanup - Bounds reconnect integration cleanup with independent 30-second close and dispose limits. - Sends `CloseSession` through the awaited transport request path during `Session.DisposeAsync`. - Preserves explicit `Session.CloseAsync` behavior that waits for or cancels outstanding publish requests. - Covers the disposal request path with a regression test. ## Sample organization - `samples/OpenUsd`: `GeneratorServer`, `SiteCompositionServer` - `samples/Redundancy`: `RedundantClient`, `RedundantServer`, `RedundantPubSub` - `samples/Isa95`: `MinimalIsa95Server` - `samples/MinimalApi`: `MinimalBoilerServer`, `MinimalCalcServer`, `MinimalClient` - `samples/DI`: `PumpDeviceIntegrationServer` - `samples/Lds`: `ConsoleLdsServer` - `samples/Reference`: `ConsoleReferenceClient`, `ConsoleReferenceServer` - `samples/PubSub`: `ConsoleReferencePubSubClient` `UA.slnx` now exposes the same requested domain groups. Project references, tests, runtime sample probes, CI workflows, Dockerfiles, Compose files, helper scripts, and documentation were updated to the new paths. `samples/Robotics/MinimalRobotServer` remains under `Robotics`. ## Validation - `dotnet build UA.slnx` - 3 Redundancy short-haul sample tests - 70 DI sample tests - 914 OpenUSD tests - 1 WotCon model-materialization test - 137 AOT tests - `docker compose config` for all 6 moved Redundancy Compose files ## Related Issues - Fixes #4273 ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce new build or analyzer warnings. - [x] I built the complete `UA.slnx` solution locally. - [ ] I ran all tests locally against at least .NET Framework and .NET 10. - [ ] I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings. - [x] I have addressed all PR feedback received. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com> Co-authored-by: Marc Schier <mschier@microsoft.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Copilot-Session: a2c2838f-74ae-4997-a1d4-50574d9e0636
138 lines
4.6 KiB
Python
138 lines
4.6 KiB
Python
"""Regenerates the referenced pump asset from the P101 master in Plant.usda.
|
|
|
|
pump.usda is the component asset the server references once per configured
|
|
pump. It must carry the same geometry as /Plant/Pumps/P101 so every pump in the
|
|
hall renders at full fidelity, but it has to be self-contained: material paths
|
|
are rewritten to a local Looks scope.
|
|
|
|
Run from the repository root:
|
|
python samples/DI/PumpDeviceIntegrationServer/Assets/generate_pump_assets.py
|
|
"""
|
|
|
|
import pathlib
|
|
import re
|
|
|
|
HERE = pathlib.Path(__file__).parent
|
|
PLANT = HERE / "Plant.usda"
|
|
|
|
# The P101 children that make up the machine itself, including the indications.
|
|
# Every configured pump is composed from this asset and driven by its own
|
|
# bindings, so each machine carries its own gauges, suction vessel and alarm
|
|
# ring rather than sharing one plant-side set.
|
|
WANTED = [
|
|
"Baseplate",
|
|
"Body",
|
|
"Casing",
|
|
"Suction",
|
|
"SuctionVessel",
|
|
"Discharge",
|
|
"PowerEnd",
|
|
"Bearing",
|
|
"OverheatHalo",
|
|
"Impeller",
|
|
"CouplingGuard",
|
|
"Motor",
|
|
"AlarmRing",
|
|
]
|
|
|
|
def block(lines, start):
|
|
"""Returns the line range of a brace-delimited prim block starting at `start`."""
|
|
depth = 0
|
|
index = start
|
|
seen = False
|
|
while index < len(lines):
|
|
depth += lines[index].count("{") - lines[index].count("}")
|
|
if "{" in lines[index]:
|
|
seen = True
|
|
if seen and depth == 0:
|
|
return start, index + 1
|
|
index += 1
|
|
raise ValueError(f"unterminated block at line {start}")
|
|
|
|
|
|
def extract(text, name, indent):
|
|
"""Extracts the named prim block at the given indent, dedented to column 4."""
|
|
lines = text.splitlines()
|
|
pattern = re.compile(rf'^{" " * indent}def \w+ "{re.escape(name)}"')
|
|
for index, line in enumerate(lines):
|
|
if pattern.match(line):
|
|
first, last = block(lines, index)
|
|
body = lines[first:last]
|
|
trim = indent - 4
|
|
return "\n".join(line[trim:] if line.startswith(" " * trim) else line
|
|
for line in body)
|
|
raise ValueError(f"prim {name!r} not found at indent {indent}")
|
|
|
|
|
|
def looks(text):
|
|
"""Extracts the shared Looks scope and rehomes it under /Pump."""
|
|
scope = extract(text, "Looks", 4)
|
|
return scope.replace("</Plant/Looks/", "</Pump/Looks/")
|
|
|
|
|
|
def rehome(text):
|
|
text = text.replace("</Plant/Looks/", "</Pump/Looks/")
|
|
text = text.replace("</Plant/Pumps/P101/", "</Pump/")
|
|
return text
|
|
|
|
|
|
def build(plant, doc):
|
|
parts = [looks(plant)]
|
|
parts += [extract(plant, name, 12) for name in WANTED]
|
|
body = rehome("\n\n".join(parts))
|
|
return f'''#usda 1.0
|
|
(
|
|
doc = """{doc}"""
|
|
defaultPrim = "Pump"
|
|
metersPerUnit = 1
|
|
upAxis = "Z"
|
|
)
|
|
|
|
def Xform "Pump" (
|
|
kind = "component"
|
|
)
|
|
{{
|
|
matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
|
|
uniform token[] xformOpOrder = ["xformOp:transform"]
|
|
|
|
{body}
|
|
}}
|
|
'''
|
|
|
|
|
|
PUMP_DOC = """Reusable single-pump USD asset for the OPC UA - OpenUSD Bindings
|
|
pump twin. The server composes one referenced </Pump> prim per configured
|
|
pump (--pumps N), so the rendered hall holds exactly the pumps the connected
|
|
server simulates - nothing else (spec 5.12, CompositionArc = Reference).
|
|
|
|
Generated by generate_pump_assets.py from the /Plant/Pumps/P101 master in
|
|
Plant.usda, so every composed pump renders at the same fidelity: an EN 733
|
|
size 65-200 end-suction centrifugal pump (Grundfos NK / KSB Etanorm
|
|
proportions) driven by an IEC 160M motor on a 1.80 x 0.46 m baseplate, shaft
|
|
centreline 0.160 m above the baseplate. Do not edit by hand - edit P101 and
|
|
re-run the generator.
|
|
|
|
The asset carries its own instrumentation and indications - pressure and
|
|
bearing-temperature gauges, suction vessel, cooling fan, the alarm ring and
|
|
the two fault halos - because each composed pump is driven by its own
|
|
bindings and has to show its own state.
|
|
|
|
The root prim declares an identity xformOp:transform and lists it in
|
|
xformOpOrder because that is what makes the bay position binding work. A
|
|
connector resolves a Translation render target into a single matrix op, and
|
|
USD only evaluates ops named in xformOpOrder - a list that is uniform, so the
|
|
connector cannot add itself to it from a stronger layer. If the asset omits
|
|
the op, the bay position is silently discarded, every composed pump renders
|
|
on the origin, and the hall looks like it holds a single machine."""
|
|
|
|
|
|
def main():
|
|
plant = PLANT.read_text(encoding="utf-8")
|
|
(HERE / "pump.usda").write_text(
|
|
build(plant, PUMP_DOC), encoding="utf-8", newline="\r\n")
|
|
print("wrote pump.usda")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|