Ferramenta Python do ArcGIS - Importando scripts personalizados para a classe ToolValidator

9

Eu havia postado uma pergunta na semana passada sobre como personalizar uma classe ToolValidator e obtive algumas respostas muito boas. Ao trabalhar com as soluções propostas, criei um módulo personalizado que executa consultas em um banco de dados e será chamado pela classe ToolValidator (para fornecer valores para as listas suspensas) e posteriormente no script de geoprocessamento (para obter outras informações). parâmetros com base nos itens selecionados nas listas suspensas). No entanto, não consigo chamar o módulo personalizado na classe ToolValidator. Eu tenho tentado acrescentar ao caminho sem sorte. Quando tento aplicar essas alterações ao script, recebo um erro de tempo de execução: [Erro 9] Descritor de arquivo incorreto. Se eu comentar a linha de importação, não haverá erros.

sys.path.append('my_custom_module_directory')
import my_custom_module

Muitos de vocês podem estar se perguntando por que não apenas implemento uma ferramenta personalizada com o ArcObjects. O motivo é que meus usuários finais não têm os privilégios necessários para registrar QUALQUER dll no computador.

UPDATE: Isso estava acontecendo comigo no ArcGIS 10. Curiosamente, eu estava inicialmente acrescentando o caminho dentro da função initialiazeParameters da classe ToolValidator. Se eu fizer o acréscimo fora (ou seja, em cima) da classe ToolValidator, tudo funcionará conforme o esperado.

sys.path.append('C:/Working/SomeFolder')
import somescript -------->THIS WORKS

class ToolValidator:
  """Class for validating a tool's parameter values and controlling
  the behavior of the tool's dialog."""

  def __init__(self):
    """Setup arcpy and the list of tool parameters."""
    import arcpy
    sys.path.append('C:/Working/SomeFolder')
    import somescript -------> THIS DOESNT WORK
    self.params = arcpy.GetParameterInfo()

ATUALIZAÇÃO 2: Acho que encontrei a verdadeira causa do meu problema. Nos trechos de código desta postagem, anexei o que parecem ser caminhos reais (ex: C: / Working / SomeFolder) ao sys.path. Na minha classe ToolValidator real, eu estava construindo um caminho relativo usando os.path.dirname(__file__)+ "\ my_special_folder ...". Eu estava prevendo que os.path.dirname(__file__)retornaria o caminho da caixa de ferramentas, uma vez que contém a classe ToolValidator. Eu vim para descobrir que este não é o caso. Pelo que sei, a classe ToolValidator nunca é realmente gravada em um arquivo .py, e especulo que esse código seja passado para o interpretador python na memória, por isso __file__é inútil, ou algum script temporário é mantido e, em seguida, é executado o executável ( path_to_script) é chamado, renderizando novamente__file__sem utilidade. Tenho certeza de que há outras razões pelas quais também estou ausente.

Para encurtar a história, se eu usar um caminho codificado, o sys.append funcionará em qualquer lugar, os caminhos relativos não funcionarão tão bem na classe ToolValidator.

user890
fonte
Isso está em 9.3 ou 10?
Jason Scheirer
Estamos com problemas para reproduzir isso aqui na Esri, se isolarmos a causa, podemos suportar uma correção do 10.0 SP3. Enquanto isso, suponho que você esteja preso ao padrão de uso anterior e não ao último.
Jason Scheirer

Respostas:

3

A maneira como faço isso é, depois de iniciar o ArcGIS ou ArcCatalog, primeiro execute uma ferramenta fictícia ("Execute isso uma vez") chamando um script dummy.py. Depois disso, você pode importar scripts python no validador usando sys.argv [0]. Isso apontará para a pasta onde o primeiro script foi localizado. Depois disso, você pode importar o script necessário na classe Validator.

O script dummy.py chamado pela ferramenta "Executar isso uma vez":

import arcgisscripting, sys, os
gp = arcgisscripting.create(9.3)

# set up paths to Toolshare, scripts en Tooldata
scriptPath = sys.path[0]  
toolSharePath = os.path.dirname(scriptPath)
toolDataPath = toolSharePath + os.sep + "ToolData"
gp.addmessage("scriptPath: " + scriptPath)
gp.addmessage("toolSharePath: " + toolSharePath)
gp.addmessage("toolDataPath: " + toolDataPath)

# Use this to read properties, VERY handy!!
import ConfigParser
config = ConfigParser.SafeConfigParser()
config.readfp(open(scriptPath + os.sep + 'properties.ini'))
activeOTAP = config.get('DEFAULT', 'activeOTAP')
activeprojectspace = config.get('DEFAULT', 'activeprojectspace')
activeproject = config.get('DEFAULT', 'activeproject')
activesdeconnection = config.get('DEFAULT', 'activesdeconnection')

Desculpe, não é possível obter a formatação correta Atenciosamente, Maarten Tromp

Maarten Tromp
fonte
3

Finalmente quebrou esse bug horrível! Por exemplo, ao tentar aplicar alterações na importação de um módulo ou pacote relativo, você poderá ver o seguinte erro:

insira a descrição da imagem aqui

Opção 1:
Apenas para o desenvolvedor, adicione o caminho completo ao módulo no PYTHONPATH . Você precisará reiniciar o ArcMap / ArcCatalog antes que ele entre em vigor. Use o código abaixo para importar o módulo de um caminho relativo (para implantação). Não se preocupe, o usuário final não precisa de acréscimos à sua variável PYTHONPATH, ele funcionará!

Opção 2:
adicione uma linha adicional ao código abaixo para anexar o caminho codificado, por exemplo: sys.path.append (r "c: \ temp \ test \ scripts")
Quando estiver pronto para implantar, você terá um diretório externo, mas isso não importa, todos devem funcionar no computador do usuário final, porque o primeiro caminho que você adicionou foi o diretório relativo (nosso objetivo era apenas ultrapassar o diálogo de falha).

Código comum a ambas as opções:

import os
import sys

tbxPath = __file__.split("#")[0]
srcDirName = os.path.basename(tbxPath).rstrip(".tbx").split("__")[0] + ".src" 
tbxParentDirPath =  os.path.dirname(tbxPath)
srcDirPath = os.path.join(tbxParentDirPath, srcDirName)

sys.path.append(srcDirPath)
# sys.path.append(r"c:\temp\test\scripts")  #option2

from esdlepy.metrics.validation.LandCoverProportions import ToolValidator

Atualizar

Adeus corte e colagem do mal! Atualizei o exemplo de código para que a classe ToolValidator seja importada da biblioteca. Eu recordo e colo apenas uma vez quando os parâmetros da ferramenta são definidos pela primeira vez. Eu armazeno esse snippit de código na documentação do ToolValidator sendo importado.

Neste exemplo, o nome do diretório de origem é baseado no nome do tbx. Essa abordagem evita colisões se você tiver duas caixas de ferramentas com diretórios de origem diferentes. O padrão que usei para nomear pastas de origem é o seguinte:
TOOLBOXNAME__anything.tbx -> TOOLBOXNAME.src

Por que o "__qualquer coisa"? Como os arquivos binários não podem ser mesclados em nosso DVCS, podemos atribuir ferramentas a indivíduos e não nos preocupar em perder alterações. Quando a ferramenta é finalizada, ela é recortada e colada no mestre.

Eu também precisava acessar arquivos na pasta de origem para preencher uma lista suspensa, use este método para obter o caminho para a caixa de ferramentas de dentro do seu módulo importado:

import __main__
tbxPath = __main__.__file__.split("#")[0]
Michael Jackson
fonte
Será que o código ToolValidator está definindo o valor padrão do parâmetro? Verifique a configuração 'Valor padrão' do parâmetro nas propriedades da ferramenta de script.
blah238
Obrigado pela sugestão. Eu verifiquei e o valor padrão não está definido na caixa de ferramentas ... mas copiei a caixa de ferramentas e renomeei tudo, e o valor ainda persistiu nas duas cópias. Portanto, abandonarei minha ideia de cache e sugiro que ela possa realmente ser armazenada no arquivo .tbx, que ainda é um comportamento estranho.
MJ
2

Colocando as importações na parte superior do módulo de validação, fora da ToolValidatorclasse parece funcionar bem para mim - estou no 10.0 SP2. No entanto, não estou fazendo nada com o módulo importado em nenhum outro lugar, exceto no updateParameters.

import os
import sys
scriptDir = os.path.join(os.path.dirname(__file__.split("#")[0]), "Scripts") 
sys.path.append(scriptDir)
from someModuleInScriptDir import someFunction

class ToolValidator:
    ...
blah238
fonte
Tentei importar fora da classe ToolValidator, mas ela falharia na instrução de importação. Você estava usando um ArcCatalog recém-aberto, antes da execução de qualquer script? Eu imagino que é por isso que a ESRI está tendo dificuldade em reproduzir o erro ... isso ocorre apenas em um aplicativo recém-aberto antes da execução de qualquer script.
MJ
Funciona para mim com um ArcCatalog recém-aberto. Gostaria de saber se está importando uma classe vs uma função que é o problema?
blah238
Obrigado, você pode estar interessado em alguma coisa ... Lembro-me vagamente de um caso em que funcionou quando importou diretamente uma função, farei mais alguns testes.
MJ
Comportamento muito estranho ... funcionaria até que eu conseguisse quebrá-lo. Depois de quebrá-lo, ele consistentemente geraria um erro. O uso do PYTHONPATH na máquina do desenvolvedor ou o acréscimo de um segundo caminho codificado, conforme descrito acima, foram suficientes.
MJ
0

Consegui mover minha validação para um arquivo py importando-o e chamando-o a partir da validação da ferramenta do TBX existente. A chave estava chamando a importação dentro do construtor. Se eu chamei de fora da classe ToolValidator, a importação falhou. Aqui está o que eu tinha na guia de validação do TBX.

import arcpy
import os
import sys

class ToolValidator(object):
   """Class for validating a tool's parameter values and controlling
   the behavior of the tool's dialog."""

def __init__(self):
   """Setup arcpy and the list of tool parameters."""
   self.scriptDir = os.path.dirname(__file__.split("#")[0])
   sys.path.append(self.scriptDir)
   import ExportParcelIntersected
   self.validator = ExportParcelIntersected.ToolValidator()
   self.params = self.validator.params

 def initializeParameters(self):
   """Refine the properties of a tool's parameters.  This method is
   called when the tool is opened."""
   self.validator.initializeParameters()
   return

 def updateParameters(self):
   """Modify the values and properties of parameters before internal
   validation is performed.  This method is called whenever a parameter
   has been changed."""
   self.validator.updateParameters()
   return

 def updateMessages(self):
   """Modify the messages created by internal validation for each tool
   parameter.  This method is called after internal validation."""
   self.validator.updateMessages()
   return

Minha lógica de validação residia em ExportParcelIntersected.ToolValidator (). Onde poderia ser mantido mais fácil.

TurboGus
fonte