diff --git a/README.md b/README.md index 921cb995..cec5cf30 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The data formats, examples, and available web service interfaces will all change | Schema version | Java | SPIDAcalc | SPIDAstudio | |----------------------------------------------------------------------------------|------|-------------------------|-------------| +| [12.0.1](https://github.com/spidasoftware/schema/releases/tag/v12.0.1) | 11+ | SPIDAcalc 25.0.0 | | | [11.0.3](https://github.com/spidasoftware/schema/releases/tag/v11.0.3) | 11+ | SPIDAcalc 24.1.1 - 24.1.2 | | | [11.0.2](https://github.com/spidasoftware/schema/releases/tag/v11.0.2) | 11+ | SPIDAcalc 24.1.0 | | | [10.0.1](https://github.com/spidasoftware/schema/releases/tag/v10.0.1) | 11+ | SPIDAcalc 24.0.0 | | diff --git a/build.gradle b/build.gradle index 4a91a73e..2fc4f46a 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ apply plugin: 'com.spidasoftware.releaseNotes' apply plugin: 'application' group = 'com.spidasoftware' -version = '12.0.2' // If you're changing the major version also change the currentVersion in ConverterUtils. +version = '13.0.0-SNAPSHOT' // If you're changing the major version also change the currentVersion in ConverterUtils. def schemaReleaseVersion = System.getenv("SCHEMA_RELEASE_VERSION") if(schemaReleaseVersion){ diff --git a/resources/schema/spidacalc/calc/structure_group.schema b/resources/schema/spidacalc/calc/structure_group.schema new file mode 100644 index 00000000..db90c7db --- /dev/null +++ b/resources/schema/spidacalc/calc/structure_group.schema @@ -0,0 +1,57 @@ +{ + "type": "object", + "id": "#/spidacalc/calc/structure_group.schema", + "description": "A mapping of structures to analyze", + "required": [ + "primaryStructureId", + "primaryStructure", + "neighbors", + "spanningWireConnections" + ], + "properties": { + "primaryStructureId": { + "type": "string", + "description": "The ID of the primary structures parent" + }, + "primaryStructure": { + "description": "The primary structure to analyze", + "$ref": "../../spidacalc/calc/structure.schema" + }, + "neighbors": { + "description": "List of neighbor structures to include in analysis finite model", + "type": "array", + "items": { + "$ref": "../../spidacalc/calc/structure.schema" + } + }, + "spanningWireConnections": { + "description": "Attach height of the guy attach point.", + "type": "array", + "items": { + "description": "A mapping of (sourceStructureId, sourceWireId) -> (targetStructureId, targetWireId) for spanning wire connections", + "type": "object", + "required": [ + "sourceStructureId", + "sourceWireId", + "targetStructureId", + "targetWireId" + ], + "properties": { + "sourceStructureId": { + "type": "string" + }, + "sourceWireId": { + "type": "string" + }, + "targetStructureId": { + "type": "string" + }, + "targetWireId": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false +} diff --git a/resources/schema/spidacalc/cee/analysis.schema b/resources/schema/spidacalc/cee/analysis.schema index 93c72e11..ac5267ff 100644 --- a/resources/schema/spidacalc/cee/analysis.schema +++ b/resources/schema/spidacalc/cee/analysis.schema @@ -3,7 +3,7 @@ "description": "SPIDA CEE analysis object. The analysis objects are validated by the specific version of the engine that specified.", "type": "object", "required": [ - "structure", + "structureGroup", "analysisCase", "clientData" ], @@ -23,9 +23,9 @@ {"$ref": "../../spidacalc/client/strength_case.schema"} ] }, - "structure": { - "description": "The structure to be analyzed.", - "$ref": "../../spidacalc/calc/structure.schema" + "structureGroup": { + "description": "The structure group to be analyzed. Includes primary structure and optional neighbor structures.", + "$ref": "../../spidacalc/calc/structure_group.schema" }, "clientData": { "description": "The engineering data to be used to fill in the references on the structure.", diff --git a/resources/schema/spidacalc/client/load_case.schema b/resources/schema/spidacalc/client/load_case.schema index 78012155..47d2d2ea 100644 --- a/resources/schema/spidacalc/client/load_case.schema +++ b/resources/schema/spidacalc/client/load_case.schema @@ -41,6 +41,9 @@ "ALL" ] }, + "includeNeighborStructures": { + "type": "boolean" + }, "analyzeGroundLineOnly": { "type": "boolean" }, diff --git a/src/main/groovy/com/spidasoftware/schema/conversion/changeset/ConverterUtils.groovy b/src/main/groovy/com/spidasoftware/schema/conversion/changeset/ConverterUtils.groovy index d0d87677..a5f8e522 100644 --- a/src/main/groovy/com/spidasoftware/schema/conversion/changeset/ConverterUtils.groovy +++ b/src/main/groovy/com/spidasoftware/schema/conversion/changeset/ConverterUtils.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Bentley Systems, Incorporated. All rights reserved. + * Copyright (c) 2026 Bentley Systems, Incorporated. All rights reserved. */ package com.spidasoftware.schema.conversion.changeset @@ -16,13 +16,14 @@ import com.spidasoftware.schema.conversion.changeset.v9.* import com.spidasoftware.schema.conversion.changeset.v10.* import com.spidasoftware.schema.conversion.changeset.v11.* import com.spidasoftware.schema.conversion.changeset.v12.* +import com.spidasoftware.schema.conversion.changeset.v13.* import groovy.util.logging.Slf4j import org.apache.commons.lang3.StringUtils @Slf4j class ConverterUtils { - static final int currentVersion = 12 + static final int currentVersion = 13 static { addCalcConverter(new CalcProjectConverter()) @@ -89,6 +90,7 @@ class ConverterUtils { converter.addChangeSet(12, new ComponentBraceChangeset()) converter.addChangeSet(12, new SidedPoleChangeset()) converter.addChangeSet(12, new CSAMaxWindLoadFactorsChangeSet()) + converter.addChangeSet(13, new LeadAnalysisChangeSet()) // add calc changesets above here converter.setCurrentVersion(currentVersion) @@ -118,6 +120,7 @@ class ConverterUtils { converter.addChangeSet(12, new ComponentBraceChangeset()) converter.addChangeSet(12, new SidedPoleChangeset()) converter.addChangeSet(12, new CSAMaxWindLoadFactorsChangeSet()) + converter.addChangeSet(13, new LeadAnalysisChangeSet()) // add client data changesets above here converter.setCurrentVersion(currentVersion) @@ -148,6 +151,7 @@ class ConverterUtils { converter.addChangeSet(12, new ComponentBraceChangeset()) converter.addChangeSet(12, new SidedPoleChangeset()) converter.addChangeSet(12, new CSAMaxWindLoadFactorsChangeSet()) + converter.addChangeSet(13, new LeadAnalysisChangeSet()) // add result changesets above here converter.setCurrentVersion(currentVersion) @@ -185,6 +189,8 @@ class ConverterUtils { return null } switch (engineVersion) { + case 26.0: + return 13 case 25.0: return 12 case 24.1: diff --git a/src/main/groovy/com/spidasoftware/schema/conversion/changeset/v13/LeadAnalysisChangeSet.groovy b/src/main/groovy/com/spidasoftware/schema/conversion/changeset/v13/LeadAnalysisChangeSet.groovy new file mode 100644 index 00000000..43260bbd --- /dev/null +++ b/src/main/groovy/com/spidasoftware/schema/conversion/changeset/v13/LeadAnalysisChangeSet.groovy @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026 Bentley Systems, Incorporated. All rights reserved. + */ +package com.spidasoftware.schema.conversion.changeset.v13 + +import com.spidasoftware.schema.conversion.changeset.ConversionException +import com.spidasoftware.schema.conversion.changeset.client.AbstractClientDataChangeSet +import groovy.transform.CompileStatic + +@CompileStatic +class LeadAnalysisChangeSet extends AbstractClientDataChangeSet { + + @Override + boolean applyToClientData(Map clientDataJSON) throws ConversionException { + // do nothing + return false + } + + @Override + boolean revertClientData(Map clientDataJSON) throws ConversionException { + boolean changed = false + clientDataJSON.analysisCases?.each { Map analysisCase -> + changed |= (analysisCase.remove("includeNeighborStructures") != null) + } + return changed + } + + @Override + void revertProject(Map projectJSON) throws ConversionException { + super.revertProject(projectJSON) + projectJSON.defaultLoadCases?.each { Map loadCase -> + loadCase.remove("includeNeighborStructures") + } + } + + @Override + void revertDesign(Map designJSON) throws ConversionException { + super.revertDesign(designJSON) + designJSON.analysis?.each { Map analysis -> + Map analysisCase = analysis.analysisCaseDetails as Map + analysisCase?.remove("includeNeighborStructures") + } + } + + @Override + boolean revertResults(Map resultsJSON) throws ConversionException { + boolean changed = super.revertResults(resultsJSON) + resultsJSON.results?.each { Map result -> + Map analysisCase = result.analysisCaseDetails as Map + if (analysisCase) { + changed |= (analysisCase.remove("includeNeighborStructures") != null) + } + } + return changed + } +} diff --git a/src/test/groovy/com/spidasoftware/schema/conversion/changeset/ConverterUtilsSpec.groovy b/src/test/groovy/com/spidasoftware/schema/conversion/changeset/ConverterUtilsSpec.groovy index 362881b1..8100badc 100644 --- a/src/test/groovy/com/spidasoftware/schema/conversion/changeset/ConverterUtilsSpec.groovy +++ b/src/test/groovy/com/spidasoftware/schema/conversion/changeset/ConverterUtilsSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025 Bentley Systems, Incorporated. All rights reserved. + * Copyright (c) 2026 Bentley Systems, Incorporated. All rights reserved. */ package com.spidasoftware.schema.conversion.changeset @@ -60,7 +60,7 @@ class ConverterUtilsSpec extends Specification { def "test getPossibleVersionsNewestToOldest"() { expect: - ConverterUtils.getPossibleVersionsNewestToOldest() == [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2] as LinkedHashSet + ConverterUtils.getPossibleVersionsNewestToOldest() == [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2] as LinkedHashSet } def "pole-lean validation"() { diff --git a/src/test/groovy/com/spidasoftware/schema/conversion/changeset/v13/LeadAnalysisChangeSetTest.groovy b/src/test/groovy/com/spidasoftware/schema/conversion/changeset/v13/LeadAnalysisChangeSetTest.groovy new file mode 100644 index 00000000..a3deb5e6 --- /dev/null +++ b/src/test/groovy/com/spidasoftware/schema/conversion/changeset/v13/LeadAnalysisChangeSetTest.groovy @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026 Bentley Systems, Incorporated. All rights reserved. + */ +package com.spidasoftware.schema.conversion.changeset.v13 + +import groovy.json.JsonSlurper +import spock.lang.Specification + +class LeadAnalysisChangeSetTest extends Specification { + + static LeadAnalysisChangeSet changeSet + + def setupSpec() { + changeSet = new LeadAnalysisChangeSet() + } + + def "revert client data"() { + setup: + InputStream stream = LeadAnalysisChangeSetTest.getResourceAsStream("/conversions/v13/leadAnalysis-v13-project.json") + Map json = new JsonSlurper().parse(stream) as Map + stream.close() + expect: + json.clientData.analysisCases[0].includeNeighborStructures == false + json.clientData.analysisCases[1].includeNeighborStructures == true + when: + boolean reverted = changeSet.revertClientData(json.clientData as Map) + then: + reverted + json.clientData.analysisCases[0].includeNeighborStructures == null + json.clientData.analysisCases[1].includeNeighborStructures == null + } + + def "revert project"() { + setup: + InputStream stream = LeadAnalysisChangeSetTest.getResourceAsStream("/conversions/v13/leadAnalysis-v13-project.json") + Map json = new JsonSlurper().parse(stream) as Map + stream.close() + expect: + json.clientData.analysisCases[0].includeNeighborStructures == false + json.clientData.analysisCases[1].includeNeighborStructures == true + json.defaultLoadCases[0].includeNeighborStructures == false + json.defaultLoadCases[1].includeNeighborStructures == true + json.leads[0].locations[0].designs[0].analysis[0].analysisCaseDetails.includeNeighborStructures == false + json.leads[0].locations[0].designs[0].analysis[1].analysisCaseDetails.includeNeighborStructures == true + when: + changeSet.revertProject(json) + then: + json.clientData.analysisCases[0].includeNeighborStructures == null + json.clientData.analysisCases[1].includeNeighborStructures == null + json.defaultLoadCases[0].includeNeighborStructures == null + json.defaultLoadCases[1].includeNeighborStructures == null + json.leads[0].locations[0].designs[0].analysis[0].analysisCaseDetails.includeNeighborStructures == null + json.leads[0].locations[0].designs[0].analysis[1].analysisCaseDetails.includeNeighborStructures == null + } + + def "revert results"() { + setup: + InputStream stream = LeadAnalysisChangeSetTest.getResourceAsStream("/conversions/v13/leadAnalysis-v13-results.json") + Map json = new JsonSlurper().parse(stream) as Map + stream.close() + expect: + json.clientData.analysisCases[0].includeNeighborStructures == false + json.clientData.analysisCases[1].includeNeighborStructures == true + json.results[0].analysisCaseDetails.includeNeighborStructures == false + json.results[1].analysisCaseDetails.includeNeighborStructures == true + when: + boolean reverted = changeSet.revertResults(json) + then: + reverted + json.clientData.analysisCases[0].includeNeighborStructures == null + json.clientData.analysisCases[1].includeNeighborStructures == null + json.results[0].analysisCaseDetails.includeNeighborStructures == null + json.results[1].analysisCaseDetails.includeNeighborStructures == null + } +} diff --git a/src/test/resources/conversions/v13/leadAnalysis-v13-project.json b/src/test/resources/conversions/v13/leadAnalysis-v13-project.json new file mode 100644 index 00000000..6a0443b0 --- /dev/null +++ b/src/test/resources/conversions/v13/leadAnalysis-v13-project.json @@ -0,0 +1,89 @@ +{ + "label": "New Project", + "clientFile": "Demo.client", + "schema": "/schema/spidacalc/calc/project.schema", + "version": 13, + "clientData": { + "schema": "/schema/spidacalc/client/data.schema", + "version": 13, + "name": "Demo.client", + "analysisCases": [ + { + "name": "CSA Max Wind (don't include neighbor structures)", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": false + }, + { + "name": "CSA Max Wind (include neighbor structures)", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": true + } + ], + "hash": "42f9cd1777461fd888783a021847a43b" + }, + "clientFileVersion": "42f9cd1777461fd888783a021847a43b", + "defaultLoadCases": [ + { + "name": "CSA Max Wind (don't include neighbor structures)", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": false + }, + { + "name": "CSA Max Wind (include neighbor structures)", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": true + } + ], + "leads": [ + { + "label": "Lead", + "locations": [ + { + "label": "Location-10-02-2024-13-07-40-393", + "designs": [ + { + "label": "Measured Design", + "layerType": "Measured", + "analysis": [ + { + "id": "CSA Max Wind (don't include neighbor structures)", + "analysisCaseDetails": { + "name": "CSA Max Wind (don't include neighbor structures)", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": false + } + }, + { + "id": "CSA Max Wind (include neighbor structures)", + "analysisCaseDetails": { + "name": "CSA Max Wind (include neighbor structures)", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": true + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/resources/conversions/v13/leadAnalysis-v13-results.json b/src/test/resources/conversions/v13/leadAnalysis-v13-results.json new file mode 100644 index 00000000..6731802b --- /dev/null +++ b/src/test/resources/conversions/v13/leadAnalysis-v13-results.json @@ -0,0 +1,57 @@ +{ + "results": [ + { + "analysisCase": "CSA Max Wind (don't include neighbor structures)", + "analysisCaseVersion": "b90d0a5dc19ec1e2614385e005c287cd", + "analysisDate": 1727896441703, + "engineVersion": "24.1.0.99999", + "analysisCaseDetails": { + "name": "old csa max wind", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": false + } + }, + { + "analysisCase": "CSA Max Wind (include neighbor structures)", + "analysisCaseVersion": "b90d0a5dc19ec1e2614385e005c287cd", + "analysisDate": 1727896441703, + "engineVersion": "24.1.0.99999", + "analysisCaseDetails": { + "name": "old csa max wind", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": true + } + } + ], + "id": "66fd9b782cec05af05a684a1", + "clientData": { + "schema": "/schema/spidacalc/client/data.schema", + "version": 13, + "name": "Demo.client", + "analysisCases": [ + { + "name": "CSA Max Wind (don't include neighbor structures)", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": false + }, + { + "name": "CSA Max Wind (include neighbor structures)", + "type": "CSA 2020 Maximum Wind", + "analysisMethod": "CO_ROTATIONAL", + "connectionType": "CONTINUATION", + "displacementBasedLoadChangesLevel": "PARTIAL", + "includeNeighborStructures": true + } + ], + "hash": "42f9cd1777461fd888783a021847a43b" + } +}