35#include <wx/filename.h>
68 "schematic.drawing.field_names",
69 [&]() -> nlohmann::json
71 nlohmann::json ret = nlohmann::json::array();
76 ret.push_back( nlohmann::json( {
77 {
"name", field.m_Name },
78 {
"visible", field.m_Visible },
79 {
"url", field.m_URL }
85 [&](
const nlohmann::json& aJson )
87 if( aJson.is_string() )
91 TEMPLATES::SCOPE::PROJECT );
93 else if( aJson.is_array() )
97 for(
const nlohmann::json& entry : aJson )
99 if( entry.contains(
"name" ) && entry.contains(
"url" )
100 && entry.contains(
"visible" ) )
103 field.
m_URL = entry[
"url"].get<
bool>();
104 field.
m_Visible = entry[
"visible"].get<
bool>();
151 [&]() -> nlohmann::json
153 nlohmann::json ret = nlohmann::json::array();
156 ret.push_back( libName );
160 [&](
const nlohmann::json& aJson )
162 if( aJson.empty() || !aJson.is_array() )
167 for(
const nlohmann::json& entry : aJson )
172 [&]() -> nlohmann::json
176 return nlohmann::json();
178 nlohmann::json ret = nlohmann::json::object();
182 nlohmann::json members = nlohmann::json::array();
184 for(
const wxString& member : alias.second )
185 members.push_back( member );
187 ret[ alias.first.ToStdString() ] = members;
192 [&](
const nlohmann::json& aJson )
197 if( !aJson.is_object() )
200 for(
auto it = aJson.begin(); it != aJson.end(); ++it )
202 const nlohmann::json& membersJson = it.value();
204 if( !membersJson.is_array() )
207 std::vector<wxString> members;
209 for(
const nlohmann::json& entry : membersJson )
211 if( entry.is_string() )
213 wxString member = entry.get<wxString>().Strip( wxString::both );
215 if( !member.IsEmpty() )
216 members.push_back( member );
220 wxString
name = wxString::FromUTF8( it.key().c_str() ).Strip( wxString::both );
222 if( !
name.IsEmpty() )
228 m_params.back()->SetClearUnknownKeys();
230 m_NetSettings = std::make_shared<NET_SETTINGS>(
this,
"net_settings" );
233 std::make_shared<COMPONENT_CLASS_SETTINGS>(
this,
"component_class_settings" );
271 [
this]() {
return nlohmann::json(
m_IP2581Bom.sections.value_or( wxString() ) ); },
272 [
this](
const nlohmann::json& aVal )
274 wxString sections = aVal.is_string() ? aVal.get<wxString>() : wxString();
275 m_IP2581Bom.sections = sections.IsEmpty() ? std::nullopt : std::optional<wxString>( sections );
277 nlohmann::json(
"" ) ) );
281 [
this](
const nlohmann::json& aVal )
283 if( aVal.is_boolean() && aVal.get<
bool>() && !
m_IP2581Bom.sections )
286 nlohmann::json(
false ) ) );
330 auto p(
"/board/layer_presets"_json_pointer );
337 for( nlohmann::json& entry : presets )
348 auto p(
"/board/layer_presets"_json_pointer );
355 for( nlohmann::json& entry : presets )
366 const auto aliases =
GetJson(
"schematic.bus_aliases" );
368 if( aliases && aliases->is_object() && aliases->empty() )
371 Set(
"schematic.bus_aliases", nlohmann::json() );
385 std::set<wxString> group_blacklist;
392 auto loadPinnedLibs =
393 [&](
const std::string& aDest )
396 wxString libKey = wxT(
"PinnedItems" );
399 nlohmann::json libs = nlohmann::json::array();
401 while( aCfg->Read( libKey, &str ) )
403 libs.push_back( str );
405 aCfg->DeleteEntry( libKey,
true );
407 libKey = wxT(
"PinnedItems" );
408 libKey << ++libIndex;
414 aCfg->SetPath( wxT(
"/LibeditFrame" ) );
415 loadPinnedLibs(
"libraries.pinned_symbol_libs" );
417 aCfg->SetPath( wxT(
"/ModEditFrame" ) );
418 loadPinnedLibs(
"libraries.pinned_footprint_libs" );
420 aCfg->SetPath( wxT(
"/cvpcb/equfiles" ) );
424 wxString eqKey = wxT(
"EquName" );
427 nlohmann::json eqs = nlohmann::json::array();
429 while( aCfg->Read( eqKey, &str ) )
431 eqs.push_back( str );
433 eqKey = wxT(
"EquName" );
437 Set(
"cvpcb.equivalence_files", eqs );
441 group_blacklist.insert( wxT(
"/cvpcb" ) );
443 aCfg->SetPath( wxT(
"/eeschema" ) );
446 aCfg->SetPath( wxT(
"/eeschema/libraries" ) );
450 wxString libKey = wxT(
"LibName" );
453 nlohmann::json libs = nlohmann::json::array();
455 while( aCfg->Read( libKey, &str ) )
457 libs.push_back( str );
459 libKey = wxT(
"LibName" );
463 Set(
"schematic.legacy_lib_list", libs );
466 group_blacklist.insert( wxT(
"/eeschema" ) );
468 aCfg->SetPath( wxT(
"/text_variables" ) );
475 nlohmann::json vars = nlohmann::json();
477 while( aCfg->Read( txtKey, &str ) )
479 wxArrayString tokens = wxSplit( str,
':' );
481 if( tokens.size() == 2 )
482 vars[ tokens[0].ToStdString() ] = tokens[1];
488 Set(
"text_variables", vars );
491 group_blacklist.insert( wxT(
"/text_variables" ) );
493 aCfg->SetPath( wxT(
"/schematic_editor" ) );
495 fromLegacyString( aCfg,
"PageLayoutDescrFile",
"schematic.page_layout_descr_file" );
498 fromLegacy<bool>( aCfg,
"SpiceAjustPassiveValues",
"schematic.spice_adjust_passive_values" );
499 fromLegacy<int>( aCfg,
"SubpartIdSeparator",
"schematic.subpart_id_separator" );
500 fromLegacy<int>( aCfg,
"SubpartFirstId",
"schematic.subpart_first_id" );
502 fromLegacy<int>( aCfg,
"LineThickness",
"schematic.drawing.default_line_thickness" );
503 fromLegacy<int>( aCfg,
"WireThickness",
"schematic.drawing.default_wire_thickness" );
504 fromLegacy<int>( aCfg,
"BusThickness",
"schematic.drawing.default_bus_thickness" );
505 fromLegacy<int>( aCfg,
"LabSize",
"schematic.drawing.default_text_size" );
507 if( !
fromLegacy<int>( aCfg,
"PinSymbolSize",
"schematic.drawing.pin_symbol_size" ) )
510 Set(
"schematic.drawing.pin_symbol_size", 0 );
513 fromLegacy<int>( aCfg,
"JunctionSize",
"schematic.drawing.default_junction_size" );
515 fromLegacyString( aCfg,
"FieldNameTemplates",
"schematic.drawing.field_names" );
517 if( !
fromLegacy<double>( aCfg,
"TextOffsetRatio",
"schematic.drawing.text_offset_ratio" ) )
520 Set(
"schematic.drawing.text_offset_ratio", 0.08 );
521 Set(
"schematic.drawing.label_size_ratio", 0.25 );
525 group_blacklist.insert( wxT(
"/schematic_editor" ) );
527 aCfg->SetPath( wxT(
"/pcbnew" ) );
529 fromLegacyString( aCfg,
"PageLayoutDescrFile",
"pcbnew.page_layout_descr_file" );
534 fromLegacyString( aCfg,
"LastSpecctraDSNExportPath",
"pcbnew.last_paths.specctra_dsn" );
535 fromLegacyString( aCfg,
"LastGenCADExportPath",
"pcbnew.last_paths.gencad" );
537 std::string bp =
"board.design_settings.";
541 wxString key = wxT(
"DRCExclusion" );
544 nlohmann::json exclusions = nlohmann::json::array();
546 while( aCfg->Read( key, &str ) )
548 exclusions.push_back( str );
550 key = wxT(
"DRCExclusion" );
554 Set( bp +
"drc_exclusions", exclusions );
558 fromLegacy<bool>( aCfg,
"AllowBlindVias", bp +
"rules.allow_blind_buried_vias" );
564 if( !
fromLegacy<double>( aCfg,
"MinThroughDrill", bp +
"rules.min_through_hole_diameter" ) )
567 fromLegacy<double>( aCfg,
"MinMicroViaDiameter", bp +
"rules.min_microvia_diameter" );
570 fromLegacy<double>( aCfg,
"CopperEdgeClearance", bp +
"rules.min_copper_edge_clearance" );
571 fromLegacy<double>( aCfg,
"SolderMaskClearance", bp +
"rules.solder_mask_clearance" );
573 fromLegacy<double>( aCfg,
"SolderPasteClearance", bp +
"rules.solder_paste_clearance" );
574 fromLegacy<double>( aCfg,
"SolderPasteRatio", bp +
"rules.solder_paste_margin_ratio" );
577 fromLegacy<double>( aCfg,
"ModuleOutlineThickness", bp +
"defaults.silk_line_width" );
585 if( !
fromLegacy<double>( aCfg,
"SilkTextSizeThickness", bp +
"defaults.silk_text_thickness" ) )
586 fromLegacy<double>( aCfg,
"ModuleTextSizeThickness", bp +
"defaults.silk_text_thickness" );
588 fromLegacy<bool>( aCfg,
"SilkTextItalic", bp +
"defaults.silk_text_italic" );
589 fromLegacy<bool>( aCfg,
"SilkTextUpright", bp +
"defaults.silk_text_upright" );
591 if( !
fromLegacy<double>( aCfg,
"CopperLineWidth", bp +
"defaults.copper_line_width" ) )
594 if( !
fromLegacy<double>( aCfg,
"CopperTextSizeV", bp +
"defaults.copper_text_size_v" ) )
597 if( !
fromLegacy<double>( aCfg,
"CopperTextSizeH", bp +
"defaults.copper_text_size_h" ) )
600 if( !
fromLegacy<double>( aCfg,
"CopperTextThickness", bp +
"defaults.copper_text_thickness" ) )
601 fromLegacy<double>( aCfg,
"PcbTextThickness", bp +
"defaults.copper_text_thickness" );
603 fromLegacy<bool>( aCfg,
"CopperTextItalic", bp +
"defaults.copper_text_italic" );
604 fromLegacy<bool>( aCfg,
"CopperTextUpright", bp +
"defaults.copper_text_upright" );
606 if( !
fromLegacy<double>( aCfg,
"EdgeCutLineWidth", bp +
"defaults.board_outline_line_width" ) )
608 bp +
"defaults.board_outline_line_width" );
610 fromLegacy<double>( aCfg,
"CourtyardLineWidth", bp +
"defaults.courtyard_line_width" );
615 fromLegacy<double>( aCfg,
"FabTextSizeThickness", bp +
"defaults.fab_text_thickness" );
617 fromLegacy<bool>( aCfg,
"FabTextUpright", bp +
"defaults.fab_text_upright" );
619 if( !
fromLegacy<double>( aCfg,
"OthersLineWidth", bp +
"defaults.other_line_width" ) )
620 fromLegacy<double>( aCfg,
"ModuleOutlineThickness", bp +
"defaults.other_line_width" );
624 fromLegacy<double>( aCfg,
"OthersTextSizeThickness", bp +
"defaults.other_text_thickness" );
625 fromLegacy<bool>( aCfg,
"OthersTextItalic", bp +
"defaults.other_text_italic" );
626 fromLegacy<bool>( aCfg,
"OthersTextUpright", bp +
"defaults.other_text_upright" );
628 fromLegacy<int>( aCfg,
"DimensionUnits", bp +
"defaults.dimension_units" );
629 fromLegacy<int>( aCfg,
"DimensionPrecision", bp +
"defaults.dimension_precision" );
631 std::string sev = bp +
"rule_severities";
633 fromLegacy<bool>( aCfg,
"RequireCourtyardDefinitions", sev +
"legacy_no_courtyard_defined" );
635 fromLegacy<bool>( aCfg,
"ProhibitOverlappingCourtyards", sev +
"legacy_courtyards_overlap" );
639 wxString keyBase =
"TrackWidth";
640 wxString key = keyBase;
643 nlohmann::json widths = nlohmann::json::array();
647 while( aCfg->Read( key, &val ) )
649 widths.push_back( val );
654 Set( bp +
"track_widths", widths );
659 wxString keyBase =
"ViaDiameter";
660 wxString key = keyBase;
664 nlohmann::json vias = nlohmann::json::array();
668 while( aCfg->Read( key, &diameter ) )
672 aCfg->Read( key << idx, &drill );
674 nlohmann::json
via = { {
"diameter", diameter }, {
"drill", drill } };
675 vias.push_back(
via );
681 Set( bp +
"via_dimensions", vias );
686 wxString keyBase =
"dPairWidth";
687 wxString key = keyBase;
692 nlohmann::json pairs = nlohmann::json::array();
696 while( aCfg->Read( key, &width ) )
700 aCfg->Read( key << idx, &gap );
704 aCfg->Read( key << idx, &via_gap );
706 nlohmann::json pair = { {
"width", width }, {
"gap", gap }, {
"via_gap", via_gap } };
707 pairs.push_back( pair );
713 Set( bp +
"diff_pair_dimensions", pairs );
716 group_blacklist.insert( wxT(
"/pcbnew" ) );
719 group_blacklist.insert( wxT(
"/general" ) );
722 aCfg->SetPath( wxT(
"/" ) );
724 auto loadSheetNames =
729 nlohmann::json arr = nlohmann::json::array();
733 aCfg->SetPath( wxT(
"/sheetnames" ) );
735 while( aCfg->Read( wxString::Format(
"%d", sheet++ ), &entry ) )
737 wxArrayString tokens = wxSplit( entry,
':' );
739 if( tokens.size() == 2 )
741 wxLogTrace(
traceSettings, wxT(
"%d: %s = %s" ), sheet, tokens[0], tokens[1] );
742 arr.push_back( nlohmann::json::array( { tokens[0], tokens[1] } ) );
746 Set(
"sheets", arr );
748 aCfg->SetPath(
"/" );
753 std::vector<wxString> groups;
755 groups.emplace_back( wxEmptyString );
757 auto loadLegacyPairs =
758 [&](
const std::string& aGroup ) ->
bool
760 wxLogTrace(
traceSettings, wxT(
"Migrating group %s" ), aGroup );
767 while( aCfg->GetNextEntry( keyStr,
index ) )
769 if( !aCfg->Read( keyStr, &val ) )
772 std::string key( keyStr.ToUTF8() );
778 Set(
"legacy." + aGroup +
"." + key, val );
789 for(
size_t i = 0; i < groups.size(); i++ )
791 aCfg->SetPath( groups[i] );
793 if( groups[i] == wxT(
"/sheetnames" ) )
795 ret |= loadSheetNames();
799 aCfg->DeleteEntry( wxT(
"last_client" ),
true );
800 aCfg->DeleteEntry( wxT(
"update" ),
true );
801 aCfg->DeleteEntry( wxT(
"version" ),
true );
807 while( aCfg->GetNextGroup( str,
index ) )
809 wxString
group = groups[i] +
"/" + str;
811 if( !group_blacklist.count(
group ) )
812 groups.emplace_back(
group );
815 aCfg->SetPath(
"/" );
832 wxString projectName =
m_project->GetProjectName();
836 defaultSheet.
name = projectName;
837 defaultSheet.
filename = projectName +
".kicad_sch";
844 wxLogTrace(
traceSettings, wxT(
"PROJECT_FILE: Migrated old single-root format to top_level_sheets" ) );
853 wxString projectPath =
m_project->GetProjectPath();
854 wxString projectName =
m_project->GetProjectName();
858 wxFileName referencedFile( projectPath, sheetInfo.filename );
860 if( referencedFile.FileExists() )
866 wxFileName candidateFile( projectPath, expectedFile );
868 if( candidateFile.FileExists() )
871 wxT(
"PROJECT_FILE: Fixing stale top_level_sheets reference '%s' -> '%s'" ),
872 sheetInfo.filename, expectedFile );
874 sheetInfo.filename = expectedFile;
875 sheetInfo.name = projectName;
928 return std::string();
936 wxString oldProjectName = oldFilename.GetName();
937 wxString oldProjectPath = oldFilename.GetPath();
943 [&]( wxString& aPath )
945 if( aPath.StartsWith( oldProjectName + wxS(
"." ) ) )
946 aPath.Replace( oldProjectName, aFile,
false );
947 else if( aPath.StartsWith( oldProjectPath + wxS(
"/" ) ) )
948 aPath.Replace( oldProjectPath, aDirectory,
false );
956 auto updatePathByPtr =
957 [&](
const std::string& aPtr )
961 updatePath(
path.value() );
966 updatePathByPtr(
"schematic.page_layout_descr_file" );
967 updatePathByPtr(
"schematic.plot_directory" );
968 updatePathByPtr(
"schematic.ngspice.workbook_filename" );
969 updatePathByPtr(
"pcbnew.page_layout_descr_file" );
973 updatePath( sheetInfo.filename );
976 if( sheetInfo.name == oldProjectName )
977 sheetInfo.name = aFile;
1005 aJson = nlohmann::json::array( { aPair.first.AsString().ToUTF8(), aPair.second.ToUTF8() } );
1011 wxCHECK( aJson.is_array() && aJson.size() == 2, );
1012 aPair.first =
KIID( wxString( aJson[0].get<std::string>().c_str(), wxConvUTF8 ) );
1013 aPair.second = wxString( aJson[1].get<std::string>().c_str(), wxConvUTF8 );
1019 aJson = nlohmann::json::object();
1021 aJson[
"name"] = aInfo.
name.ToUTF8();
1022 aJson[
"filename"] = aInfo.
filename.ToUTF8();
1028 wxCHECK( aJson.is_object(), );
1030 if( aJson.contains(
"uuid" ) )
1031 aInfo.
uuid =
KIID( wxString( aJson[
"uuid"].get<std::string>().c_str(), wxConvUTF8 ) );
1033 if( aJson.contains(
"name" ) )
1034 aInfo.
name = wxString( aJson[
"name"].get<std::string>().c_str(), wxConvUTF8 );
1036 if( aJson.contains(
"filename" ) )
1037 aInfo.
filename = wxString( aJson[
"filename"].get<std::string>().c_str(), wxConvUTF8 );
bool fromLegacyString(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy wxConfig string value to a given JSON pointer value.
bool fromLegacy(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy wxConfig value to a given JSON pointer value.
bool flushToStore()
Flushes nested settings and parameters into the JSON store.
void Set(const std::string &aPath, ValueType aVal)
Stores a value into the JSON document Will throw an exception if ValueType isn't something that the l...
std::optional< nlohmann::json > GetJson(const std::string &aPath) const
Fetches a JSON object that is a subset of this JSON_SETTINGS object, using a path of the form "key1....
bool m_modified
True if the JSON data store has been written to since the last file write.
virtual bool LoadFromFile(const wxString &aDirectory="")
Loads the backing file from disk and then calls Load()
void SetReadOnly(bool aReadOnly)
wxString GetFullFilename() const
std::optional< ValueType > Get(const std::string &aPath) const
Fetches a value from within the JSON document.
std::vector< PARAM_BASE * > m_params
The list of parameters (owned by this object)
void registerMigration(int aOldSchemaVersion, int aNewSchemaVersion, std::function< bool(void)> aMigrator)
Registers a migration from one schema version to another.
JSON_SETTINGS(const wxString &aFilename, SETTINGS_LOC aLocation, int aSchemaVersion)
bool m_deleteLegacyAfterMigration
Whether or not to delete legacy file after migration.
std::unique_ptr< JSON_SETTINGS_INTERNALS > m_internals
void SetFilename(const wxString &aFilename)
virtual bool SaveToFile(const wxString &aDirectory="", bool aForce=false)
Calls Store() and then writes the contents of the JSON document to a file.
std::string formatFileContents()
Overlays the current store on the original file contents.
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
wxString GetFilename() const
wxString AsString() const
Stores an enum as an integer.
Like a normal param, but with custom getter and setter functions.
static void MigrateToV9Layers(nlohmann::json &aJson)
static void MigrateToNamedRenderLayers(nlohmann::json &aJson)
Represents a list of strings holding directory paths.
Stores a path as a string with directory separators normalized to unix-style.
A helper for <wxString, wxString> maps.
std::map< wxString, wxString > m_TextVars
wxString getFileExt() const override
bool m_BusAliasesDefined
An explicit alias table was loaded or set, including a deliberately empty table.
std::vector< LAYER_PAIR_INFO > m_LayerPairInfos
List of stored 3D viewports (view matrixes)
ERC_SETTINGS * m_ErcSettings
Eeschema params.
SCHEMATIC_SETTINGS * m_SchematicSettings
bool migrateSchema1To2()
Schema version 2: Bump for KiCad 9 layer numbering changes.
wxString m_BoardDrawingSheetFile
PcbNew params.
std::shared_ptr< NET_SETTINGS > m_NetSettings
Net settings for this project (owned here)
struct IP2581_BOM m_IP2581Bom
Layer pair list for the board.
wxString m_PcbLastPath[LAST_PATH_SIZE]
MRU path storage.
TEMPLATES m_TemplateFieldNames
Project and global field name templates shared by project editors.
PROJECT * m_project
A link to the owning PROJECT.
std::vector< TOP_LEVEL_SHEET_INFO > m_topLevelSheets
A list of top-level schematic sheets in this project.
std::vector< VIEWPORT > m_Viewports
List of stored layer presets.
BOARD_DESIGN_SETTINGS * m_BoardSettings
Board design settings for this project's board.
std::vector< wxString > m_PinnedDesignBlockLibs
The list of pinned design block libraries.
bool SaveAs(const wxString &aDirectory, const wxString &aFile)
std::map< wxString, std::vector< wxString > > m_BusAliases
Bus alias definitions for the schematic project.
std::vector< wxString > m_EquivalenceFiles
CvPcb params.
bool Store() override
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
bool migrateSchema2To3()
Schema version 3: move layer presets to use named render layers.
wxString getLegacyFileExt() const override
std::vector< wxString > m_PinnedFootprintLibs
The list of pinned footprint libraries.
bool LoadFromFile(const wxString &aDirectory="") override
Loads the backing file from disk and then calls Load()
bool migrateSchema3To4()
Schema version 4 distinguishes explicit empty alias tables from older default writes.
std::string SerializeToString()
Serialize to the bytes SaveToFile would write, without touching the file.
std::vector< FILE_INFO_PAIR > m_sheets
An list of schematic sheets in this project.
std::vector< wxString > m_FindByPropertiesQueries
Recent queries for Find by Properties dialog.
virtual bool MigrateFromLegacy(wxConfigBase *aCfg) override
Migrates from wxConfig to JSON-based configuration.
std::vector< LAYER_PRESET > m_LayerPresets
std::vector< FILE_INFO_PAIR > m_boards
A list of board files in this project.
std::shared_ptr< TUNING_PROFILES > m_tuningProfileParameters
Tuning profile parameters for this project.
wxArrayString m_LegacyLibNames
std::vector< wxString > m_PinnedSymbolLibs
Below are project-level settings that have not been moved to a dedicated file.
IDF_EXPORT_SETTINGS m_IdfExportSettings
IPC-2581 BOM settings.
std::vector< VIEWPORT3D > m_Viewports3D
List of stored viewports (pos + zoom)
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
Calls Store() and then writes the contents of the JSON document to a file.
PROJECT_FILE(const wxString &aFullPath)
Construct the project file for a project.
std::shared_ptr< COMPONENT_CLASS_SETTINGS > m_ComponentClassSettings
Component class settings for the project (owned here)
Container for project specific data.
static std::string ToStdString(const wxString &aStr)
static const std::string ProjectFileExtension
static const std::string LegacyProjectFileExtension
static const std::string KiCadSchematicFileExtension
void to_json(nlohmann::json &aJson, const FILE_INFO_PAIR &aPair)
void from_json(const nlohmann::json &aJson, FILE_INFO_PAIR &aPair)
const int projectFileSchemaVersion
! Update the schema version whenever a migration is required
std::pair< KIID, wxString > FILE_INFO_PAIR
For files like sheets and boards, a pair of that object KIID and display name Display name is typical...
Hold a name of a symbol's field, field value, and default visibility.
Information about a top-level schematic sheet.
KIID uuid
Unique identifier for the sheet.
wxString name
Display name for the sheet.
wxString filename
Relative path to the sheet file.
Definition of file extensions used in Kicad.