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!!!