Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions .github/workflows/validate_xml_with_xsd.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,65 @@
# .github/workflows/validate_xml_with_xsd.yml
name: Validate PVCollada XML examples

on:
push:
paths:
- schema/pvcollada_schema_0.1.xsd
- schema/collada_schema_1_5.xsd
- Examples/**
- schema/**
- .github/workflows/validate_xml_with_xsd.py
- .github/workflows/validate_xml_with_xsd.yml

- Examples/05 - VerySimpleFixedPVC2_with_electrical_layout_v3.pvc
pull_request:
paths:
- schema/pvcollada_schema_0.1.xsd
- schema/collada_schema_1_5.xsd
- Examples/**
- schema/**
- .github/workflows/validate_xml_with_xsd.py
- .github/workflows/validate_xml_with_xsd.yml

- Examples/05 - VerySimpleFixedPVC2_with_electrical_layout_v3.pvc
workflow_dispatch:

jobs:
discover:
name: Discover *.pv2 example files
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.build.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: build
shell: bash
run: |
python3 - <<'PY' > matrix.json
import json, re, pathlib
root = pathlib.Path("Examples")
pattern = re.compile(r".+\.pv2$", re.IGNORECASE)
files = []
if root.exists():
for p in sorted(root.iterdir()):
if p.is_file() and pattern.match(p.name):
files.append(f"{root}/{p.name}")
print(json.dumps({"xml_doc": files}))
PY
echo "matrix=$(cat matrix.json)" >> "$GITHUB_OUTPUT"

validate-schema:
strategy:
matrix:
xml_doc:
- Examples/05 - VerySimpleFixedPVC2_with_electrical_layout_v3.pvc
name: Validate XML
needs: discover
runs-on: ubuntu-latest
# Skip job cleanly if no files matched (prevents a matrix on an empty list)
if: ${{ fromJSON(needs.discover.outputs.matrix).xml_doc && join(fromJSON(needs.discover.outputs.matrix).xml_doc) != '' }}
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.discover.outputs.matrix) }}
steps:
- name: Install Python 3.10
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install lxml Python dependency
run: pip3 install lxml==5.3.1

- name: Checkout PVCollada and COLLADA schemas, the validation script, and the XML doc
uses: actions/checkout@v4
with:
Expand All @@ -43,6 +69,10 @@ jobs:
.github/workflows/validate_xml_with_xsd.py
${{ matrix.xml_doc }}
sparse-checkout-cone-mode: false

- name: Run validation
working-directory: schema
run: python3 "$GITHUB_WORKSPACE"/.github/workflows/validate_xml_with_xsd.py pvcollada_schema_0.1.xsd "$GITHUB_WORKSPACE"/"${{ matrix.xml_doc }}"
run: |
python3 "$GITHUB_WORKSPACE"/.github/workflows/validate_xml_with_xsd.py \
pvcollada_schema_0.1.xsd \
"$GITHUB_WORKSPACE"/"${{ matrix.xml_doc }}"
256 changes: 256 additions & 0 deletions Examples/01 - VerySimpleFixedPVC2_without_electrical_layout.pv2
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
<?xml version="1.0" encoding="utf-8"?>
<COLLADA xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:pv="http://www.example.com/pvcollada"
version="1.5.0" xmlns="http://www.collada.org/2008/03/COLLADASchema">
<asset>
<contributor>
<author>PVsyst</author>
<comments>First PVC 2.0 sample file : 1 terrain and 3 tables, all based on the same model. No electrical components</comments>
</contributor>
<coverage>
<geographic_location>
<longitude>15.3</longitude>
<latitude>10.1</latitude>
<altitude mode="absolute">150</altitude>
</geographic_location>
</coverage>
<created>2024-06-06T13:32:31Z</created>
<modified>2025-09-16T10:12:18Z</modified>
<unit meter="0.01" name="cm" />
<up_axis>Z_UP</up_axis>
<extra>
<technique profile="PVCollada-2.0">
<!-- software tag -->
<pv:software>
<pv:source>Helios 3D</pv:source>
<pv:target>PVsyst</pv:target>
</pv:software>
<!-- project tag -->
<pv:project>
<pv:name>First example</pv:name>
</pv:project>
<!-- components tag -->
<pv:components>
<!-- List of modules -->
<pv:modules>
<!-- module tag, would usually contain a link to a PAN file or its content
as CDATA -->
<pv:module id="ModuleModel1">
<pv:manufacturer>Generic</pv:manufacturer>
<pv:name>Generic 300W bifacial</pv:name>
<pv:module_type>bifacial</pv:module_type>
<pv:nom_power>300</pv:nom_power>
<pv:length>1992</pv:length>
<pv:width>1000</pv:width>
<pv:depth>6</pv:depth>
</pv:module>
</pv:modules>
</pv:components>
</technique>
</extra>
</asset>
<library_materials>
<material id="Material1" name="Dummy Material">
<instance_effect url="#Material1EffectId" />
</material>
</library_materials>
<library_effects>
<effect id="Material1EffectId" name="Dummy Material Effect">
<profile_COMMON>
<technique sid="standard">
<lambert>
<emission>
<color sid="emission">0.000000 0.000000 0.000000 1.000000</color>
</emission>
<ambient>
<color sid="ambient">0.200000 0.200000 0.200000 1.000000</color>
</ambient>
<diffuse>
<color sid="diffuse">0.250000 0.500000 0.000000 1.000000</color>
</diffuse>
<transparent opaque="RGB_ZERO">
<color sid="transparent">0.000000 0.000000 0.000000 1.000000</color>
</transparent>
<transparency>
<float sid="transparency">0</float>
</transparency>
</lambert>
</technique>
</profile_COMMON>
</effect>
</library_effects>
<library_geometries>
<!-- We define a terrain -->
<geometry id="Terrain1">
<mesh>
<source id="terrain1MeshSource">
<float_array id="terrain1FloatArray" count="12">
2000 -2500 0
-2000 -2500 0
-2000 2500 0
2000 2500 0
</float_array>
<technique_common>
<accessor count="4" source="#terrain1FloatArray" stride="3">
<param name="X" type="float" />
<param name="Y" type="float" />
<param name="Z" type="float" />
</accessor>
</technique_common>
</source>
<vertices id="terrain1VerticesSource">
<input semantic="POSITION" source="#terrain1MeshSource" />
</vertices>
<polylist material="CommonMaterial" count="1">
<input offset="0" semantic="VERTEX" source="#terrain1VerticesSource" />
<vcount>5</vcount>
<p>0 1 2 3 0</p>
</polylist>
</mesh>
<extra>
<technique profile="PVCollada-2.0">
<!-- terrain tag -->
<pv:terrain sid="Terrain">
<pv:vertex_count>4</pv:vertex_count>
<pv:triangle_count>2</pv:triangle_count>
<pv:bounding_box_min>-10.0 -20.0 -30.0</pv:bounding_box_min>
<pv:bounding_box_max>10.0 20.0 30.0</pv:bounding_box_max>
</pv:terrain>
</technique>
</extra>
</geometry>
<!-- We define the model for a rack -->
<geometry id="Rack1Model">
<mesh>
<source id="rack1MeshSource">
<float_array id="rack1FloatArray" count="12">
0 0 0
-1500 0 0
-1500 -500 200
0 -500 200
</float_array>
<technique_common>
<accessor count="4" source="#rack1FloatArray" stride="3">
<param name="X" type="float" />
<param name="Y" type="float" />
<param name="Z" type="float" />
</accessor>
</technique_common>
</source>
<vertices id="rack1VerticesSource">
<input semantic="POSITION" source="#rack1MeshSource" />
</vertices>
<triangles count="2" material="CommonMaterial">
<input offset="0" semantic="VERTEX" source="#rack1VerticesSource" />
<p>3 1 2 1 3 0</p>
</triangles>
</mesh>
<extra>
<technique profile="PVCollada-2.0">
<!-- rack tag -->
<pv:rack>
<pv:rack_type>fixed_tilt</pv:rack_type>
<pv:module_rows>1</pv:module_rows>
<pv:module_columns>4</pv:module_columns>
<pv:tilt>0</pv:tilt>
<pv:azimuth>0</pv:azimuth>
<pv:module_id>ModuleModel1</pv:module_id>
</pv:rack>
</technique>
</extra>
</geometry>
</library_geometries>
<library_nodes>
<!-- We define the model for a table with 1 rack -->
<node id="TableModel1">
<!-- We instantiate the rack model -->
<instance_geometry url="#Rack1Model">
<bind_material>
<technique_common>
<instance_material symbol="CommonMaterial" target="#Material1" />
</technique_common>
</bind_material>
<extra>
<technique profile="PVCollada-2.0">
<!-- instance_rack tag -->
<pv:instance_rack id="rackInstance1" />
</technique>
</extra>
</instance_geometry>
<extra>
<technique profile="PVCollada-2.0">
<!-- table tag -->
<pv:table>
<pv:type>fixed</pv:type>
</pv:table>
</technique>
</extra>
</node>
</library_nodes>
<library_visual_scenes>
<visual_scene id="VisualSceneNode" name="Visual Scene">
<!-- We instantiate the terrain -->
<node id="TerrainNode1" name="TerrainNode1" sid="TerrainNode1">
<instance_geometry url="#Terrain1">
<extra>
<technique profile="PVCollada-2.0">
<!-- instance_terrain tag, might not have any child tags at the start but can be useful in the future -->
<pv:instance_terrain />
</technique>
</extra>
</instance_geometry>
</node>
<!-- We instantiate a first fixed tilt table, without any transformation -->
<node id="PVnode1" name="PVnode1" sid="PVnode1">
<instance_node url="#TableModel1">
<extra>
<technique profile="PVCollada-2.0">
<!-- instance_table tag -->
<pv:instance_table>
<pv:instance_racks_array>
<pv:instance_rack_ref id="rackId1" url="#rackInstance1" />
</pv:instance_racks_array>
</pv:instance_table>
</technique>
</extra>
</instance_node>
</node>
<!-- We instantiate a second fixed tilt table (with a translation) -->
<node id="PVnode2" name="PVnode2" sid="PVnode2">
<translate>0 -800 0</translate>
<instance_node url="#TableModel1">
<extra>
<technique profile="PVCollada-2.0">
<!-- instance_table tag -->
<pv:instance_table>
<pv:instance_racks_array>
<pv:instance_rack_ref id="rackId2" url="#rackInstance1" />
</pv:instance_racks_array>
</pv:instance_table>
</technique>
</extra>
</instance_node>
</node>
<!-- We instantiate a third fixed tilt table (with another translation) -->
<node id="PVnode3" name="PVnode3" sid="PVnode3">
<translate>0 -1600 0</translate>
<instance_node url="#TableModel1">
<extra>
<technique profile="PVCollada-2.0">
<!-- instance_table tag -->
<pv:instance_table>
<pv:instance_racks_array>
<pv:instance_rack_ref id="rackId3" url="#rackInstance1" />
</pv:instance_racks_array>
</pv:instance_table>
</technique>
</extra>
</instance_node>
</node>
</visual_scene>
</library_visual_scenes>
<scene>
<instance_visual_scene url="#VisualSceneNode" />
</scene>
</COLLADA>
Loading