From b51a3322e276953ef021932485283d1fd8e7a2c0 Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Wed, 18 Jun 2025 18:32:17 +0200 Subject: [PATCH] Fix `toggleFade` for multiple property owners, and add `openspace.propertyOwner` API function (#3721) * Add a function for getting all propertyowners matching a certain regex * Fix broken toggleFade - now works with multiple nodes * Utilize updated toggle fade function in existing toggle actions * Make `openspace.propertyOwner` work when only tag is included * And make toggleFade work in a situation where tag matches both a screenspace renderable and scene graph node * Remove mistakenly added scene graph node (it is really a screenspace renderable) * Cleanup property check code to reuse check in both property and propertyowner function --- .../toggle_all_dwarf_planet_trails.asset | 7 +- .../trails/toggle_all_minor_moon_trails.asset | 7 +- .../actions/trails/toggle_all_trails.asset | 71 +----- ...gle_all_trails_planets_moons_instant.asset | 11 +- .../trails/toggle_trails_planets_moons.asset | 29 +-- data/assets/base_keybindings.asset | 7 +- .../missions/rosetta/actions.asset | 7 +- .../missions/voyager/actions.asset | 7 +- .../solarsystem/planets/earth/earth.asset | 23 +- .../solarsystem/telescopes/jwst/actions.asset | 54 ++--- .../telescopes/jwst/targets/hudf.asset | 2 - include/openspace/engine/openspaceengine.h | 5 + include/openspace/properties/propertyowner.h | 16 +- include/openspace/query/query.h | 1 + scripts/core_scripts.lua | 134 ++++++----- src/engine/openspaceengine.cpp | 14 ++ src/properties/propertyowner.cpp | 16 ++ src/query/query.cpp | 4 + src/scene/scene.cpp | 1 + src/scene/scene_lua.inl | 214 ++++++++++++++---- 20 files changed, 334 insertions(+), 296 deletions(-) diff --git a/data/assets/actions/trails/toggle_all_dwarf_planet_trails.asset b/data/assets/actions/trails/toggle_all_dwarf_planet_trails.asset index 529e552c86..a352e3ac52 100644 --- a/data/assets/actions/trails/toggle_all_dwarf_planet_trails.asset +++ b/data/assets/actions/trails/toggle_all_dwarf_planet_trails.asset @@ -1,12 +1,7 @@ local ToggleDwarfPlanetTrails = { Identifier = "os.ToggleDwarfPlanetTrails", Name = "Toggle dwarf planet trails", - Command = [[ - local list = openspace.property("{planetTrail_dwarf}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{planetTrail_dwarf}.Renderable")]], Documentation = "Toggle on/off trails for all dwarf planets in the solar system", GuiPath = "/Trails", IsLocal = false diff --git a/data/assets/actions/trails/toggle_all_minor_moon_trails.asset b/data/assets/actions/trails/toggle_all_minor_moon_trails.asset index 0b442deed4..26add1ac2d 100644 --- a/data/assets/actions/trails/toggle_all_minor_moon_trails.asset +++ b/data/assets/actions/trails/toggle_all_minor_moon_trails.asset @@ -1,12 +1,7 @@ local ToggleMinorMoonTrails = { Identifier = "os.ToggleMinorMoonTrails", Name = "Toggle minor moon trails", - Command = [[ - local list = openspace.property("{moonTrail_minor}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{moonTrail_minor}.Renderable")]], Documentation = "Toggle on/off minor moon trails for all planets in the solar system", GuiPath = "/Trails", IsLocal = false diff --git a/data/assets/actions/trails/toggle_all_trails.asset b/data/assets/actions/trails/toggle_all_trails.asset index a9efd1fb4a..1dae3be815 100644 --- a/data/assets/actions/trails/toggle_all_trails.asset +++ b/data/assets/actions/trails/toggle_all_trails.asset @@ -2,14 +2,8 @@ local FadeUpTrails = { Identifier = "os.FadeUpTrails", Name = "Show all trails", Command = [[ - local capList = openspace.property("Scene.*Trail.Renderable.Fade") - for _,v in ipairs(capList) do - openspace.setPropertyValueSingle(v, 1, 2) - end - local list = openspace.property("Scene.*trail.Renderable.Fade") - for _,v in ipairs(list) do - openspace.setPropertyValueSingle(v, 1, 2) - end + openspace.fadeIn("Scene.*Trail.Renderable") + openspace.fadeIn("Scene.*trail.Renderable") ]], Documentation = "Fade up all enabled trails in the Scene", GuiPath = "/Trails", @@ -20,14 +14,8 @@ local FadeDownTrails = { Identifier = "os.FadeDownTrails", Name = "Hide all trails", Command = [[ - local capList = openspace.property("Scene.*Trail.Renderable.Fade") - for _,v in ipairs(capList) do - openspace.setPropertyValueSingle(v, 0, 2) - end - local list = openspace.property("Scene.*trail.Renderable.Fade") - for _,v in ipairs(list) do - openspace.setPropertyValueSingle(v, 0, 2) - end + openspace.fadeOut("Scene.*Trail.Renderable") + openspace.fadeOut("Scene.*trail.Renderable") ]], Documentation = "Fade down all enabled trails in the Scene", GuiPath = "/Trails", @@ -38,30 +26,8 @@ local ToggleTrails = { Identifier = "os.ToggleTrails", Name = "Toggle all trails", Command = [[ - local capList = openspace.property("*Trail.Renderable.Fade") - local list = openspace.property("*trail.Renderable.Fade") - if (#capList == 0) and (#list == 0) then - openspace.printWarning("No trails to toggle") - else - local prop - if #capList > 0 then - prop = capList[1] - else - prop = list[1] - end - local currentFade = openspace.propertyValue(prop) - local newFade = 0 - if currentFade < 1 then - newFade = 1 - end - if (#capList > 0) then - openspace.setPropertyValue("Scene.*Trail.Renderable.Fade", newFade, 2) - end - if (#list > 0) then - openspace.setPropertyValue("Scene.*trail.Renderable.Fade", newFade, 2) - end - openspace.setPropertyValue("Scene.*TrailEarth.Renderable.Fade", newFade, 2) - end + openspace.toggleFade("Scene.*Trail.Renderable") + openspace.toggleFade("Scene.*trail.Renderable") ]], Documentation = "Toggle fade for all trails in the Scene", GuiPath = "/Trails", @@ -72,29 +38,8 @@ local ToggleTrailsInstant = { Identifier = "os.ToggleTrailsInstant", Name = "Toggle all trails instantly", Command = [[ - local capList = openspace.property("*Trail.Renderable.Fade") - local list = openspace.property("*trail.Renderable.Fade") - if (#capList == 0) and (#list == 0) then - openspace.printWarning("No trails to toggle") - else - local prop - if #capList > 0 then - prop = capList[1] - else - prop = list[1] - end - local currentFade = openspace.propertyValue(prop) - local newFade = 0 - if currentFade < 1 then - newFade = 1 - end - if (#capList > 0) then - openspace.setPropertyValue("Scene.*Trail.Renderable.Fade", newFade) - end - if (#list > 0) then - openspace.setPropertyValue("Scene.*trail.Renderable.Fade", newFade) - end - end + openspace.toggleFade("Scene.*Trail.Renderable", 0.0) + openspace.toggleFade("Scene.*trail.Renderable", 0.0) ]], Documentation = "Toggle fade instantly for all trails in the Scene", GuiPath = "/Trails", diff --git a/data/assets/actions/trails/toggle_all_trails_planets_moons_instant.asset b/data/assets/actions/trails/toggle_all_trails_planets_moons_instant.asset index a351ddb0ef..03316770bf 100644 --- a/data/assets/actions/trails/toggle_all_trails_planets_moons_instant.asset +++ b/data/assets/actions/trails/toggle_all_trails_planets_moons_instant.asset @@ -2,15 +2,8 @@ local ToggleTrailsInstant = { Identifier = "os.ToggleTrailsInstant", Name = "Toggle planet and moon trails (instant)", Command = [[ - local list = openspace.property("{planetTrail_solarSystem}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - - local moonlist = openspace.property("{moonTrail_solarSystem}.Renderable.Enabled") - for _,v in pairs(moonlist) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end + openspace.toggleFade("{planetTrail_solarSystem}.Renderable", 0.0) + openspace.toggleFade("{moonTrail_solarSystem}.Renderable", 0.0) ]], Documentation = "Toggles the visibility of planet and moon trails", GuiPath = "/Solar System", diff --git a/data/assets/actions/trails/toggle_trails_planets_moons.asset b/data/assets/actions/trails/toggle_trails_planets_moons.asset index 0a65d1618c..bf12f34988 100644 --- a/data/assets/actions/trails/toggle_trails_planets_moons.asset +++ b/data/assets/actions/trails/toggle_trails_planets_moons.asset @@ -2,8 +2,8 @@ local FadeUpTrails = { Identifier = "os.planetsmoons.FadeUpTrails", Name = "Show planet and moon trails", Command = [[ - openspace.setPropertyValue("{planetTrail_solarSystem}.Renderable.Fade", 1, 2) - openspace.setPropertyValue("{moonTrail_solarSystem}.Renderable.Fade", 1, 2) + openspace.fadeIn("{planetTrail_solarSystem}.Renderable") + openspace.fadeIn("{moonTrail_solarSystem}.Renderable") ]], Documentation = "Fade up all planet and moon trails in the Scene", GuiPath = "/Trails", @@ -14,8 +14,8 @@ local FadeDownTrails = { Identifier = "os.planetsmoons.FadeDownTrails", Name = "Hide planet and moon trails", Command = [[ - openspace.setPropertyValue("{planetTrail_solarSystem}.Renderable.Fade", 0, 2) - openspace.setPropertyValue("{moonTrail_solarSystem}.Renderable.Fade", 0, 2) + openspace.fadeOut("{planetTrail_solarSystem}.Renderable") + openspace.fadeOut("{moonTrail_solarSystem}.Renderable") ]], Documentation = "Fade down all planet and moon trails in the Scene", GuiPath = "/Trails", @@ -26,25 +26,8 @@ local ToggleTrails = { Identifier = "os.planetsmoons.ToggleTrails", Name = "Toggle planet and moon trails", Command = [[ - local capList = openspace.property("{planetTrail_solarSystem}.Renderable.Fade") - local list = openspace.property("{moonTrail_solarSystem}.Renderable.Fade") - if (#capList == 0) and (#list == 0) then - openspace.printWarning("No trails to toggle") - else - local prop - if (#capList > 0) then - prop = capList[1] - else - prop = list[1] - end - local currentFade = openspace.propertyValue(prop) - local newFade = 0 - if currentFade < 1 then - newFade = 1 - end - openspace.setPropertyValue("{planetTrail_solarSystem}.Renderable.Fade", newFade, 2) - openspace.setPropertyValue("{moonTrail_solarSystem}.Renderable.Fade", newFade, 2) - end + openspace.toggleFade("{planetTrail_solarSystem}.Renderable.Fade") + openspace.toggleFade("{moonTrail_solarSystem}.Renderable.Fade") ]], Documentation = "Toggle fade for planet and moon trails in the Scene", GuiPath = "/Trails", diff --git a/data/assets/base_keybindings.asset b/data/assets/base_keybindings.asset index ce49ac2117..da88eafd0c 100644 --- a/data/assets/base_keybindings.asset +++ b/data/assets/base_keybindings.asset @@ -7,12 +7,7 @@ local allTrailsInstantAction = asset.require("actions/trails/toggle_all_trails") local TogglePlanetLabels = { Identifier = "os_default.TogglePlanetLabels", Name = "Toggle planet labels", - Command = [[ - local list = openspace.property("{solarsystem_labels}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{solarsystem_labels}.Renderable")]], Documentation = "Turns on visibility for all solar system labels", GuiPath = "/Solar System", IsLocal = false diff --git a/data/assets/scene/solarsystem/missions/rosetta/actions.asset b/data/assets/scene/solarsystem/missions/rosetta/actions.asset index 81b11f4dfb..aac83acb2f 100644 --- a/data/assets/scene/solarsystem/missions/rosetta/actions.asset +++ b/data/assets/scene/solarsystem/missions/rosetta/actions.asset @@ -1,12 +1,7 @@ local ToggleOuterPlanetaryTrails = { Identifier = "os.rosetta.ToggleOuterPlanetaryTrails", Name = "Toggle outer planetary trails", - Command = [[ - local list = openspace.property("{planetTrail_giants}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{planetTrail_giants}.Renderable")]], Documentation = "Toggles the visibility of all trails further from the Sun than 67P", GuiPath = "/Missions/Rosetta", IsLocal = false diff --git a/data/assets/scene/solarsystem/missions/voyager/actions.asset b/data/assets/scene/solarsystem/missions/voyager/actions.asset index 293e1d8abb..c770326132 100644 --- a/data/assets/scene/solarsystem/missions/voyager/actions.asset +++ b/data/assets/scene/solarsystem/missions/voyager/actions.asset @@ -49,12 +49,7 @@ local SaturnFocus = { local ToggleMinorMoonTrails = { Identifier = "os.voyager.ToggleMinorMoonTrails", Name = "Toggle minor trails", - Command = [[ - local list = openspace.property("{moonTrail_minor}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{moonTrail_minor}.Renderable")]], Documentation = "Toggles the trails of the minor moons", GuiPath = "/Trails", IsLocal = false diff --git a/data/assets/scene/solarsystem/planets/earth/earth.asset b/data/assets/scene/solarsystem/planets/earth/earth.asset index f9ed083e22..781200580c 100644 --- a/data/assets/scene/solarsystem/planets/earth/earth.asset +++ b/data/assets/scene/solarsystem/planets/earth/earth.asset @@ -91,14 +91,7 @@ local FocusEarth = { local ToggleSatelliteTrails = { Identifier = "os.solarsystem.ToggleSatelliteTrails", Name = "Toggle satellite trails", - Command = [[ - local list = openspace.property( - "{earth_satellites~space_stations}.Renderable.Enabled" - ) - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{earth_satellites~space_stations}.Renderable")]], Documentation = [[ Toggle trails on or off for the satellites around Earth (excluding space stations) ]], @@ -109,12 +102,7 @@ local ToggleSatelliteTrails = { local ToggleSpaceStations = { Identifier = "os.solarsystem.ToggleSpaceStations", Name = "Toggle space stations", - Command = [[ - local list = openspace.property("{space_stations}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{space_stations}.Renderable")]], Documentation = [[ Toggle visiblity of the space stations around Earth (including their trail). ]], @@ -125,12 +113,7 @@ local ToggleSpaceStations = { local ToggleSpaceStationTrails = { Identifier = "os.solarsystem.ToggleSpaceStationTrails", Name = "Toggle space station trails", - Command = [[ - local list = openspace.property("{space_stations&trails}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{space_stations&trails}.Renderable")]], Documentation = [[ Toggle visiblity of the trails for the space stations around Earth. ]], diff --git a/data/assets/scene/solarsystem/telescopes/jwst/actions.asset b/data/assets/scene/solarsystem/telescopes/jwst/actions.asset index ed9854d6f8..6b2402fe83 100644 --- a/data/assets/scene/solarsystem/telescopes/jwst/actions.asset +++ b/data/assets/scene/solarsystem/telescopes/jwst/actions.asset @@ -1,12 +1,7 @@ local ToggleLagrangianPoints = { Identifier = "os.jwst.ToggleLagrangianPoints", Name = "Toggle Lagrangian points", - Command = [[ - local list = openspace.property("{lagrange_points_earth}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{lagrange_points_earth}.Renderable")]], Documentation = "Toggle points and labels for the Lagrangian points for Earth Sun system", GuiPath = "/JWST", IsLocal = false @@ -15,11 +10,11 @@ local ToggleLagrangianPoints = { local ToggleHudf = { Identifier = "os.jwst.ToggleHudf", Name = "Toggle Hubble Ultra Deep Field", + -- The tag used here matches both a screenspace renderable and a scene graph node, + -- hence the need for the double toggleFade Command = [[ - local list = openspace.property("{mission_jwst_hudf}.*.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end + openspace.toggleFade("{mission_jwst_hudf}") + openspace.toggleFade("{mission_jwst_hudf}.Renderable") ]], Documentation = "Toggle Hubble Ultra Deep Field image and line towards its coordinate", GuiPath = "/JWST", @@ -29,12 +24,7 @@ local ToggleHudf = { local ToggleL2 = { Identifier = "os.jwst.ToggleL2", Name = "Toggle L2 line and small L2 label", - Command = [[ - local list = openspace.property("{lagrange_points_earth_l2_small}.*.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{lagrange_points_earth_l2_small}.Renderable")]], Documentation = "Toggle L2 label, point and line", GuiPath = "/JWST", IsLocal = false @@ -43,12 +33,7 @@ local ToggleL2 = { local ToggleFov = { Identifier = "os.jwst.ToggleFov", Name = "Toggle JWST field of view and view band", - Command = [[ - local list = openspace.property("{mission_jwst_fov}.*.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - ]], + Command = [[openspace.toggleFade("{mission_jwst_fov}.Renderable")]], Documentation = "Toggle James Webb Space Telecope field of view and view band", GuiPath = "/JWST", IsLocal = false @@ -82,8 +67,7 @@ local ToggleSunTrail = { Identifier = "os.jwst.ToggleSunTrail", Name = "Toggle JWST Sun trail", Command = [[ - local value = openspace.propertyValue("Scene.JWSTSunTrail.Renderable.Enabled") - openspace.setPropertyValueSingle("Scene.JWSTSunTrail.Renderable.Enabled", not value) + openspace.toggleFade("Scene.JWSTSunTrail.Renderable") ]], Documentation = "Toggle JWST trail relative to the Sun", GuiPath = "/JWST", @@ -94,15 +78,8 @@ local ToggleTrailsExceptMoon = { Identifier = "os.jwst.ToggleTrailsExceptMoon", Name = "Toggle trails (except Moon)", Command = [[ - local list = openspace.property("{planetTrail_solarSystem}.Renderable.Enabled") - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - local moonlist = openspace.property("{moonTrail_solarSystem}.Renderable.Enabled") - for _,v in pairs(moonlist) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end - openspace.setPropertyValueSingle("Scene.MoonTrail.Renderable.Enabled", true) + openspace.toggleFade("{planetTrail_solarSystem}.Renderable") + openspace.toggleFade("{moonTrail_solarSystem~moon_earth}.Renderable") ]], Documentation = "Toggle all planet and moon trails, except the Moon", GuiPath = "/JWST", @@ -113,14 +90,9 @@ local ToggleJwstTrails = { Identifier = "os.jwst.ToggleJwstTrails", Name = "Toggle JWST trail", Command = [[ - local list = { - "Scene.JWSTTrailLaunch.Renderable.Enabled", - "Scene.JWSTTrailCruise.Renderable.Enabled", - "Scene.JWSTTrailCoRevOrbit.Renderable.Enabled" - } - for _,v in pairs(list) do - openspace.setPropertyValueSingle(v, not openspace.propertyValue(v)) - end + openspace.toggleFade("Scene.JWSTTrailLaunch.Renderable") + openspace.toggleFade("Scene.JWSTTrailCruise.Renderable") + openspace.toggleFade("Scene.JWSTTrailCoRevOrbit.Renderable") ]], Documentation = "Toggle JWST launch, cruise and L2 co-revolving orbit trails, not the Sun trail", GuiPath = "/JWST", diff --git a/data/assets/scene/solarsystem/telescopes/jwst/targets/hudf.asset b/data/assets/scene/solarsystem/telescopes/jwst/targets/hudf.asset index f3e60a457c..12193c48d7 100644 --- a/data/assets/scene/solarsystem/telescopes/jwst/targets/hudf.asset +++ b/data/assets/scene/solarsystem/telescopes/jwst/targets/hudf.asset @@ -77,12 +77,10 @@ asset.onInitialize(function() openspace.addScreenSpaceRenderable(HUDFImage) openspace.addSceneGraphNode(HUDFPosition) - openspace.addSceneGraphNode(HUDFImage) openspace.addSceneGraphNode(HUDFJWSTLine) end) asset.onDeinitialize(function() openspace.removeSceneGraphNode(HUDFPosition) - openspace.removeSceneGraphNode(HUDFImage) openspace.removeSceneGraphNode(HUDFJWSTLine) openspace.removeScreenSpaceRenderable(HUDFImage) diff --git a/include/openspace/engine/openspaceengine.h b/include/openspace/engine/openspaceengine.h index 5770b91877..ea46750f00 100644 --- a/include/openspace/engine/openspaceengine.h +++ b/include/openspace/engine/openspaceengine.h @@ -135,7 +135,9 @@ public: LoadingScreen* loadingScreen(); void invalidatePropertyCache(); + void invalidatePropertyOwnerCache(); const std::vector& allProperties() const; + const std::vector& allPropertyOwners() const; void createUserDirectoriesIfNecessary(); @@ -182,6 +184,9 @@ private: mutable bool _isAllPropertiesCacheDirty = true; mutable std::vector _allPropertiesCache; + + mutable bool _isAllPropertyOwnersCacheDirty = true; + mutable std::vector _allPropertyOwnersCache; }; /** diff --git a/include/openspace/properties/propertyowner.h b/include/openspace/properties/propertyowner.h index 1849f08a28..bc34bfdea7 100644 --- a/include/openspace/properties/propertyowner.h +++ b/include/openspace/properties/propertyowner.h @@ -138,6 +138,15 @@ public: */ std::vector propertiesRecursive() const; + /** + * Returns a list of all PropertyOwners directly or indirectly owned by this + * PropertyOwner. + * + * \return A list of all PropertyOwners directly or indirectly owned by this + * PropertyOwner + */ + std::vector subownersRecursive() const; + /** * Retrieves a Property identified by \p uri from this PropertyOwner. If \p uri does * not contain a `.` it is an identifier and must refer to a Property directly owned @@ -338,9 +347,14 @@ protected: /// Collection of string tag(s) assigned to this property std::vector _tags; + /// A cached version of the full URI of this property owner, which includes the + /// identifiers of all owners + std::string _uriCache; + bool _isUriCacheDirty = true; private: - /// Will regenerate the uri caches for all directly or indirectly owned properties + /// Will regenerate the uri caches for this property owner and all directly or + /// indirectly owned properties void updateUriCaches(); }; diff --git a/include/openspace/query/query.h b/include/openspace/query/query.h index 9cee40a2ea..9af26edcd7 100644 --- a/include/openspace/query/query.h +++ b/include/openspace/query/query.h @@ -44,6 +44,7 @@ const Renderable* renderable(const std::string& name); properties::Property* property(const std::string& uri); properties::PropertyOwner* propertyOwner(const std::string& uri); const std::vector& allProperties(); +const std::vector& allPropertyOwners(); } // namespace openspace diff --git a/scripts/core_scripts.lua b/scripts/core_scripts.lua index 7a67c47675..b3a5e4e36c 100644 --- a/scripts/core_scripts.lua +++ b/scripts/core_scripts.lua @@ -206,49 +206,45 @@ openspace.fadeIn = function(identifier, fadeTime, endScript) local hasTag, _ = identifier:find("{") local hasWild, _ = identifier:find("*") + -- Check if the properties exist + local hasEnabled = true + local hasFade = true if hasTag ~= nil or hasWild ~= nil then -- Regex, several nodes local enabledPropertyList = openspace.property(enabledProperty) if next(enabledPropertyList) == nil then -- List is empty, no matches found - openspace.printError( - "Error when calling script 'openspace.fadeIn': " .. - "Could not find any property matching '" .. enabledProperty .. "'" - ) - return + hasEnabled = false end local fadePropertyList = openspace.property(fadeProperty) if next(fadePropertyList) == nil then -- List is empty, no matches found - openspace.printError( - "Error when calling script 'openspace.fadeIn': " .. - "Could not find any property matching '" .. fadeProperty .. "'" - ) - return + hasFade = false end else -- Literal, single node - local hasEnabled = openspace.hasProperty(enabledProperty) - local hasFade = openspace.hasProperty(fadeProperty) - - if not hasEnabled then - openspace.printError( - "Error when calling script 'openspace.fadeIn': " .. - "Could not find property '" .. enabledProperty .. "'" - ) - return - elseif not hasFade then - openspace.printError( - "Error when calling script 'openspace.fadeIn': " .. - "Could not find property '" .. fadeProperty .. "'" - ) - return - else + hasEnabled = openspace.hasProperty(enabledProperty) + hasFade = openspace.hasProperty(fadeProperty) + if hasEnabled then isEnabled = openspace.propertyValue(enabledProperty) end end + if not hasEnabled then + openspace.printError( + "Error when calling script 'openspace.fadeIn': " .. + "Could not find property '" .. enabledProperty .. "'" + ) + return + elseif not hasFade then + openspace.printError( + "Error when calling script 'openspace.fadeIn': " .. + "Could not find property '" .. fadeProperty .. "'" + ) + return + end + -- If node is already enabled we only have to fade it if not isEnabled then openspace.setPropertyValue(fadeProperty, 0.0) @@ -273,49 +269,45 @@ openspace.fadeOut = function(identifier, fadeTime, endScript) local hasTag, _ = identifier:find("{") local hasWild, _ = identifier:find("*") + -- Check if the properties exist + local hasEnabled = true + local hasFade = true if hasTag ~= nil or hasWild ~= nil then -- Regex, several nodes local enabledPropertyList = openspace.property(enabledProperty) if next(enabledPropertyList) == nil then -- List is empty, no matches found - openspace.printError( - "Error when calling script 'openspace.fadeIn': " .. - "Could not find any property matching '" .. enabledProperty .. "'" - ) - return + hasEnabled = false end local fadePropertyList = openspace.property(fadeProperty) if next(fadePropertyList) == nil then -- List is empty, no matches found - openspace.printError( - "Error when calling script 'openspace.fadeIn': " .. - "Could not find any property matching '" .. fadeProperty .. "'" - ) - return + hasFade = false end else -- Literal, single node - local hasEnabled = openspace.hasProperty(enabledProperty) - local hasFade = openspace.hasProperty(fadeProperty) - - if not hasEnabled then - openspace.printError( - "Error when calling script 'openspace.fadeIn': " .. - "Could not find property '" .. enabledProperty .. "'" - ) - return - elseif not hasFade then - openspace.printError( - "Error when calling script 'openspace.fadeIn': " .. - "Could not find property '" .. fadeProperty .. "'" - ) - return - else + hasEnabled = openspace.hasProperty(enabledProperty) + hasFade = openspace.hasProperty(fadeProperty) + if hasEnabled then isEnabled = openspace.propertyValue(enabledProperty) end end + if not hasEnabled then + openspace.printError( + "Error when calling script 'openspace.fadeIn': " .. + "Could not find property '" .. enabledProperty .. "'" + ) + return + elseif not hasFade then + openspace.printError( + "Error when calling script 'openspace.fadeIn': " .. + "Could not find property '" .. fadeProperty .. "'" + ) + return + end + -- If node is already disabled we don't have to do anything if isEnabled then local disableScript = "openspace.setPropertyValue('" .. enabledProperty .. "', false)" @@ -334,17 +326,37 @@ openspace.toggleFade = function(renderable, fadeTime, endScript) fadeTime = openspace.propertyValue("OpenSpaceEngine.FadeDuration") end - local enabled = openspace.propertyValue(renderable .. ".Enabled") - local fadeState = openspace.propertyValue(renderable .. ".Fade") + local renderablesList = openspace.propertyOwner(renderable) - if (enabled) then - if (fadeState < 0.5) then - openspace.fadeIn(renderable, fadeTime-(fadeTime*fadeState), endScript) - else - openspace.fadeOut(renderable, fadeTime*fadeState, endScript) + if next(renderablesList) == nil then + -- List is empty, no matches found + openspace.printError( + "Error when calling script 'openspace.toggleFade': " .. + "Could not find any property owner matching '" .. renderable .. "'" + ) + return + end + + for i = 1, #renderablesList do + local renderable = renderablesList[i] + + -- Skip any matches that do not have the properties we need + if openspace.hasProperty(renderable .. ".Enabled") and + openspace.hasProperty(renderable .. ".Fade") + then + local enabled = openspace.propertyValue(renderable .. ".Enabled") + local fadeState = openspace.propertyValue(renderable .. ".Fade") + + if (enabled) then + if (fadeState < 0.5) then + openspace.fadeIn(renderable, fadeTime-(fadeTime*fadeState), endScript) + else + openspace.fadeOut(renderable, fadeTime*fadeState, endScript) + end + else + openspace.fadeIn(renderable, fadeTime, endScript) + end end - else - openspace.fadeIn(renderable, fadeTime, endScript) end end diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index d34acdf6ad..2f0a5846be 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -1753,6 +1753,10 @@ void OpenSpaceEngine::invalidatePropertyCache() { _isAllPropertiesCacheDirty = true; } +void OpenSpaceEngine::invalidatePropertyOwnerCache() { + _isAllPropertyOwnersCacheDirty = true; +} + const std::vector& OpenSpaceEngine::allProperties() const { if (_isAllPropertiesCacheDirty) { _allPropertiesCache = global::rootPropertyOwner->propertiesRecursive(); @@ -1762,6 +1766,16 @@ const std::vector& OpenSpaceEngine::allProperties() const return _allPropertiesCache; } +const std::vector& OpenSpaceEngine::allPropertyOwners() const +{ + if (_isAllPropertyOwnersCacheDirty) { + _allPropertyOwnersCache = global::rootPropertyOwner->subownersRecursive(); + _isAllPropertyOwnersCacheDirty = false; + } + + return _allPropertyOwnersCache; +} + AssetManager& OpenSpaceEngine::assetManager() { ghoul_assert(_assetManager, "Asset Manager must not be nullptr"); return *_assetManager; diff --git a/src/properties/propertyowner.cpp b/src/properties/propertyowner.cpp index dbd3061f59..055219890d 100644 --- a/src/properties/propertyowner.cpp +++ b/src/properties/propertyowner.cpp @@ -95,6 +95,17 @@ std::vector PropertyOwner::propertiesRecursive() const { return props; } +std::vector PropertyOwner::subownersRecursive() const { + std::vector subowners = _subOwners; + + for (const PropertyOwner* owner : _subOwners) { + std::vector p = owner->subownersRecursive(); + subowners.insert(subowners.end(), p.begin(), p.end()); + } + + return subowners; +} + Property* PropertyOwner::property(const std::string& uri) const { auto it = std::find_if( _properties.begin(), @@ -323,6 +334,7 @@ void PropertyOwner::addPropertySubOwner(openspace::properties::PropertyOwner* ow updateUriCaches(); if (global::openSpaceEngine) { global::openSpaceEngine->invalidatePropertyCache(); + global::openSpaceEngine->invalidatePropertyOwnerCache(); } // Notify change so UI gets updated @@ -392,6 +404,10 @@ void PropertyOwner::removePropertySubOwner(openspace::properties::PropertyOwner* global::openSpaceEngine->invalidatePropertyCache(); } _subOwners.erase(it); + + if (global::openSpaceEngine) { + global::openSpaceEngine->invalidatePropertyOwnerCache(); + } } else { LERROR(std::format( diff --git a/src/query/query.cpp b/src/query/query.cpp index 8499b21ad1..6df0a0a01a 100644 --- a/src/query/query.cpp +++ b/src/query/query.cpp @@ -65,4 +65,8 @@ const std::vector& allProperties() { return global::openSpaceEngine->allProperties(); } +const std::vector& allPropertyOwners() { + return global::openSpaceEngine->allPropertyOwners(); +} + } // namespace openspace diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index c0f707de8e..fc84034dce 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -978,6 +978,7 @@ at the end of the interpolation. If 0 was provided, the script runs immediately. }, codegen::lua::HasProperty, codegen::lua::Property, + codegen::lua::PropertyOwner, codegen::lua::AddCustomProperty, codegen::lua::RemoveCustomProperty, codegen::lua::AddSceneGraphNode, diff --git a/src/scene/scene_lua.inl b/src/scene/scene_lua.inl index ace4c0c90e..683ea328f1 100644 --- a/src/scene/scene_lua.inl +++ b/src/scene/scene_lua.inl @@ -68,7 +68,7 @@ namespace { * and if that does not contain the requested tag, its own owners are checked recursively. */ bool ownerMatchesGroupTag(const openspace::properties::PropertyOwner* owner, - std::string_view tagToMatch) + std::string_view tagToMatch, bool recursive = true) { using namespace openspace; @@ -171,9 +171,11 @@ bool ownerMatchesGroupTag(const openspace::properties::PropertyOwner* owner, } // If we got this far, we have an owner and we haven't found a match, so we have to - // try one level higher - ghoul_assert(owner, "Owner does not exist"); - return ownerMatchesGroupTag(owner->owner(), tagToMatch); + // try one level higher if we are checking recursively + if (recursive) { + return ownerMatchesGroupTag(owner->owner(), tagToMatch); + } + return false; } // Checks to see if URI contains a group tag (with { } around the first term) @@ -229,6 +231,61 @@ std::tuple parseRegex(std::string_view } } +bool checkUriMatchFromRegexResults(std::string_view uri, + std::tuple regexResults, + std::string_view groupTag, + const openspace::properties::PropertyOwner* parentOwner + ) +{ + const bool isGroupMode = !groupTag.empty(); + auto [parentUri, identifier, isLiteral] = regexResults; + + // Literal check + if (isLiteral && uri != identifier) { + return false; + } + + if (!identifier.empty()) { + const size_t propertyPos = uri.find(identifier); + if ( + // Check if the identifier appears in the URI at all + (propertyPos == std::string::npos) || + // Check that the identifier fully matches the identifier in URI + ((propertyPos + identifier.length() + 1) < uri.length()) || + // Match parent URI + (!parentUri.empty() && uri.find(parentUri) == std::string::npos)) + { + return false; + } + + // At this point we know that the identifier matches, so another way this + // property or property owner to fail is if we provided a tag and the owner + // doesn't match it + if (isGroupMode && !ownerMatchesGroupTag(parentOwner, groupTag)) { + return false; + } + } + else if (!parentUri.empty()) { + const size_t parentPos = uri.find(parentUri); + if (parentPos == std::string::npos) { + return false; + } + + // Check tag + if (isGroupMode) { + if (!ownerMatchesGroupTag(parentOwner, groupTag)) { + return false; + } + } + else if (parentPos != 0) { + // Check that the node identifier fully matches the node in URI + return false; + } + } + + return true; +} + std::vector findMatchesInAllProperties( std::string_view regex, std::string_view groupTag) @@ -236,10 +293,10 @@ std::vector findMatchesInAllProperties( using namespace openspace; using namespace properties; - auto [nodeName, propertyIdentifier, isLiteral] = parseRegex(regex); + auto [parentUri, propertyIdentifier, isLiteral] = parseRegex(regex); const bool isGroupMode = !groupTag.empty(); - if (nodeName.empty() && isGroupMode) { + if (parentUri.empty() && isGroupMode) { isLiteral = false; } @@ -255,50 +312,76 @@ std::vector findMatchesInAllProperties( [&](Property* prop) { const std::string_view uri = prop->uri(); - if (isLiteral && uri != propertyIdentifier) { - return; + bool isMatch = checkUriMatchFromRegexResults( + uri, + { parentUri, propertyIdentifier, isLiteral }, + groupTag, + prop->owner() + ); + + if (isMatch) { + std::lock_guard g(mutex); + matches.push_back(prop); } + } + ); - if (!propertyIdentifier.empty()) { - const size_t propertyPos = uri.find(propertyIdentifier); - if ( - // Check if the propertyIdentifier appears in the URI at all - (propertyPos == std::string::npos) || - // Check that the propertyIdentifier fully matches the property in URI - ((propertyPos + propertyIdentifier.length() + 1) < uri.length()) || - // Match node name - (!nodeName.empty() && uri.find(nodeName) == std::string::npos)) - { - return; - } + return matches; +} - // At this point we know that the property name matches, so another way - // this property to fail is if we provided a tag and the owner doesn't - // match it - if (isGroupMode && !ownerMatchesGroupTag(prop->owner(), groupTag)) { +std::vector findMatchesInAllPropertyOwners( + std::string_view regex, + std::string_view groupTag) +{ + using namespace openspace; + using namespace properties; + + auto [parentUri, ownerIdentifier, isLiteral] = parseRegex(regex); + + const bool isGroupMode = !groupTag.empty(); + if (isGroupMode) { + isLiteral = false; + } + + // If we are in group mode, no parent URI is found, and there is no punctuation + // in the returned owner identifier, we only got the group - no identifier + const bool inputIsOnlyTag = isGroupMode && parentUri.empty() && + ownerIdentifier.find(".") == std::string::npos; + + const std::vector& propertyOwners = allPropertyOwners(); + + std::vector matches; + + std::mutex mutex; + std::for_each( + std::execution::par_unseq, + propertyOwners.cbegin(), + propertyOwners.cend(), + [&](PropertyOwner* propOwner) { + if (inputIsOnlyTag) { + // If we only got a tag as input, the result is all owners that directly + // match the group tag (without looking recusively in parent owners) + if (!ownerMatchesGroupTag(propOwner, groupTag, false)) { return; } } - else if (!nodeName.empty()) { - const size_t nodePos = uri.find(nodeName); - if (nodePos == std::string::npos) { - return; - } + else { + const std::string uri = propOwner->uri(); - // Check tag - if (isGroupMode) { - if (!ownerMatchesGroupTag(prop->owner(), groupTag)) { - return; - } - } - else if (nodePos != 0) { - // Check that the node identifier fully matches the node in URI + bool isMatch = checkUriMatchFromRegexResults( + uri, + { parentUri, ownerIdentifier, isLiteral }, + groupTag, + propOwner->owner() + ); + + if (!isMatch) { return; } } std::lock_guard g(mutex); - matches.push_back(prop); + matches.push_back(propOwner); } ); @@ -631,15 +714,17 @@ namespace { * `uri` identifies the property or properties that are returned by this function and can * include both wildcards `*` which match anything, as well as tags (`{tag}`) which match * scene graph nodes that have this tag. There is also the ability to combine two tags - * through the `&`, `|`, and `~` operators. `{tag1&tag2}` will match anything that has the - * tag1 and the tag2. `{tag1|tag2}` will match anything that has the tag1 or the tag 2, - * and `{tag1~tag2}` will match anything that has tag1 but not tag2. If no wildcards or - * tags are provided at most one property value will be changed. With wildcards or tags - * all properties that match the URI are changed instead. + * through the `&`, `|`, and `~` operators. `{tag1&tag2}` will match anything that has + * both tags `tag1` and `tag2`. `{tag1|tag2}` will match anything that has `tag1` or + * `tag2`, and `{tag1~tag2}` will match anything that has `tag1` but not `tag2`. If no + * wildcards or tags are provided at most one property identifier will be returned. With + * wildcards or tags, the identifiers of all properties that match the URI are returned + * instead. * - * \param uri The URI that identifies the property or properties whose values should be - * changed. The URI can contain 0 or 1 wildcard `*` characters or a tag - * expression (`{tag}`) that identifies a property owner. + * \param uri The URI that identifies the property or properties to get. The URI can + * contain 0 or 1 wildcard `*` characters or a tag expression (`{tag}`) that + * identifies a property owner. + * \ return A list of property URIs */ [[codegen::luawrap]] std::vector property(std::string uri) { using namespace openspace; @@ -660,6 +745,43 @@ namespace { return matches; } +/** + * Returns a list of property owner identifiers that match the passed regular expression. + * The `uri` identifies the property owner or owner that are returned by this function and + * can include both wildcards `*` which match anything, as well as tags (`{tag}`) which + * match scene graph nodes that have this tag. There is also the ability to combine two + * tags through the `&`, `|`, and `~` operators. `{tag1&tag2}` will match anything that + * has both tags `tag1` and `tag2`. `{tag1|tag2}` will match anything that has the tag + * `tag1` or `tag2`, * and `{tag1~tag2}` will match anything that has `tag1` but not + * `tag2`. If no wildcards or tags are provided at most one property owner identifier + * will be returned. With wildcards or tags, the identifiers of all property owners that + * match the URI are returned instead. + * + * \param uri The URI that identifies the property owner or owners to get. The URI can + * contain 0 or 1 wildcard `*` characters or a tag expression (`{tag}`) that + * identifies a property owner. + * \ return A list of property owner URIs + */ +[[codegen::luawrap]] std::vector propertyOwner(std::string uri) { + using namespace openspace; + + std::string tag = groupTag(uri); + if (!tag.empty()) { + // Remove group name from start of regex and replace with '*' + uri = removeGroupTagFromUri(uri); + } + + std::vector owners = + findMatchesInAllPropertyOwners(uri, tag); + + std::vector matches; + matches.reserve(owners.size()); + for (properties::PropertyOwner* owner : owners) { + matches.emplace_back(owner->uri()); + } + return matches; +} + /** * Loads the SceneGraphNode described in the table and adds it to the SceneGraph. */