Error: #<RuntimeError: Undo operation already open>

Hello,

I’m Really stucked here. I’ve read a lot of post foum about
start_operation and commit_operation but nothings helps me for now.

What I’m trying to do is a loop with a componant creation inside usingbegin / rescue syntax in it to manage issues.
If I don’t use begin / rescue and use start_operation /commit_operation before and after the loop and it works fine.
But if I use begin rescue inside the loop the undo stack is not updated and I have this error in the console : Error: #<RuntimeError: Undo operation already open>

I tried to use “transparent operation” inside the loop but I’m still not satisfied of the results because the name of the operation doesn’t appear in undo menu. It’s the change property that appears.

Here is my code , would you help me find what’s wrong ? thanks !

	echelle_incomplete = false
	
	carto_xyzL.each{|ligne|
		
		begin
			model.start_operation("Importer une carto 3D", true, false, true)
			transformation = Geom::Transformation.new([ligne[0],ligne[1],ligne[2]])
			componentinstance = entities.add_instance(definition, transformation)			
		rescue
			 puts "erreur d'import de :", ligne
		else
			model.commit_operation
		end
		
		begin
			model.start_operation("Importer une carto 3D", true, false, true)
			componentinstance.material= format_matiere_echelle(ligne[3].to_i)
		rescue
			echelle_incomplete = true	
		else
			model.commit_operation
		end
			
	}
	
	model.commit_operation
	UI.messagebox("Warning echelle !") if echelle_incomplete

Do not put operation inside loops if instead the whole loop can go inside an operation.
(And Ruby uses 2 space indentation.)

  begin
    model.start_operation("Importer une carto 3D", true)
  
    carto_xyzL.each { |ligne|
      transformation = Geom::Transformation.new([ligne[0],ligne[1],ligne[2]])
      componentinstance = entities.add_instance(definition, transformation)      
      componentinstance.material= format_matiere_echelle(ligne[3].to_i)
    }
  rescue
    puts "erreur d'import de :", ligne
    model.abort_operation
    UI.messagebox("Warning echelle !")
  else
    model.commit_operation
  end

Use the “Keep it Simple” philosophy.

2 Likes

Thanks,

but in the code you propose It raises the same error when the component failed to add or when the materiel failed to apply.

Thats the reason why I add the begin rescue statement twice in my version.

In an other version I tried to nest two begin rescue but it was failing the same way.

I don’t understand the behevior of commit operation with begin rescue so I can not code clearly what I wanted for. Maybe I have to change my approach to avoid this issue.

Okay, let us try this …

  begin
    model.start_operation("Importer une carto 3D", true)
      #  
      carto_xyzL.each { |ligne|
        transformation = Geom::Transformation.new([ligne[0],ligne[1],ligne[2]])
        componentinstance = entities.add_instance(definition, transformation)      
        componentinstance.material= format_matiere_echelle(ligne[3].to_i)
      }
      #
    model.commit_operation
  rescue => err
    puts "erreur d'import de :", ligne
    model.abort_operation
    UI.messagebox("Warning echelle !\n#{err.inspect}")
  else
    puts "Succès"
  end

There is still the same problem with this syntax : in case of error the whole loop fail.
I’m looking for a way to manage error for each insertion of component of matérial assignation.
I still wonder if it’s possible.

Okay, let us go back to your original code and reorganize it, making sure that each transparent operation step is committed …

def import_operation(carto_xyzL)
  echelle_incomplete = false
  carto_xyzL.each { |ligne|
    begin
      model.start_operation("Importer une carto 3D", true, false, true)
        transformation = Geom::Transformation.new([ligne[0],ligne[1],ligne[2]])
        componentinstance = entities.add_instance(definition, transformation)      
      model.commit_operation
    rescue
      puts "erreur d'import de :", ligne
    else
      begin
        model.start_operation("Importer une carto 3D", true, false, true)
          componentinstance.material= format_matiere_echelle(ligne[3].to_i)
        model.commit_operation
      rescue
        echelle_incomplete = true  
      end
    end
  }
  UI.messagebox("Warning echelle !") if echelle_incomplete
end
1 Like

Than you that works !
But the name of the operation is not taken into account in the edit undo menu. Thats a minor probleme but still, I wonder why.

Finnaly your in your code you put the commit_operation into the begin statement whereas I was puting it into else.
Shall I conclude that I should put start and commit always in the same block ?

You need to ensure that the operation is always committed.
Otherwise the operation will be left open and you’ll get that exception.

That is because the initial start of the operation is attached to the previous operation.
In order to have your set of operations stand alone, you’ll need to start the operation without making it transparent with the last one. Like so using Enumerable#each_with_index

def import_operation(carto_xyzL)
  echelle_incomplete = false
  carto_xyzL.each_with_index { |ligne, i|
    begin
      if i == 0
        model.start_operation("Importer une carto 3D", true)
      else
        model.start_operation("Importer une carto 3D", true, false, true)
      end
        transformation = Geom::Transformation.new([ligne[0],ligne[1],ligne[2]])
        componentinstance = entities.add_instance(definition, transformation)      
      model.commit_operation
    rescue
      puts "erreur d'import de :", ligne
    else
      begin
        model.start_operation("Importer une carto 3D", true, false, true)
          componentinstance.material= format_matiere_echelle(ligne[3].to_i)
        model.commit_operation
      rescue
        echelle_incomplete = true  
      end
    end
  }
  UI.messagebox("Warning echelle !") if echelle_incomplete
end
1 Like

Thank you, all my questions finaly have answers :slight_smile:

1 Like