6 Replies Latest reply on Nov 27, 2015 12:01 PM by johnrellis

    Copy develop settings from one photo to another: having problems! photo:getDevelopSettings LrDevelopController.setValue

    roryj2010499 Level 1

      I've also attached the full code at the bottom of this message in case that helps, but, to summarize,

       

      I have a lua plugin (MIDI2LR by rsjaffe) that receives MIDI messages from an application through a LrSocket. Everything has been working, but I've tried to add copy/paste develop settings without success.

       

      In the following code, the copy settings is:

      function copySettings()

          local photo = LrApplication.activeCatalog():getTargetPhoto()

          settings = photo:getDevelopSettings()

      end

      and the paste settings is:

      function pasteSettings()

          applySettings(settings) 

      end


      function applySettings(set) --still experimental

          if LrApplicationView.getCurrentModuleName() ~= 'develop' then

                  LrApplicationView.switchToModule('develop')

          end

          for x,v in pairs(set) do

      --      SERVER:send(string.format('%s %d\n', x, develop_lerp_to_midi(v)))

      --      PARAM_OBSERVER[x] = v

            LrDevelopController.setValue(x,v)

          end

      end


      The functions do get called, but nothing happens to the target photo. I've also been unable to attach a debugger (tried ZeroBrane studio as described in Debugging Adobe Lightroom plugins with ZeroBrane Studio - ZeroBrane but can't get Lightroom to load mobdebug.lrmodule.


      Can someone point out where I'm going wrong?

      Thanks.


       

       

       

       

      require 'strict.lua' -- catch some incorrect variable names

      require 'Develop_Params.lua' -- global table of develop params we need to observe

      local LrApplication      = import 'LrApplication'

      local LrApplicationView  = import 'LrApplicationView'

      local LrDevelopController = import 'LrDevelopController'

      local LrFunctionContext  = import 'LrFunctionContext'

      local LrSelection        = import 'LrSelection'

      local LrShell            = import 'LrShell'

      local LrSocket            = import 'LrSocket'

      local LrTasks            = import 'LrTasks'

      local LrUndo              = import 'LrUndo'

       

       

      -- File-local consts

      local RECEIVE_PORT = 58763

      local SEND_PORT    = 58764

      local PICKUP_THRESHOLD = 4

       

       

      -- File-local vars

      local CopyUUID

      local settings

      local LAST_PARAM = ''

      local PARAM_OBSERVER = {}

      local PICKUP_ENABLED = true

      local SERVER = {}

       

       

      --File-local function declarations, advance declared to allow it to be in scope for all calls.

      --When defining pre-declared function, DO NOT USE local KEYWORD again, as it will define yet another local function.

      --These declaration are intended to get around some Lua gotcha's.

      local applySettings

      local copySettings

      local develop_lerp_to_midi

      local midi_lerp_to_develop

      local pasteSettings

      local processMessage

      local sendChangedParams

      local startServer

      local updateParam

       

       

      local ACTIONS = {

       

       

          ['DecrementLastDevelopParameter'] = function () LrDevelopController.decrement(LAST_PARAM) end,

          ['VirtualCopy']      = function () LrApplication.activeCatalog():createVirtualCopies() end,

          ['ToggleScreenTwo']  = LrApplicationView.toggleSecondaryDisplay,

          ['CopySettings']    = copySettings,

          ['PasteSettings']    = pasteSettings,

      }

       

       

      local TOOL_ALIASES = {

          ['Loupe']          = 'loupe',

          ['CropOverlay']    = 'crop',

          ['SpotRemoval']    = 'dust',

          ['RedEye']          = 'redeye',

          ['GraduatedFilter'] = 'gradient',

          ['RadialFilter']    = 'circularGradient',

          ['AdjustmentBrush'] = 'localized',

      }

       

       

      local SETTINGS = {

          ['Pickup'] = function(enabled) PICKUP_ENABLED = (enabled == 1) end,

      }

       

       

      function copySettings()

          local photo = LrApplication.activeCatalog():getTargetPhoto()

          settings = photo:getDevelopSettings()

      end

       

       

      function pasteSettings()

          applySettings(settings) 

      end

       

       

      function midi_lerp_to_develop(param, midi_value)

          -- map midi range to develop parameter range

          local min,max = LrDevelopController.getRange(param)

      --    if(param == 'Temperature') then

      --        min = 3000

      --        max = 9000

      --    end

         

          local result = midi_value/127 * (max-min) + min

          return result

      end

       

       

      function develop_lerp_to_midi(param)

          -- map develop parameter range to midi range

          local min, max = LrDevelopController.getRange(param)

      --    if(param == 'Temperature') then

      --        min = 3000

      --        max = 9000

      --    end

         

          local result = (LrDevelopController.getValue(param)-min)/(max-min) * 127

          return result

      end

       

       

      function updateParam(param, midi_value)

          -- this function does a 'pickup' type of check

          -- that is, it will ensure the develop parameter is close

          -- to what the inputted command value is before updating it

          if LrApplicationView.getCurrentModuleName() ~= 'develop' then

                  LrApplicationView.switchToModule('develop')

          end

         

          if((not PICKUP_ENABLED) or (math.abs(midi_value - develop_lerp_to_midi(param)) <= PICKUP_THRESHOLD)) then

              PARAM_OBSERVER[param] = midi_lerp_to_develop(param, midi_value)

              LrDevelopController.setValue(param, midi_lerp_to_develop(param, midi_value))

              LAST_PARAM = param

          end

      end

       

       

      function applySettings(set) --still experimental

          if LrApplicationView.getCurrentModuleName() ~= 'develop' then

                  LrApplicationView.switchToModule('develop')

          end

          for x,v in pairs(set) do

      --      SERVER:send(string.format('%s %d\n', x, develop_lerp_to_midi(v)))

      --      PARAM_OBSERVER[x] = v

            LrDevelopController.setValue(x,v)

          end

      end

       

       

      -- message processor

      function processMessage(message)

          if type(message) == 'string' then

              -- messages are in the format 'param value'

              local _, _, param, value = string.find( message, '(%S+)%s(%d+)' )

           

              if(ACTIONS[param] ~= nil) then -- perform a one time action

                  if(tonumber(value) == 127) then ACTIONS[param]() end

              elseif(param:find('Reset') == 1) then -- perform a reset other than those explicitly coded in ACTIONS array

                if(tonumber(value) == 127) then LrDevelopController.resetToDefault(param:sub(6)) end

              elseif(param:find('SwToM') == 1) then -- perform a switch to module

                  if(tonumber(value) == 127) then LrApplicationView.switchToModule(param:sub(6)) end

              elseif(param:find('ShoVw') == 1) then -- change application's view mode

                  if(tonumber(value) == 127) then LrApplicationView.showView(param:sub(6)) end

              elseif(param:find('ShoScndVw') == 1) then -- change application's view mode

                  if(tonumber(value) == 127) then LrApplicationView.showSecondaryView(param:sub(10)) end

              elseif(TOOL_ALIASES[param] ~= nil) then -- switch to desired tool

                  if(tonumber(value) == 127) then

                      if(LrDevelopController.getSelectedTool() == TOOL_ALIASES[param]) then -- toggle between the tool/loupe

                          LrDevelopController.selectTool('loupe')

                      else

                          LrDevelopController.selectTool(TOOL_ALIASES[param])

                      end

                  end

              elseif(SETTINGS[param] ~= nil) then

                  SETTINGS[param](tonumber(value))

              else -- otherwise update a develop parameter

                  updateParam(param, tonumber(value))

              end

          end

      end

       

       

      -- send changed parameters to MIDI2LR

      function sendChangedParams( observer )

          for _, param in ipairs(DEVELOP_PARAMS) do

              if(observer[param] ~= LrDevelopController.getValue(param)) then

                  SERVER:send(string.format('%s %d\n', param, develop_lerp_to_midi(param)))

                  observer[param] = LrDevelopController.getValue(param)

                  LAST_PARAM = param

              end

          end

      end

       

       

      function startServer(context)

          SERVER = LrSocket.bind {

                functionContext = context,

                plugin = _PLUGIN,

                port = SEND_PORT,

                mode = 'send',

                onClosed = function( socket ) -- this callback never seems to get called...

                  -- MIDI2LR closed connection, allow for reconnection

                  -- socket:reconnect()

                end,

                onError = function( socket, err )

                  socket:reconnect()

                end,

              }

      end

       

       

      -- Main task

      LrTasks.startAsyncTask( function()

          LrFunctionContext.callWithContext( 'socket_remote', function( context )

              LrDevelopController.revealAdjustedControls( true ) -- reveal affected parameter in panel track

             

              -- add an observer for develop param changes

              LrDevelopController.addAdjustmentChangeObserver( context, PARAM_OBSERVER, sendChangedParams )

             

              local client = LrSocket.bind {

                  functionContext = context,

                  plugin = _PLUGIN,

                  port = RECEIVE_PORT,

                  mode = 'receive',

                  onMessage = function(socket, message)

                      processMessage(message)

                  end,

                  onClosed = function( socket )

                      -- MIDI2LR closed connection, allow for reconnection

                      socket:reconnect()

                     

                      -- calling SERVER:reconnect causes LR to hang for some reason...

                      SERVER:close()

                      startServer(context)

                  end,

                  onError = function(socket, err)

                      if err == 'timeout' then -- reconnect if timed out

                          socket:reconnect()

                      end

                  end

              }

             

              startServer(context)

             

              while true do

                  LrTasks.sleep( 1/2 )

              end

             

              client:close()

              SERVER:close()

          end )

      end )

       

      LrTasks.startAsyncTask( function()

          if(WIN_ENV) then

              LrShell.openFilesInApp({_PLUGIN.path..'/Info.lua'}, _PLUGIN.path..'/MIDI2LR.exe')

          else

              LrShell.openFilesInApp({_PLUGIN.path..'/Info.lua'}, _PLUGIN.path..'/MIDI2LR.app') -- On Mac it seems like the files argument has to include an existing file

          end

      end)

        • 1. Re: Copy develop settings from one photo to another: having problems! photo:getDevelopSettings LrDevelopController.setValue
          johnrellis Most Valuable Participant

          Re debugging: You absolutely need a more functional method of debugging than print statements.  See this thread for how someone else got ZeroBrane to work for them: Interactive debugger for Lightroom plugins.

           

          If that doesn't work, you can use my free Debugging Toolkit for Lightroom SDK. It's not as capable as a full-fledged IDE, but it's much better than print statements.

          • 2. Re: Copy develop settings from one photo to another: having problems! photo:getDevelopSettings LrDevelopController.setValue
            johnrellis Most Valuable Participant

            Re your use of LrDevelopController: I haven't seen much experience posted here yet.  It's not clear from the documentation whether the client needs to call LrDevelopController.startTracking() / stopTracking() around a call to setValue().

             

            There is also the undocumented photo:applyDevelopSettings().   However, if you are connecting to a MIDI controller for interactive use, LrDevelopController looks more appropriate, with regard to history states and control over interactive updates of the display.

            • 3. Re: Copy develop settings from one photo to another: having problems! photo:getDevelopSettings LrDevelopController.setValue
              StefanKeller42 Level 1

              there are some mistakes:

              1. getDevelopSettings has to be called in an AsyncTask

              2. Values are numbers, booleans and strings.

                 booleans can be changed, but I guess string settings (as ToneCurveName) are no way suported now

              3.some Values with 2012 in the name are not working, at least Exposure

               

              the SDK says:

              WARNING:The develop settings APIs are considered experimental. You should not depend on the contents of the settings table remaining compatible in future versions of Lightroom. The definitive list is the one shown in the UI.

               

              my source based on yours:

              you may map the 2012 settings to the correct ones...

               

              local settings

               

              function copySettings()

                LrTasks.startAsyncTask ( function ()

                  local photo = LrApplication.activeCatalog():getTargetPhoto()

                  settings = photo:getDevelopSettings()

                end )

              end

               

              function pasteSettings()

                 applySettings(settings)

              end

               

              function applySettings(set) -- not working eg with 2012 Settings

                  for x,v in pairs(set) do

                    if type (v) == "boolean"

                      then

                        if v then v=1 else v=0 end

                      end

               

                    if type (v) == "number"

                      then

                        --      SERVER:send(string.format('%s %d\n', x, develop_lerp_to_midi(v)))

                        --      PARAM_OBSERVER[x] = v

               

                      --dbgout (string.format('%s is %d', x, v))

                      LrDevelopController.setValue(x,v)

                      end

               

                    if type (v) == "string"

                      then

                      -- ??????

                      end

                  end

              end

              • 4. Re: Copy develop settings from one photo to another: having problems! photo:getDevelopSettings LrDevelopController.setValue
                StefanKeller42 Level 1

                this may be a correct way, it seems to work

                 

                 

                local settings

                 

                function copySettings()

                  LrTasks.startAsyncTask ( function ()

                    local photo = LrApplication.activeCatalog():getTargetPhoto()

                    settings = photo:getDevelopSettings()

                  end )

                end

                 

                function pasteSettings()

                  if settings ~= nil then

                    LrTasks.startAsyncTask ( function ()

                      catalog = LrApplication.activeCatalog()

                      catalog:withWriteAccessDo( "pasteSettings", function()

                        local photo = catalog:getTargetPhoto()

                        photo:applyDevelopSettings(settings)

                      end )

                    end )

                  end

                end

                • 5. Re: Copy develop settings from one photo to another: having problems! photo:getDevelopSettings LrDevelopController.setValue
                  roryj2010499 Level 1

                  Thank you very much for your assistance. The final version of the code follows.

                   

                  To find out what was wrong, I put in debug print statements -- that made it clear that problem #1 was Lightroom doesn't completely unload one version of a long-running plugin when you reload a new version! I was making edits and reloading the plugin in LR. However, the main plugin task thread runs continuously in my plugin, and LR doesn't terminate that thread when it reloads a plugin--so I'd either have to, in the main plugin task thread, monitor for plugin termination, or restart LR to test the changes.

                   

                  local function PasteSettings  ()

                    LrTasks.startAsyncTask ( function ()

                        LrApplication.activeCatalog():withWriteAccessDo(

                          'Paste settings',

                          function() LrApplication.activeCatalog():getTargetPhoto():applyDevelopSettings(MIDI2LR.Copied_Settin gs) end,

                          { timeout = 4,

                            callback = function() LrDialogs.showError('Unable to get catalog write access for copy settings') end,

                            asynchronous = true }

                        )

                      end )

                  end

                   

                   

                  local function CopySettings ()

                    LrTasks.startAsyncTask (

                      function () MIDI2LR.Copied_Settings = LrApplication.activeCatalog():getTargetPhoto():getDevelopSettings() end

                    )

                  end

                  1 person found this helpful
                  • 6. Re: Copy develop settings from one photo to another: having problems! photo:getDevelopSettings LrDevelopController.setValue
                    johnrellis Most Valuable Participant

                    Lightroom doesn't completely unload one version of a long-running plugin when you reload a new version! ...so I'd either have to, in the main plugin task thread, monitor for plugin termination, or restart LR to test the changes.

                    I believe you can also disable and re-enable the plugin, but I'm not 100% sure about that.

                     

                    The technique I use for handling reloads is pretty simple.  The .lua file defining the background task defines a global variable that gets incremented every time the file gets loaded:

                     

                    currentLoadVersion = rawget (_G, "currentLoadVersion") or 0
                        --[[ Incremented every time the module is loaded. Avoid triggering
                        "undeclared variable" from strict.lua.]]
                    
                    

                     

                    The use of "rawget" ensures that strict.lua won't think there's an access to an undeclared variable.  (You should definitely use strict.lua, if you're not.)

                     

                    When it first starts, the background task increments "currentLoadVersion" and then periodically checks for changes to its value, indicating the file has been reloaded:

                     

                    LrTasks.startAsyncTask (Debug.showErrors (function ()
                        currentLoadVersion = currentLoadVersion + 1
                        local loadVersion = currentLoadVersion
                        while (loadVersion == currentLoadVersion) do
                            ...do some work...
                            LrTasks.sleep (...)
                            end
                    
                    

                     

                    Info.lua also lets you define scripts that get invoked when the plugin is reloaded or when LR itself is shutting down.  But they need to communicate with the background task using a similar mechanism, with a global counter that the background task periodically checks.