Exportar tabela do Civil 3D para HTML

Hoje vamos tirar um pouco o foco do SOLIDOS, aquele programa bacanudo, hehehe

Que tal exercitar um pouco a programação raiz? Nada de Dynamo hoje!!!

O desafio: exportar as tabelas do Civil 3D. Sim eu sei que tem um plugin na loja da Autodesk, mas você já pensou em COMO ele funciona?

Se você comprou (e leu!!!) aquele livro do programação que escrevi uns anos atrás:

Deve ter visto um “truque sujo” lá quando não tem API no Civil 3D para alguma coisa:

Explodir e listar o conteúdo.

Esta abordagem é bem interessante, pois você pode obter dados que a API não expõe diretamente, tal como:

  • Coordenadas dos PIs dos alinhamentos (usei no NOTASERV2)
  • Informações do Catchment (usei no C3DRENESG4)
  • Áreas das seções gabaritadas (usei no DDM) (boa sorte com o a propriedade AREA! PQP Autodesk!!!)

Entre outras, enfim, vamos ao código:

Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.DatabaseServices.OpenMode
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.Civil.DatabaseServices.Styles
Imports System.IO
Imports System.Environment
Imports TableStyle = Autodesk.Civil.DatabaseServices.Styles.TableStyle
Imports Table = Autodesk.Civil.DatabaseServices.Table

Public Module EXPORTTABELA
    Private ReadOnly htmlTemplate As String = "
<html>
<head>
<style>
body { font-family: Arial; }
table { border-collapse: collapse; border-style:outset; border: 1px solid black; margin: 10px; font-size: small; }
th { background-color: lightgray; padding:5px; text-align: center; border: 1px solid black ; }
td { padding:3px; border: 1px solid black ; text-align: center; align-items: center ;}
</style>
<title>{dwgname}</title>
</head>
<body>
<h2><img src='https://tbn2net.com/static/civil.png' style='width: 32px; height: 32px; border: none;'/> {dwgname}</h2>
<hr />
{tabelas}
<hr />
<p>Powered by <img src='https://tbn2net.com/static/civil.png' style='width: 16px; height: 16px; border: none;'/> <a href='https://tbn2net.com'>tbn2net</a></p>
</body>
</html>
"


    'explode 2x, pois a primeira é resulta num blockreference
    Private Function DoubleExplode(obj As Entity) As DBObjectCollection
        Dim dbo As New DBObjectCollection
        obj.Explode(dbo)
        obj = dbo(0)
        dbo = New DBObjectCollection
        obj.Explode(dbo)
        Return dbo
    End Function

    'encontra o indice da coluna
    Private Function FindColIndex(lista As List(Of Extents3d), extents As Extents3d) As Integer
        Dim medio = (extents.MaxPoint.X + extents.MinPoint.X) / 2
        Dim ext = lista.Find(Function(e) e.MinPoint.X < medio AndAlso e.MaxPoint.X > medio)
        Return lista.IndexOf(ext)
    End Function

    'encontra o indice da linha
    Private Function FindRowIndex(lista As List(Of Extents3d), extents As Extents3d) As Integer
        Dim medio = (extents.MaxPoint.Y + extents.MinPoint.Y) / 2
        Dim ext = lista.Find(Function(e) e.MinPoint.Y < medio AndAlso e.MaxPoint.Y > medio)
        Return lista.IndexOf(ext)
    End Function


    'obtem um texto que representa o conteudo da celula
    'se for mtext, é o conteudo
    'se for hatch, informa hexadecimal da cor
    'senão informa o que é; dificilmente chega aqui
    Private Function GetValueText(ent As Entity) As String
        If TypeOf ent Is MText Then Return DirectCast(ent, MText).Contents.Replace("\\P", "<br/>")

        If TypeOf ent Is Hatch Then

            Dim cor As System.Drawing.Color = DirectCast(ent, Hatch).Color.ColorValue

            Return "<div style='background: " & ConvertColorToHtml(cor) & ";width:32px;height:32px'></div>"
        End If

        Return ent.GetType.ToString
    End Function

    'obtem uma versao da cor, que pode ser adicionado ao html
    Function ConvertColorToHtml(cor As System.Drawing.Color) As String
        Return String.Format("#{0:X2}{1:X2}{2:X2}", cor.R, cor.G, cor.B)
    End Function

    'obtem o titulo da tabela
    Private Function GetTitle(tabela As Table) As String
        Dim estilo As TableStyle = tabela.StyleId.GetObject(ForWrite)
        For Each v As TableDisplayStyleType In [Enum].GetValues(GetType(TableDisplayStyleType))
            estilo.GetDisplayStylePlan(v).Visible = v = TableDisplayStyleType.TitleText
        Next
        Return GetValueText(DoubleExplode(tabela)(0))
    End Function


    'obtem a lista de Extents3d das colunas, ordenadas pelo X
    Private Function GetColsExtensions(tabela As Table) As List(Of Extents3d)
        Dim estilo As TableStyle = tabela.StyleId.GetObject(ForWrite)
        For Each v As TableDisplayStyleType In [Enum].GetValues(GetType(TableDisplayStyleType))
            estilo.GetDisplayStylePlan(v).Visible = v = TableDisplayStyleType.HeaderAreaFill
        Next
        Dim listaCols As New List(Of Extents3d)
        For Each hatch As Entity In DoubleExplode(tabela)
            listaCols.Add(hatch.GeometricExtents)
        Next
        listaCols.Sort(Function(a, b) a.MinPoint.X.CompareTo(b.MinPoint.X)) 'nao confie na ordem...
        Return listaCols
    End Function


    'obtem a lista de Extents3d das linhas dos dados, ordenadas pelo Y
    Private Function GetRowsExtensions(tabela As Table, listaCols As List(Of Extents3d)) As List(Of Extents3d)
        Dim estilo As TableStyle = tabela.StyleId.GetObject(ForWrite)
        For Each v As TableDisplayStyleType In [Enum].GetValues(GetType(TableDisplayStyleType))
            estilo.GetDisplayStylePlan(v).Visible = v = TableDisplayStyleType.DataAreaFill
        Next

        Dim listaLinhas As New List(Of Extents3d)

        For Each hacth As Entity In DoubleExplode(tabela)
            Dim indice = FindColIndex(listaCols, hacth.GeometricExtents)
            If indice = 0 Then listaLinhas.Add(hacth.GeometricExtents)
        Next

        listaLinhas.Sort(Function(a, b) b.MinPoint.Y.CompareTo(a.MinPoint.Y)) 'nao confie na ordem...

        Return listaLinhas
    End Function

    'obtem a lista dos cabeçalhos da tabela
    Private Function GetListHeaders(tabela As Table, listaCols As List(Of Extents3d)) As String()
        Dim estilo As TableStyle = tabela.StyleId.GetObject(ForWrite)
        For Each v As TableDisplayStyleType In [Enum].GetValues(GetType(TableDisplayStyleType))
            estilo.GetDisplayStylePlan(v).Visible = v = TableDisplayStyleType.HeaderText
        Next

        Dim titulos(listaCols.Count - 1) As String

        For Each b As Entity In DoubleExplode(tabela)
            Dim indiceCol = FindColIndex(listaCols, b.GeometricExtents)
            titulos(indiceCol) = GetValueText(b)
        Next

        Return titulos
    End Function

    'obtem os dados da tabela
    Private Function GetDataArray(tabela As Table, listaCols As List(Of Extents3d), listaLinhas As List(Of Extents3d)) As String(,)
        Dim estilo As TableStyle = tabela.StyleId.GetObject(ForWrite)
        For Each v As TableDisplayStyleType In [Enum].GetValues(GetType(TableDisplayStyleType))
            estilo.GetDisplayStylePlan(v).Visible = v = TableDisplayStyleType.DataText
        Next

        Dim dados(listaLinhas.Count - 1, listaCols.Count - 1) As String

        For Each b In DoubleExplode(tabela)
            Dim indiceCol = FindColIndex(listaCols, b.GeometricExtents)
            Dim indiceLinha = FindRowIndex(listaLinhas, b.GeometricExtents)
            dados(indiceLinha, indiceCol) = GetValueText(b)
        Next

        Return dados
    End Function

    'cria uma tabela HTML
    Private Function GetHtmlTable(titulo As String, cabecalhos As String(), dados As String(,)) As String
        Dim html As String = "<table>" &
                   "<tr><th colspan=" & cabecalhos.Count & ">" & titulo & "</th></tr>" & NewLine

        html &= "<tr>"
        For Each colName In cabecalhos
            html &= "<th>" & colName & "</th>" & NewLine
        Next
        html &= "</tr>"

        Dim qtdLinhas = dados.GetUpperBound(0)
        Dim qtdCols = dados.GetUpperBound(1)

        For lin = 0 To qtdLinhas
            html &= "<tr>"
            For col = 0 To qtdCols
                html &= "<td>" & dados(lin, col) & "</td>" & NewLine
            Next
            html &= "</tr>" & NewLine
        Next

        html &= "</table>"

        Return html
    End Function


    <CommandMethod("EXPORTATABELA")>
    Public Sub EXPORTATABELA()

        Dim ss = ED.GetSelection(New PromptSelectionOptions With {.MessageForAdding = "Selecione as tabelas do Civil 3D"},
                                     New SelectionFilter({New TypedValue(DxfCode.Start, "AECC_*_TABLE")}))

        If ss.Status <> PromptStatus.OK Then Exit Sub

        Dim ofd As New SaveFileDialog With {.AddExtension = True, .Filter = "Arquivo HTML|*.html", .Title = "Salvar tabela em HTML"}
        If ofd.ShowDialog <> DialogResult.OK Then Exit Sub

        Using tr = DOC.TransactionManager.StartTransaction

            Dim tabelaEmHtml As New List(Of String)

            For Each oid As ObjectId In ss.Value.GetObjectIds
                Dim tabela As Table = tr.GetObject(oid, ForWrite)

                'obtem a extensao do cabeçalho
                Dim titulo As String
                Try
                    titulo = GetTitle(tabela)
                Catch
                    'tabelas de volume geram "fantasmas". se não tem titulo, pode ignorar
                    Continue For
                End Try

                'obtem a extensao dos titulos
                Dim listaCols = GetColsExtensions(tabela)

                'obtem as alturas das linhas
                Dim listaLinhas = GetRowsExtensions(tabela, listaCols)

                'processa os cabechalhos
                Dim cabechalhos = GetListHeaders(tabela, listaCols)

                'processa os dados
                Dim dados = GetDataArray(tabela, listaCols, listaLinhas)

                'converte para html:
                Dim htm = GetHtmlTable(titulo, cabechalhos, dados)

                tabelaEmHtml.Add(htm)
            Next

            Using sw As New StreamWriter(ofd.FileName, False, System.Text.Encoding.Default)
                Dim htmlTable = htmlTemplate.Replace("{dwgname}", DOC.Name).Replace("{tabelas}", String.Join("<hr/>", tabelaEmHtml))
                sw.WriteLine(htmlTable)
            End Using

            ED.WriteMessage(NewLine & "exportado")
        End Using
    End Sub
End Module

Agora, vamos analisar a ideia.

Como eu disse, explodir é o truque. Até onde eu sei, o plugin que está na loja faz isso, já como ele processa os dados deve ser diferente, não olhei.

Se você tem a tabela de volumes, ou mesmo a tabela de análise de superfícies e a explode, vai formar um bloco do AutoCAD. Se explodir novamente, terá um monte de linhas, textos e hachuras, dependendo de como o estilo estiver configurado.

Então, podemos manipular o estilo para que “filtrar” o que será gerado pelo método Explode, por exemplo, este trecho:

Dim estilo As TableStyle = tabela.StyleId.GetObject(ForWrite)
For Each v As TableDisplayStyleType In [Enum].GetValues(GetType(TableDisplayStyleType))
     estilo.GetDisplayStylePlan(v).Visible = v = TableDisplayStyleType.TitleText
Next

Ele está na função GetTitle, que retorna o título da tabela. Se simplesmente explodir a tabela inserida no model space, vai vir um monte de “lixo”. Ajuste o estilo para aparecer APENAS o título e a explosão resultará em um MTEXT. Simples não?

Observe que a explosão da tabela primeiro gera o BlockReference principal. Aí é necessário explodir este para obter os sub-blocos. Então já deixei a função DoubleExplode preparada para fazer isso.

Vamos listar as funções do módulo:

  • Funcion DoubleExplode(obj As Entity) As DBObjectCollection
    explode um objeto ( a tabela ), pega o primeiro bloco ( na primeira explosão, só gera um item mesmo ) e explode, retornando uma coleção de objetos
  • Function FindColIndex(lista As List(Of Extents3d), extents As Extents3d) As Integer
    retorna o índice de um objeto extents3d em uma lista, buscando X médio
    é útil para localizar o índice da coluna do cabeçalho e dos dados da tabela
  • Function FindRowIndex(lista As List(Of Extents3d), extents As Extents3d) As Integer
    retorna o índice de um objeto extents3d em uma lista, buscando Y médio
    é útil para localizar o índice da linha dos dados da tabela
  • Function GetValueText(ent As Entity) As String
    Retorna uma string que representa o conteúdo de uma célula da tabela
    note que nas tabelas de análises das superfícies, não será um MTEXT, mas uma HATCH que resulta do DoubleExplode
    neste caso, cria uma string HTML para colorir a tabela. Se você for modificar o código para exportar para o Excel, terá de fornecer uma transformação na função abaixo:
  • Function ConvertColorToHtml(cor As System.Drawing.Color) As String
    Converte a cor da hachura em um formato hexadecimal válido para o html
  • Function GetTitle(tabela As Table) As String
    Retorna o título da tabela, usando a idéia de manipular o estilo para que ele só mostre a título no model space
  • Function GetColsExtensions(tabela As Table) As List(Of Extents3d)
    Retorna uma lista ordenada de objetos Extents3d, para que possamos localizar os índices das colunas
  • Function GetRowsExtensions(tabela As Table, listaCols As List(Of Extents3d)) As List(Of Extents3d)
    Retorna uma lista ordenada de objetos Extents3d, para que possamos localizar os índices das linhas
    Note que passei a lista das colunas, para não ter de processar todas as células da tabela. O código está interessado apenas na primeira coluna
  • Function GetListHeaders(tabela As Table, listaCols As List(Of Extents3d)) As String()
    Retorna a lista de textos dos cabeçalhos de colunas
    listaCols é fornecido para que se ordene a lista de textos
  • Function GetDataArray(tabela As Table, listaCols As List(Of Extents3d), listaLinhas As List(Of Extents3d)) As String(,)
    Retorna uma matriz (Array) bidimensional com os dados da tabela
    listaCols é fornecido para que se identifique o índice da coluna
    listaLinhas é fornecido para que se identifique o índice da linha
  • Function GetHtmlTable(titulo As String, cabecalhos As String(), dados As String(,)) As String
    Cria uma representação HTML da tabela
    me parece que os argumentos são óbvios, não?

Claro que nem tudo são flores. NADA me garante que explodir a tabela, gera uma lista já ORDENADA dos dados. É bem provável que sim, mas por vias das dúvidas, garanta que estão ordenados. Por isso usei LISTA e o método SORT desta.

Preferi exportar para HTML porque não obrigo o usuário a ter o EXCEL para rodar o plugin, mas nada te impede de modificar o código para fazê-lo. Aliás, tem um truque aí também, que você pode verificar lá no meu livro!!!

Ah, mas você prefere o C# ao VB? Sem problemas, acesse: https://converter.telerik.com/

Copia e cola o código lá. Ele converte para você!!!

Ah, mas eu quero fazer isso no Dynamo. Veja, o código é bem simples. o Código em C# fica bem parecido com Python. Dá pra fazer as partes que não tiver node pronto. Boa sorte!!!

Deixe um comentário

Rolar para cima