Como montar uma lisp com dcl de maneira eficiente

Vi em alguns forums que os iniciantes querem fazer lisps com dcl, mas muitas vezes não tem muita noção de como fazer isso, seja no "como" preencher os campos, controlar se eles estão disponíveis ou não, o que acontece se colocar um valor ínvalido, etc. Aí, até sai um código, mas que fica bastante complexo, para uma coisa que deveria ser bem simples... Não foram poucas as rotinas que vi, onde há "malabarismos" para obter valores, ou até mesmo setar ações para os campos de uma DCL... por exemplo, esse negócio de colocar ações mais elaboradaas via (action_tile "campo" "acao"), onde "acao" é muito mais que um simples "setq", é ruim, principlamente se você precisar manipular strings dentro da ação, ruim, porque não há como depurar o código... além de ficar feio!!! Outra coisa é empilhar (veja a parte dos set_tile), assim:
(action_tile "campo1" "(acao1)")
(
action_tile "campo2" "(acao2)")
;......
(action_tile "campon" "(acaon)")

putz... é fácil, fazer mas depois fica complicado manter, ainda mais se cada "(acaon)" é uma rotina com mais de 5 palavras.

Uma coisa que atrapalha bastante é o fato de algum campo depender de outro. Nestes casos, pelo que pude observar, é melhor deixar desabilitado o campo dependente, do que colocar na sua "action" um IF que avalie os campos dos quais ele depende. Ainda mais se tiver mais de um campo assim.

O programinha abaixo, mostrado na figura, expõe estes aspectos e, mostra soluções viáveis e simples. Tenho feito sempre assim nos meus programas, veja:
Salve esta parte como "circulo.lsp":

;função que controla as ações das keys, seta valores, erros, etc
(defun acao (key val)
  (
cond ((= key "raio") (if (> (atof val) 0)
               (
setq raio val);valor correto
               (progn
                 (alert "Erro:\nDeve ser maior que zero!!")
                 (
mode_tile "raio" 2) ;seleciona o conteudo
                 )))
    ((
= key "layer") (if (snvalid val) ;é um nome de layer válido?
               (setq layer val)
               (
progn
                 (alert "Erro:\nDeve ser preenchido!!")
                 (
mode_tile "layer" 2)
                 )))

;|acao que precisa da linha de comando, entao encerra o dcl
  com um valor  diferente de 0 (cancel) e 1 (ok)|;

    ((= key "botao") (done_dialog 2))
    ;((key "outras_coisas") (done_dialog 3))
    )
;altera o estado dos campos (habilitar ou nao)
  (modes)
  )


;função que (des)habilita keys
(defun modes nil
;condiciona o layer ao raio estar correto:
  (mode_tile "layer" (if raio 0 1))
;condiciona o botao ao layer
  (mode_tile "botao" (if layer 0 1))
;condiciona o botao OK ao raio, ao layer e a coordenada
  (mode_tile "accept" (if (and layer raio coord) 0 1)))

;função principal:
(defun c:circulo (/ ;veja que uso as variaveis com nomes iguais as keys:
        raio layer coord ;keys em forma de symbol
        pt ;variavel temporaria
        dcl dlg faz ;variaveis de controle do dcl
        key val ;variaveis de controle dos campos da dcl
        )
;inicia o dcl
  (setq dcl (load_dialog "d:/circulo.dcl")
    faz t);controla o looping
  

;|valores padrao, descomente a linha abaixo se preferir.
  veja que os uso em forma de string já, para facilitar o
  preenchimento dos campos do dcl|;

;(mapcar 'set '(raio layer coord) '("10" "circulo" "(5 3)"))
  

;enquanto FAZ:
  (while faz
;abre a tela do dcl:
    (new_dialog "circulo" dcl)

;|preenche o formulario e seta as ações
  na primeira execução estará tudo "zerado",
  se "valores padrao" nao forem setados|;

    (foreach key '("raio" "layer" "botao" "coord")
      (
action_tile key "(acao $key $value)")
      (
if (setq val (eval (read key)))
    (
set_tile key val)))
    

;(des)habilita as keys:
    (modes)
    

;inicia o formulario:
    (setq dlg (start_dialog))

    (
cond
;|acoes padrao, mesmo que voce nao faça action_tile para o
  botao "ok" e "cancel", eles fazem (done_dialog 1) e (done_dialog 0)
  respectivamente, em geral nao se muda isso mesmo|;

          ((= dlg 0) (alert "cancelado") (setq faz nil))
      ((
= dlg 1) (alert "OK") (setq faz nil))
;|a partir daqui, vão todas as ações que dependem da linha de comando
  sao as funções get*: getpoint, getcorner, getint...|;

      ((= dlg 2) (if (setq pt (getpoint "\nEntre com um ponto"))
               (
setq coord (strcat "("
                       (rtos (car pt) 2 2)
                       " "
                       (rtos (cadr pt) 2 2)
                       ")"
                       ))
               (
setq coord nil)))
      ;((dlg 3) (outras_coisas))
      )
;|se foi clicado o "botao", ele pede uma coordenada e volta ao
  new_dialog, senao encerra de vez o dcl|;

    );end while

;descarrega o dcl da memoria:
  (unload_dialog dcl)
  

;única condição válida é clicar "OK", para desenhar o círculo:
  (if (= dlg 1)
    (
entmake (list '(0 . "CIRCLE")
           (
cons 10 (read coord))
           (
cons 8 layer)
           (
cons 40 (atof raio)))))
  (
princ)
  )


Agora salve esta como "circulo.dcl":

circulo :dialog {
    label = "Desenhar circulo";
    initial_focus= "raio";
  :
boxed_column {label = "Powered by Neyton";    
    :
edit_box {label = "raio"; key = "raio" ;}
    :edit_box {label = "layer"; key = "layer" ;}
    :button {label = "pegar um ponto >"; key = "botao";}
    :text {key="coord"; label = "clique o botao";}}
  ok_cancel;
}


Ah, claro, na rotina, salvei no drive D, caso salve em outro lugar, corriga na lisp a linha (setq dcl (load_dialog "d:/circulo.dcl")... para o caminho correto
Se preferir, salve ambos na pasta do autocad, e retire o "d:/" dessa linha

O programa é bem simples e está bem comentado, a função dele é mostrar um quadro de diálogo, onde você deve informar o raio, o layer e uma coordenada, para desenhar um círculo. Sim eu sei, tudo isso, pra isso, é so um exemplo, hehehehe, que pode ser escalado para lisps/dcls bem mais complexas.

Veja que o nome do layer só fica editável, depois de informar um raio válido, assim como o botão OK só fica disponível depois de você fornecer todos os dados.

É interessante observar a sequência de eventos que deve acontecer até o "OK" ficar disponível. Existe um loopingo no meio do caminho e você pode se perguntar por que ele está ali.

Bem, quando o DCL está aberto (visível na tela), não é possível usar funções que dependem da linha de comando, tais como getpoint, getint, getcorner, a maioria das variações do ssget, o entsel, etc. Por isso, é necessário fechar o DCL, quando se precisa delas. Experimente trocar a ação do botão, adicionando esta linha:
(action_tile "botao" "(setq pt (getpoint))")
antes da linha do (start_dialog)
Salve seus desenhos, caso tenha algum aberto, e carregue a rotina.... quando você for clicar o botão, seu autocad irá travar!!! Isso mesmo, fatal error... só reiniciando o autocad!! Por que isso? sei lá, só sei que é assim.

E as variáveis, como que faz?
Isso não é regra, mas eu tenho preferido usá-las com os mesmos nomes das KEY do dcl, assim, no exemplo, a key "raio" está associada à variável raio e pra facilitar, sempre armazeno ela como uma string, algo assim: (setq raio "10.5"), só pra facilitar o preenchimento do dcl, aí quando for usar seu valor, basta um (atof raio).
No programa do exemplo, tem uma linha comentada, que mostra como seria para definir valores padrão para estas variáveis, acelerenado assim o tempo até o "ok" estar disponível.

Bem, é isso, espero que tenha ajudado!!

4 comentários:

  1. Bom não consegui de jeito nenhuma executar !

    ResponderExcluir
  2. e tu espera conseguir com um comentário desses?
    hum... seja mais específico

    ResponderExcluir
  3. Professor, bom dia. Posso utilizar tem como interagir o VBA com um programa criado em LISP, para criar caixas de diálogos pelo VBA?

    ResponderExcluir
  4. Cara, até dá, segunda a ajuda da autodesk (https://knowledge.autodesk.com/search-result/caas/CloudHelp/cloudhelp/2015/ENU/AutoCAD-AutoLISP/files/GUID-75387617-9144-49CB-97E4-03B4CD29973B-htm.html)

    Com lisp, você carrega o DVB:
    (vl-vbaload "c:/program files//sample/vba/drawline.dvb")
    "c:\\program files\\\\sample\\vba\\drawline.dvb"

    E manda rodar a macro que gerencia o seu FORM:
    (vl-vbarun "drawline")
    "drawline"

    O problema será passar valores de variáveis.
    Você poderá usar as variáveis de usuário ( "USERS1" a "USERS5" ), fazendo no lisp:
    para gravar:
    ( setvar "users1" "olá" )

    para recuperar:
    ( getvar "users1" ) para

    e no VBA:
    para definir:
    Thisdrawing.SetVariable("users1" "estou no vba!!!")

    para recuperar:
    valor = Thisdrawing.GetVariable "users1"

    Claro que você terá de fazer uma ginástica aí para passar os valores como STRING....

    ResponderExcluir