MODULAR SIMOBJECT MERGING
Modular aircraft provide a way to have multiple components and merge them together, with a hierarchy of component files that can override each other based on several factors to create a final aircraft. This merging is done in two steps:
-
On Game Start
The first merge happens when the aircraft package is mounted to the VFS on game start, so that the aircraft variations can be listed correctly in the menus. This merging takes place in the following way:
- The Common folder is read as the "base" of the aircraft, onto which other things will be attached/merged.
- Each of the Presets for the aircraft is checked and the information in the
aircraft.cfg
is used to tell the simulation which variation of the aircraft each preset represents. - The
attached_object.cfg
for each preset is parsed and an internal list of available attachments is created. - The various configuration (CFG) files from the Common, Attachments and Presets are merged.
- Finally the different Liveries are parsed and assigned as available to the appropriate presets.
Once this has happened, each variation is ready to be shown in the user menu for selection, and for each aircraft, the available liveries will be shown in the liveries panel.
-
On Flight Start
The second merge happens when a flight starts. At this moment, the aircraft itself is generated as follows:
- The attachment models required by the preset are added to the common model.
- The livery is applied.
- XML, FLT, etc... files from the common, attachments, and presets are merged together.
With all that done, the final aircraft is ready to fly.
Merge Rules
When it comes to the merging of the different configuration files, the following rules will be applied:
- A version of the file must exist in both the Common and Preset/Attachment folders for a merge to occur. If there is no Preset file, the Common one will be used, and if there is no Common file, the preset one will be used. NOTE: Technically a merge will always occur, since - if there is a file missing from the Common folder - an "empty" file will be auto-generated and merged with subsequent versions of the file from the Attachments and/or Presets folders.
- To merge a specific file, components of this file must share the same path from their respective root (see the schema below).
- To be considered as an "overrider", an Attachment must have a unique alias in the
attached_objects.cfg
file. This alias will be used as an identifier each time something needs to be merged.
- If a CFG file parameter is considered as required then this must be present in the final merged files (and as such, int he SimObject editor any required parameter that is not defined will be flagged in red in the associated presets).
Root Folder Schema
+ Project_Name |---+ attachments | |---+ My_Company | |---+ Function_MyAttach // This is the root of the SimAttachment "Function_My_Attachment" | |---+ config | |---- aircraft.cfg | |---+ common // This is the root of the Common part | |---+ config | |---- aircraft.cfg | |---- systems.cfg | |---+ presets |---+ Company_Name |---+ My_Preset // This is the root of Preset "My_Preset" |---+ config |--- aircraft.cfg // Merge = Common + Function_My_Attachment + My_Preset |--- flight_model.cfg |--- systems.cfg // Merge = Common + My_Preset
CFG Merge Examples
In this example we'll go through the merge process for CFG (and FLT) files. The example will use a schematic approach just to illustrate how the final files are created through the hierarchy of common, attachments and presets. The two examples below cover the two "modes" which can be used to merge files. These modes are set using the [MODULAR_MERGE]
section, and if this section is not included as part of the CFG file, then the default behaviour is to auto-merge.
Auto-Merge
The auto-merge process for the various CFG (and FLT) files is as follows:
- The Common files are read: these represent the base files for the aircraft. The common information about the aircraft which is not linked to any Attachments should be defined in the common files, unless the parameters have a default value in which case it can be omitted.
- Each SimAttachment in the Attachments folder with an alias, and which has a version of the CFG file currently being parsed, will override the common CFG according to the following rules:
- In the case of a SimAttachment referencing another SimAttachment using the
attached_objects.cfg
, the child attachment CFG files will be parsed before the parent, and the parent will override any parameters that they share. - In the case of a SimAttachment referencing another SimAttachment using the
attachment.cfg
, The base files will be parsed first, and then the parent files will be merged to extend the base CFG information. - If a section defined in a SimAttachment CFG is missing in the corresponding common file, this section will be added to the final CFG.
- For any given section within a CFG file, the parameters from the SimAttachment which are not indexed will override the corresponding parameters in the common CFG file.
- In case of an indexed parameter within a SimAttachment CFG, the parameter will be added at the end of the common parameters, and the index will be updated appropriately (see the example below).
- In the case of a SimAttachment referencing another SimAttachment using the
- Finally, the Presets files will be processed, following the same rules outlined above in point (2). This means that the preset files provide the final override for the aircraft variation that the preset represents.
Schema Example before auto-merging:
// From Common ///////////////////////////////////////////////////// [SECTION_1] a = 1 b = 2 c.0 = 123 c.1 = 456 c.2 = 789
// From Attachments - Attach_1 ///////////////////////////////////// [SECTION_1] b = 26 c.0 = 123456 c.1 = 654321 [SECTION_2] azerty = TRUE
// From Attachment - Attach_2 ////////////////////////////////////// [SECTION_1] b = 54
// From Preset ///////////////////////////////////////////////////// [MODULAR_MERGE] auto = TRUE [SECTION_1] c.0 = 123456789
Schema Example after auto-merging:
// Merged CFG file //////////////////////////////////////////////// [SECTION_1] // from Preset (added by Common, then overriden by Attach_1, Attach_2, then Preset) a = 1 // from Common b = 54 // from Attach_2 (added by Common, then overriden by Attach_1, then Attach_2) c.0 = 123 // from Common c.1 = 456 // from Common c.2 = 789 // from Common c.3 = 123456 // from Attach_1 (originally c.0 but the index is changed to accommodate the other indexed values) c.4 = 654321 // from Attach_1 (originally c.1 but the index is changed to accommodate the other indexed values) c.5 = 123456789 // from Preset (originally c.0 but the index is changed to accommodate the other indexed values) [SECTION_2] // from Attach_1 azerty = TRUE // from Attach_1
Manual-Merge
If auto-merge is set to false in the [MODULAR_MERGE]
section, then the process described above for auto-merging will still be performed, but there will be an additional step performed for each CFG that is parsed and merged:
-
When the merged file is processed, every parameter value is inspected. If the value for any of the parameters is a string identifying a SimAttachment alias, then the value of the field will be replaced by the corresponding value from the attachment CFG. This string is formatted as follows:
AttachmentRebind#XXX
For example:
param = AttachmentRebind#Attachment_Alias
Schema Example before manual-merging:
// From Common ///////////////////////////////////////////////////// [SECTION_1] a = 1 b = 2 c.0 = 123
// From Attachments - Attach_1 ///////////////////////////////////// [SECTION_1] b = 26 c.0 = 123456 c.1 = 456789 [SECTION_2] azerty = TRUE
// From Attachment - Attach_2 ////////////////////////////////////// [SECTION_1] b = 54 c.0 = 256 [SECTION_2] azerty = FALSE
// From Preset ///////////////////////////////////////////////////// [MODULAR_MERGE] auto = FALSE [SECTION_1] c.0 = AttachmentRebind#Attach_1 c.1 = 123456789 [SECTION_2] azerty = AttachmentRebind#Attach_1
Schema Example after auto-merging but before manual-merging:
// Merged CFG file //////////////////////////////////////////////// [SECTION_1] a = 1 b = 54 c.0 = AttachmentRebind#Attach_1 c.1 = 456789 c.2 = 256 c.4 = 123456789 [SECTION_2] azerty = AttachmentRebind#Attach_1
Schema Example after manual-merging:
// Merged CFG file //////////////////////////////////////////////// [SECTION_1] // from Preset (added by Common, then overriden by Attach_1, Attach_2, then Preset) a = 1 // from Common b = 54 // from Attach_2 (added by Common, then overriden by Attach_1, then Attach_2) c.0 = 123456 // from Attach_1 (added by Common, then overriden by Attach_1, Attach_2, then Preset) c.1 = 456789 // from Attach_1 c.2 = 123456789 // from Preset [SECTION_2] // from Preset (added by Attach_1, then overriden by Attach_2, then Preset) azerty = TRUE // from Attach_1 (added by Attach_1, then overriden by Attach_2, then Preset)
Merging CFG Dynamic Parameters
SimAttachments can have dynamic parameters assigned to them by adding a [DynamicParameters]
section to the file. These are parameters that get defined in the CFG file(s) for the attachment, and then get modified in the preset attached_objects.cfg
file so that when the final file is generated, the parameter will have different values based on the preset (or the base values of the SimAttachment if the preset does not change them).
For example, let's look at how to change the position of the aircraft wheels based on the selected preset. To start with we are creating the dynamic parameters for the wheel contact points in the SimAttachment flight_model.cfg
file:
[DynamicParameters] param.0="s_pitch, 11" param.1="center_wheel_position,-2.3, 0, -4.18" param.2="left_wheel_position,-15.8, -6, -4.14" param.3="right_wheel_position,-15.8, 6, -4.14" [CONTACT_POINTS] static_pitch = [s_pitch] point.1 = Name:center_wheel[uid] #Properties:1, [center_wheel_position], 750, 0, 0.5, 22, 0.21, 3, 0.5, 0, 0, 0, 0, 0 point.2 = Name:left_wheel[uid] #Properties:1, [left_wheel_position], 2000, 1, 0.5, 0, 0.26, 3, 0.5, 0, 0, 2, 0, 0 point.3 = Name:right_wheel[uid] #Properties:1, [right_wheel_position], 2000, 2, 0.5, 0, 0.26, 3, 0.5, 0, 0, 3, 0, 0
So, the dynamic parameter is simply a string of values, where the first one is always the identifier of the parameter. For single parameters, this is all you need to know as you then substitute the value of a CFG parameter with the dynamic parameter identifier and the merge process will "pull" the correct value for it from that. However it is important to note that if the parameter is an indexed parameter, then you will also need to have [uid]
appended to it so that the merge process can later add the attachment alias to it for proper identification.
Once you have set up the attachment parameters, you can then edit the attached_objects.cfg
for the appropriate preset(s) with the values you want to override with:
[SIM_ATTACHMENT.0] // (other params...) alias="Function_Wheels" cfg_parameter.0 = "s_pitch, 8" cfg_parameter.1 = "center_wheel_position,-2.5, 0, -4.25"
You do not need to provide override values for all the dynamic parameters you have defined, as the ones you don't supply will just use the initial values from the SimAttachment. Once you have set this up, when the files are merged as part of the build process, the flight_model.cfg
file will look like this:
[CONTACT_POINTS] static_pitch = 8 // (other params...) point.5=Name:center_wheel_Function_Wheels #Properties:1, -2.5, 0, -4.25, 750, 0, 0.5, 22, 0.21, 3, 0.5, 0, 0, 0, 0, 0 point.6=Name:left_wheel_Function_Wheels #Properties:1, -15.8, -6, -4.14, 2000, 1, 0.5, 0, 0.26, 3, 0.5, 0, 0, 2, 0, 0 point.7=Name:right_wheel_Function_Wheels #Properties:1, -15.8, 6, -4.14, 2000, 2, 0.5, 0, 0.26, 3, 0.5, 0, 0, 3, 0, 0
XML Merge Examples
When it comes to XML files, the merge process is very similar to that used by CFG files, following the path:
common > attachments > Preset > auto-merge > manual-merge
Merging is controlled using the AutoMerge
attribute, in the root node. This attribute can only exist in the Presets XML files, and when set to 1 (true) then files will use auto-merge, and if set to 0 (false) then they will use manual-merge. If this attribute is not included then then the default behaviour is to auto-merge.
Auto-Merge
The auto-merge feature for XML files works as follows:
- The Common files are read: these represent the base files for the aircraft. The common information about the aircraft which is not linked to any Attachments should be defined in the common files
- Each SimAttachment in the Attachments folder with an alias, and which has a version of the XML file currently being parsed, will override the common XML values and/or elements (including sub-elements), according to the following rules:
- The first step is to try and match the attachment's XML architecture to the common one using elements, sub-elements, and attributes. An attachment element is considered as matching a Common element when they share the same name and every attribute (and value) from the attachment node is present in the Common element.
- If an element has been found to match, then sub-elements within it will be processed. If there are no sub-elements, then the value of the matching element will be updated.
- If no match is found, then a copy on the element and its children will be made in the merge file.
- Finally, the Presets files will be processed, following the same rules outlined above in point (2). This means that the preset files provide the final override for the aircraft variation that the preset represents.
Schema example before auto-merging:
<!-- Common -->
<RootElement>
<Element>
<SubElement [attribute]="azerty">
<Something>26</Something>
<Otherthing>
<A>azerty</A>
</Otherthing>
</SubElement>
<SubElement [attribute]="qsdfg">
<Something>123456789</Something>
<Otherthing>
<A>qsdfg</A>
</Otherthing>
</SubElement>
</Element>
</RootElement>
<!-- From Attachment - Attach_1 -->
<RootElement>
<Element>
<SubElement [attribute]="qsdfg">
<Something>987654321</Something>
</SubElement>
</Element>
</RootElement>
<!-- From Attachment - Attach_2 -->
<RootElement>
<Element>
<SubElement [attribute]="wxcvbn">
<Something>0</Something>
<Otherthing>
<A>wxcvbn</A>
</Otherthing>
</SubElement>
</Element>
</RootElement>
<!-- Preset -->
<RootElement AutoMerge="1">
<Element>
<SubElement [attribute]="wxcvbn">
<Something>124</Something>
<Otherthing>
<A>toto</A>
</Otherthing>
</SubElement>
<SubElement Id="toto" />
</Element>
</RootElement>
Schema example after auto-merging:
<RootElement>
<Element>
<SubElement [attribute]="azerty"> <!-- From Common -->
<Something>26</Something> <!-- From Common -->
<Otherthing> <!-- From Common -->
<A>azerty</A> <!-- From Common -->
</Otherthing> <!-- From Common -->
</SubElement>
<SubElement [attribute]="qsdfg"> <!-- From Common -->
<Something>987654321</Something> <!-- From Attach_1 -->
<Otherthing> <!-- From Common -->
<A>qsdfg</A> <!-- From Common -->
</Otherthing> <!-- From Common -->
</SubElement>
<SubElement [attribute]="wxcvbn"> <!-- From Attach_2 -->
<Something>124</Something> <!-- From Preset -->
<Otherthing> <!-- From Preset -->
<A>toto</A> <!-- From Preset -->
</Otherthing> <!-- From Preset -->
</SubElement>
<SubElement [attribute]="toto"/> <!-- From Preset -->
</Element>
</RootElement>
Manual-Merge
If auto-merge is set to 0 (false) in the AutoMerge
attribute, then the process described above for auto-merging will still be performed, but there will be an additional step performed for each XML that is parsed and merged. In this additional step, the Preset files will be parsed for the <RemoveEntry />
and <MoveEntry>
nodes, which will be used to perform the manual-merge of the file.
Schema example before auto-merging:
<!-- Common -->
<RootElement>
<Element>
<SubElement [attribute]="azerty">
<Something>26</Something>
<Otherthing>
<A>azerty</A>
</Otherthing>
</SubElement>
<SubElement [attribute]="qsdfg">
<Something>123456789</Something>
<Otherthing>
<A>qsdfg</A>
</Otherthing>
</SubElement>
</Element>
</RootElement>
<!-- From Attachment - Attach_1 -->
<RootElement>
<Element>
<SubElement [attribute]="qsdfg">
<Something>987654321</Something>
</SubElement>
</Element>
</RootElement>
<!-- From Attachment - Attach_2 -->
<RootElement>
<Element>
<SubElement [attribute]="wxcvbn">
<Something>0</Something>
<Otherthing>
<A>wxcvbn</A>
</Otherthing>
</SubElement>
</Element>
</RootElement>
<!-- Preset -->
<RootElement AutoMerge="0">
<Element>
<SubElement [attribute]="wxcvbn">
<Something>124</Something>
<Otherthing>
<A>toto</A>
</Otherthing>
</SubElement>
<SubElement Id="toto" />
<MoveEntry Node="Child" [attribute]="azerty">
<Child [attribute]="toto"/>
<Child [attribute]="wxcvbn"/>
</MoveEntry>
<RemoveEntry Node="Child" Id="qsdfg"/>
</Element>
</RootElement>
Schema example after auto-merging but before manual-merging:
<RootElement>
<Element>
<SubElement [attribute]="azerty"> <!-- From Common -->
<Something>26</Something> <!-- From Common -->
<Otherthing> <!-- From Common -->
<A>azerty</A> <!-- From Common -->
</Otherthing> <!-- From Common -->
</SubElement>
<SubElement [attribute]="qsdfg"> <!-- From Common -->
<Something>987654321</Something> <!-- From Attach_1 -->
<Otherthing> <!-- From Common -->
<A>qsdfg</A> <!-- From Common -->
</Otherthing> <!-- From Common -->
</SubElement>
<SubElement [attribute]="wxcvbn"> <!-- From Attach_2 -->
<Something>124</Something> <!-- From Preset -->
<Otherthing> <!-- From Preset -->
<A>toto</A> <!-- From Preset -->
</Otherthing> <!-- From Preset -->
</SubElement>
<SubElement [attribute]="toto"/> <!-- From Preset -->
<MoveEntry Node="Child" [attribute]="azerty"> <!-- From Preset -->
<SubElement [attribute]="toto"/>
<SubElement [attribute]="wxcvbn"/>
</MoveEntry>
<RemoveEntry Node="Child" Id="qsdfg"/> <!-- From Preset -->
</Element>
</RootElement>
Schema example after manual-merging:
<RootElement>
<Element>
<SubElement [attribute]="toto"/>
<SubElement [attribute]="wxcvbn">
<Something>124</Something>
<Otherthing>
<A>toto</A>
</Otherthing>
</SubElement>
<SubElement [attribute]="azerty">
<Something>26</Something>
<Otherthing>
<A>azerty</A>
</Otherthing>
</SubElement>
</Element>
</RootElement>
Merging XML Dynamic Parameters
Dynamic parameters can be created in the model behaviour XML files by using the <DynamicParameters>
element, like this:
<DynamicParameters>
<PARM_NAME>
</DynamicParameters>
Using [uid]
When an element in the XML or CFG files references an indexed parameter (for example a circuit index from the systems.cfg
), then you should append [uid]
to the end of the circuit name so that the correct one is referenced. This is done because indexed parameters may have different indices after the merge process has been completed, and so we rely on a combination of the CFG parameter name and the attachment alias to identify the indexed parameter correctly, since the index is no longer a reliable indication of target.
To give an example, let’s say we have an attachment which requires the following circuit definition:
circuit.1 = Type:CIRCUIT_PFD # ConsumerCfg:ConsumerPFD #Name:PFD
If the attachment is not meant to be instanced several times on the same host, you can use the circuit name as it is defined, using the ''_n
format, as shown below:
<ANIM_CODE>(A:CIRCUIT ON:'PFD'_n, percent)</ANIM_CODE>
However, if the attachment is supposed to be created several times, you can refer to it using a concatenated name syntax with the dynamic parameter [uid]
created automatically if the sim-attachment is listed more than one time. So, if you have two attachments defined with two different aliases like this:
// first time we use the same sim-attachment
alias = "foo"
// second time we use the same sim-attachment
alias = "bar"
Then in the CFG file you would define the parameter like this:
// before merge
circuit.1 = Type:CIRCUIT_PFD # ConsumerCfg:ConsumerPFD #Name:PFD[uid]
And after merging the CFG will look like this:
circuit.1 = Type:CIRCUIT_PFD # ConsumerCfg:ConsumerPFD #Name:PFD_foo
circuit.2 = Type:CIRCUIT_PFD # ConsumerCfg:ConsumerPFD #Name:PFD_bar
And in the XML it would be referenced like this:
<ANIM_CODE>(A:CIRCUIT ON:'PFD[uid]'_n, percent)</ANIM_CODE>
This gives us 2 use cases :
- The sim-attachment is instanced only once, CFGs do not conflict, so names won't be affected
PFD[uid]
becomes simplyPFD
when instantiated
- The sim-attachment is instanced several times, CFGs and XMLs are merged using the attachment alias when relying on the automatic merge
- the first
PFD[uid]
becomesPFD_foo
when instantiated - the second
PFD[uid]
becomesPFD_bar
when instantiated
- the first
Merge Process For Liveries
When it comes to merging livery into the final modular aircraft, the simulation goes over the list of tags for each attachment and then loads the livery parts according to the tags assigned to the various folders. Take the following livery folder as an example:
+ Liveries |---+ Company_Name | |---+ Livery_One | |---+ model.wheels | | |---- wheels_LOD0.gltf | | |---- wheels_LOD1.gltf | | |---- <more_lod_files> | |---+ texture.wheels | | |---- a_texture_used_in_the_attachment_wheels_gltf.png // override a texture | | |---- a_texture_used_in_wheels_lod_gltf // a texture used in the added models | | |---- <more_texture_files> | |---+ panel.[TAG] | |---- panel.cfg // some VPaintings
When loading the wheels attachment, the attachment only has one tag: "wheels". The simulation will then check for texture.wheels
, model.wheels
, and panel.wheels
folders in the livery root directory. Each of these folders will be treated as follows:
-
Texture Folders
For each tag, if thetexture.[TAG]
folder exists, it will be used as a fallback first. Then, the simulation will prepend the attachment texture directories with the livery texture directory so that the livery textures will always be used first (if they exist). If there aretexture.cfg
files in the folder, these will be listed next in the fallbacks, as illustrated here:- livery folders:
-
texture.[tag]
: for each attachment tag
-texture.[texture_folder]
: if a texture folder is specified in theattached_objects.cfg
-texture.[model_entry_type]
: either "interior" or "exterior"
-texture
-
attachment folders:
-texture.[texture_folder]
: if a texture folder is specified in theattached_objects.cfg
-texture
-texture.[texture_folder]
andtexture
of inherited attachments from attachment to base -
base model instance folders:
- existingtexture
directories which weren't already listed as fallbacks
texture.interior
andtexture.exterior
folders are special cases which are reserved for liveries targeting the textures from the Common and Presets folders. - livery folders:
-
Model Folders
Livery models are added to the list of extra models to merge which will be processed and merged automatically when loading the attachment. The merge is done LOD by LOD, and to simplify this process the glTF names should follow the schema ofLivery_LOD[lod_number].gltf
. Thelod_number
starts at 0 and is restricted to the number of LODs the model being merging into has defined in its model info. Note that you may alternatively supply alivery.xml
file to re-use the same glTF in multiple LOD definitions. If alivery.xml
is found it will be used instead of the schema matching method to populate the per-LOD glTFs to merge, for example:
This file may also be used when the LOD files do not follow the naming schema outlined above.<?xml version="1.0" encoding="utf-8" ?> <ModelInfo> <LODS> <LOD MinSize="100" ModelFile="LIVERY_LOD0.gltf"/> <LOD MinSize="1" ModelFile="LIVERY_LOD0.gltf"/> </LODS> </ModelInfo>
NOTE: theMinSize
attribute is required but is ignored by the simulation.Note that themodel.interior
andmodel.exterior
are special cases, as they are reserved for liveries targeting the models from the Common and Presets folders.
-
Panel Folder
For each tag, if the
panel.[tag]
folder exists and has apanel.cfg
, then that CFG will be loaded. In the case of liveries, only the[VPaintingN]
section is required (although you can include other sections like[DynamicParameters]
, for example). When working with this file in a livery, it is important to note that Vpaintings can only be added and cannot override existing ones. To counteract this limitation, Vpaintings support dynamic parameter replacement using the[Panel_DynamicParameters]
section of thelivery.cfg
file, or per-preset using thevpainting_parameter.N
parameter in theattached_objects.cfg
.
The load order for modular aircraft panels guarantees merge rules are respected and looks like this:- attachment panel folder: loads both VPaintings and VCockpits
-panel.cfg
from the base to attach
-panel.cfg
of the attachment -
livery panel folder: limited to adding VPaintings
-panel.[tag]
for each attachment tag
- attachment panel folder: loads both VPaintings and VCockpits