Importing XML mesh data

I’m trying to figure out how to create a face with holes from imported data. The data has the potential to be complicated and I need to make it versatile so it can work with any file.

Here is my sample xml file.
<?xml version="1.0" encoding="UTF-8"?>
<DATA_EXPORT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <LOCATION address="14685 U.S. 224, Van Wert OH  45891" lat="40.92415722243234" long="-84.54056337475778"/>
  <STRUCTURES>
    <ROOF id="ROOF1">
      <FACES>
        <FACE id="F1" type="ROOF" name="RF-1">
          <POLYGON id="P1" path="L1,L2,L3,L4" size="3.2060012921337275" pitch="17"/>
        </FACE>
        <FACE id="F2" type="ROOF" name="RF-2">
          <POLYGON id="P2" path="L5,L6,L7,L8" size="2.4168373719967726" pitch="16"/>
        </FACE>
        <FACE id="F3" type="ROOF" name="RF-3">
          <POLYGON id="P3" path="L9,L10,L11,L12" size="2.7098196305321696" pitch="16"/>
        </FACE>
        <FACE id="F4" type="ROOF" name="RF-4">
          <POLYGON id="P4" path="L13,L14,L15" size="25.257929282695628" pitch="10"/>
        </FACE>
        <FACE id="F5" type="ROOF" name="RF-5">
          <POLYGON id="P5" path="L16,L14,L17,L18,L19,L20,L21,L22,L23,L24,L25,L26,L27,L28,L29,L30,L31,L32,L33,L34,L35,L36,L37,L38,L39" size="839.817469986963" pitch="12"/>
        </FACE>
        <FACE id="F6" type="ROOF" name="RF-6">
          <POLYGON id="P6" path="L17,L13,L40" size="25.276322988876863" pitch="10"/>
        </FACE>
        <FACE id="F7" type="ROOF" name="RF-7">
          <POLYGON id="P7" path="L41,L42,L43,L44,L45" size="379.6890197075424" pitch="12"/>
        </FACE>
        <FACE id="F8" type="ROOF" name="RF-8">
          <POLYGON id="P8" path="L46,L47,L48,L49,L50,L51,L52,L53,L54" size="372.5245083256959" pitch="12"/>
        </FACE>
        <FACE id="F9" type="ROOF" name="RF-9">
          <POLYGON id="P9" path="L55,L56,L57,L58" size="49.769822247225825" pitch="12"/>
        </FACE>
        <FACE id="F10" type="ROOF" name="RF-10">
          <POLYGON id="P10" path="L59,L60,L61,L33" size="37.367293074030044" pitch="12"/>
        </FACE>
        <FACE id="F11" type="ROOF" name="RF-11">
          <POLYGON id="P11" path="L23,L62,L63,L64" size="37.3672930740305" pitch="12"/>
        </FACE>
        <FACE id="F12" type="ROOF" name="RF-12">
          <POLYGON id="P12" path="L57,L65,L66,L67" size="49.7698222472263" pitch="12"/>
        </FACE>
        <FACE id="F13" type="ROOF" name="RF-13">
          <POLYGON id="P13" path="L32,L68,L69,L59" size="37.367293074030506" pitch="12"/>
        </FACE>
        <FACE id="F14" type="ROOF" name="RF-14">
          <POLYGON id="P14" path="L64,L70,L71,L24" size="37.36729307402987" pitch="12"/>
        </FACE>
        <FACE id="F15" type="ROOF" name="RF-15">
          <POLYGON id="P15" path="L45,L72,L73,L74" size="379.68901970754484" pitch="12"/>
        </FACE>
        <FACE id="F16" type="ROOF" name="RF-16">
          <POLYGON id="P16" path="L75,L76,L47,L77,L78,L79,L80,L81" size="372.5245083256973" pitch="12"/>
        </FACE>
        <FACE id="F17" type="ROOF" name="RF-17">
          <POLYGON id="P17" path="L82,L83,L20,L84" size="1004.6592049315932" pitch="12"/>
        </FACE>
        <FACE id="F18" type="ROOF" name="RF-18">
          <POLYGON id="P18" path="L3,L85,L86" size="1.4174791631200416" pitch="14"/>
        </FACE>
        <FACE id="F19" type="ROOF" name="RF-19">
          <POLYGON id="P19" path="L1,L87,L88" size="1.4174791631200478" pitch="14"/>
        </FACE>
      </FACES>
      <LINES>
        <LINE id="L1" type="HIP" path="C1,C2"/>
        <LINE id="L2" type="FLASHING" path="C3,C2"/>
        <LINE id="L3" type="HIP" path="C4,C3"/>
        <LINE id="L4" type="EAVE" path="C4,C1"/>
        <LINE id="L5" type="EAVE" path="C5,C6"/>
        <LINE id="L6" type="RAKE" path="C6,C7"/>
        <LINE id="L7" type="FLASHING" path="C7,C8"/>
        <LINE id="L8" type="OTHER" path="C8,C5"/>
        <LINE id="L9" type="FLASHING" path="C9,C10"/>
        <LINE id="L10" type="RAKE" path="C9,C11"/>
        <LINE id="L11" type="EAVE" path="C11,C12"/>
        <LINE id="L12" type="OTHER" path="C12,C10"/>
        <LINE id="L13" type="RIDGE" path="C13,C14"/>
        <LINE id="L14" type="VALLEY" path="C13,C15"/>
        <LINE id="L15" type="RAKE" path="C15,C14"/>
        <LINE id="L16" type="EAVE" path="C16,C15"/>
        <LINE id="L17" type="VALLEY" path="C17,C13"/>
        <LINE id="L18" type="EAVE" path="C17,C18"/>
        <LINE id="L19" type="RAKE" path="C18,C19"/>
        <LINE id="L20" type="RIDGE" path="C19,C20"/>
        <LINE id="L21" type="RAKE" path="C20,C16"/>
        <LINE id="L22" type="OTHER" path="C21,C22"/>
        <LINE id="L23" type="VALLEY" path="C23,C21"/>
        <LINE id="L24" type="VALLEY" path="C24,C23"/>
        <LINE id="L25" type="OTHER" path="C25,C24"/>
        <LINE id="L26" type="OTHER" path="C26,C25"/>
        <LINE id="L27" type="STEPFLASH" path="C27,C26"/>
        <LINE id="L28" type="FLASHING" path="C28,C27"/>
        <LINE id="L29" type="STEPFLASH" path="C29,C28"/>
        <LINE id="L30" type="OTHER" path="C22,C29"/>
        <LINE id="L31" type="OTHER" path="C30,C31"/>
        <LINE id="L32" type="VALLEY" path="C32,C30"/>
        <LINE id="L33" type="VALLEY" path="C33,C32"/>
        <LINE id="L34" type="OTHER" path="C34,C33"/>
        <LINE id="L35" type="OTHER" path="C35,C34"/>
        <LINE id="L36" type="STEPFLASH" path="C36,C35"/>
        <LINE id="L37" type="FLASHING" path="C37,C36"/>
        <LINE id="L38" type="STEPFLASH" path="C38,C37"/>
        <LINE id="L39" type="OTHER" path="C31,C38"/>
        <LINE id="L40" type="RAKE" path="C14,C17"/>
        <LINE id="L41" type="STEPFLASH" path="C39,C40"/>
        <LINE id="L42" type="RAKE" path="C40,C41"/>
        <LINE id="L43" type="EAVE" path="C42,C41"/>
        <LINE id="L44" type="RAKE" path="C42,C43"/>
        <LINE id="L45" type="RIDGE" path="C39,C43"/>
        <LINE id="L46" type="STEPFLASH" path="C44,C45"/>
        <LINE id="L47" type="RIDGE" path="C46,C44"/>
        <LINE id="L48" type="RAKE" path="C46,C47"/>
        <LINE id="L49" type="EAVE" path="C47,C48"/>
        <LINE id="L50" type="RAKE" path="C45,C48"/>
        <LINE id="L51" type="STEPFLASH" path="C49,C50"/>
        <LINE id="L52" type="OTHER" path="C51,C49"/>
        <LINE id="L53" type="STEPFLASH" path="C52,C51"/>
        <LINE id="L54" type="FLASHING" path="C50,C52"/>
        <LINE id="L55" type="EAVE" path="C53,C54"/>
        <LINE id="L56" type="RAKE" path="C55,C54"/>
        <LINE id="L57" type="RIDGE" path="C55,C56"/>
        <LINE id="L58" type="STEPFLASH" path="C56,C53"/>
        <LINE id="L59" type="RIDGE" path="C32,C57"/>
        <LINE id="L60" type="RAKE" path="C58,C57"/>
        <LINE id="L61" type="EAVE" path="C58,C33"/>
        <LINE id="L62" type="EAVE" path="C21,C59"/>
        <LINE id="L63" type="RAKE" path="C59,C60"/>
        <LINE id="L64" type="RIDGE" path="C60,C23"/>
        <LINE id="L65" type="RAKE" path="C61,C55"/>
        <LINE id="L66" type="EAVE" path="C61,C62"/>
        <LINE id="L67" type="STEPFLASH" path="C62,C56"/>
        <LINE id="L68" type="EAVE" path="C30,C63"/>
        <LINE id="L69" type="RAKE" path="C57,C63"/>
        <LINE id="L70" type="RAKE" path="C60,C64"/>
        <LINE id="L71" type="EAVE" path="C64,C24"/>
        <LINE id="L72" type="RAKE" path="C43,C65"/>
        <LINE id="L73" type="EAVE" path="C65,C66"/>
        <LINE id="L74" type="STEPFLASH" path="C66,C39"/>
        <LINE id="L75" type="EAVE" path="C67,C68"/>
        <LINE id="L76" type="RAKE" path="C67,C46"/>
        <LINE id="L77" type="STEPFLASH" path="C68,C44"/>
        <LINE id="L78" type="STEPFLASH" path="C69,C70"/>
        <LINE id="L79" type="FLASHING" path="C70,C71"/>
        <LINE id="L80" type="STEPFLASH" path="C71,C72"/>
        <LINE id="L81" type="OTHER" path="C72,C69"/>
        <LINE id="L82" type="EAVE" path="C73,C74"/>
        <LINE id="L83" type="RAKE" path="C74,C20"/>
        <LINE id="L84" type="RAKE" path="C19,C73"/>
        <LINE id="L85" type="STEPFLASH" path="C3,C75"/>
        <LINE id="L86" type="EAVE" path="C75,C4"/>
        <LINE id="L87" type="EAVE" path="C1,C76"/>
        <LINE id="L88" type="STEPFLASH" path="C2,C76"/>
      </LINES>
      <POINTS>
        <POINT id="C1" data="31.091046303012742,7.586463308561623,7.758104672798754"/>
        <POINT id="C2" data="30.221308746663713,7.51370426827543,8.582104672798756"/>
        <POINT id="C3" data="28.45270027352784,5.695735388858146,8.582104672798756"/>
        <POINT id="C4" data="28.40390384869119,4.824324920829124,7.758104672798754"/>
        <POINT id="C5" data="15.706111382244515,-8.672111176948265,18.029010148648826"/>
        <POINT id="C6" data="17.343249712044486,-6.98928166431988,18.029010148648826"/>
        <POINT id="C7" data="16.710733927027086,-6.3739395704923245,19.21276014864884"/>
        <POINT id="C8" data="16.065029879329494,-7.037664686513466,19.21276014864884"/>
        <POINT id="C9" data="21.809337847117888,-1.1330378129850585,19.21276014864884"/>
        <POINT id="C10" data="22.59280014105867,-0.32770973149299126,19.21276014864884"/>
        <POINT id="C11" data="22.44185363213529,-1.748379906812615,18.029010148648826"/>
        <POINT id="C12" data="24.21797236736531,0.07730883993011746,18.029010148648826"/>
        <POINT id="C13" data="15.904557771599487,-0.35532857100302423,23.74251014864882"/>
        <POINT id="C14" data="19.959420308921487,-4.3000959001377295,23.74251014864882"/>
        <POINT id="C15" data="15.174153343099285,-9.218915726972282,18.029010148648826"/>
        <POINT id="C16" data="3.2756088790540474,-21.449538770207692,18.029010148648826"/>
        <POINT id="C17" data="24.75058616092231,0.6247874459317752,18.029010148648826"/>
        <POINT id="C18" data="36.25088968152554,12.446054968889902,18.029010148648826"/>
        <POINT id="C19" data="25.536758123962212,22.869282819232847,33.125746496847455"/>
        <POINT id="C20" data="-7.438522678509282,-11.026310919864741,33.125746496847455"/>
        <POINT id="C21" data="20.104878969694997,9.347384283146331,27.5350532824926"/>
        <POINT id="C22" data="20.422855826596905,9.038040944134906,27.08700831051463"/>
        <POINT id="C23" data="19.986199513150613,14.046802404181621,30.930568110436145"/>
        <POINT id="C24" data="24.687103804133873,14.057495239887752,27.535053282492658"/>
        <POINT id="C25" data="25.005080661035972,13.748151900876442,27.08700831051461"/>
        <POINT id="C26" data="24.512783887964705,13.242115530206888,27.08700831051461"/>
        <POINT id="C27" data="27.93126830656536,9.916447760823587,22.27019626179496"/>
        <POINT id="C28" data="24.333637018268714,6.218409545421025,22.27019626179496"/>
        <POINT id="C29" data="20.91515259966815,9.54407731480444,27.08700831051461"/>
        <POINT id="C30" data="1.4678647515131094,-9.809773656868307,27.5350532824926"/>
        <POINT id="C31" data="1.7858416084150026,-10.11911699587973,27.08700831051461"/>
        <POINT id="C32" data="1.3491852949686953,-5.110355535833015,30.930568110436184"/>
        <POINT id="C33" data="6.0500895859519845,-5.099662700126866,27.535053282492658"/>
        <POINT id="C34" data="6.368066442853987,-5.409006039138272,27.08700831051463"/>
        <POINT id="C35" data="5.875769669782812,-5.915042409807735,27.08700831051461"/>
        <POINT id="C36" data="9.294254088383463,-9.240710179191035,22.27019626179496"/>
        <POINT id="C37" data="5.696622800086819,-12.938748394593583,22.27019626179496"/>
        <POINT id="C38" data="2.278138381486258,-9.613080625210179,27.08700831051461"/>
        <POINT id="C39" data="25.13722879373017,21.842429548962002,24.03248635912418"/>
        <POINT id="C40" data="34.91087454931145,12.334151991267959,10.12515254738877"/>
        <POINT id="C41" data="35.0641903987084,12.184998882334428,9.906992942517084"/>
        <POINT id="C42" data="48.44789168912583,25.942228256145388,9.906992942517084"/>
        <POINT id="C43" data="38.520930084147594,35.59965892277296,24.03248635912418"/>
        <POINT id="C44" data="-6.630124919055305,-10.397224739383327,23.858861466259214"/>
        <POINT id="C45" data="3.350592435137266,-20.10695161119713,9.802180264781887"/>
        <POINT id="C46" data="-19.91902115613677,-24.057003128878574,23.858861466259174"/>
        <POINT id="C47" data="-9.826086351608735,-33.87590058977346,9.644135019271951"/>
        <POINT id="C48" data="3.4628098854727636,-20.216122200278217,9.644135019271951"/>
        <POINT id="C49" data="-10.058225830431228,-20.78440938624603,19.027535291943526"/>
        <POINT id="C50" data="-8.035106837977992,-22.752597858956193,16.17820716247564"/>
        <POINT id="C51" data="-8.531060654899486,-19.21462228807394,19.027535291943526"/>
        <POINT id="C52" data="-6.5079416624462505,-21.182810760784104,16.17820716247564"/>
        <POINT id="C53" data="2.1442177246180543,18.994747002316462,8.17466371481018"/>
        <POINT id="C54" data="-1.4786954315423817,22.51929298215448,8.17466371481018"/>
        <POINT id="C55" data="-6.371897659348913,17.489525509043347,15.082226892908787"/>
        <POINT id="C56" data="-2.74898450318841,13.964979529205257,15.082226892908787"/>
        <POINT id="C57" data="8.222683509418124,-11.797228627992942,30.930568110436145"/>
        <POINT id="C58" data="10.513795926637593,-9.44217314962224,27.53505328249264"/>
        <POINT id="C59" data="24.568585310380573,5.004873833651004,27.535053282492317"/>
        <POINT id="C60" data="26.85969772760002,7.359929312021677,30.930568110436145"/>
        <POINT id="C61" data="-11.265099887155436,12.459758035932225,8.17466371481018"/>
        <POINT id="C62" data="-7.6421867309949745,8.93521205609421,8.174663714809904"/>
        <POINT id="C63" data="5.931571092198681,-14.152284106363616,27.535053282492317"/>
        <POINT id="C64" data="29.150810144819488,9.714984790392378,27.53505328249264"/>
        <POINT id="C65" data="28.593968479169344,45.25708958940055,9.906992942517084"/>
        <POINT id="C66" data="15.210267188751928,31.499860215589575,9.906992942516771"/>
        <POINT id="C67" data="-30.011955960664782,-14.238105667983712,9.64413501927161"/>
        <POINT id="C68" data="-16.723059723583287,-0.5783272784885108,9.644135019271895"/>
        <POINT id="C69" data="-16.919034623148093,-14.109881180211204,19.027535291943526"/>
        <POINT id="C70" data="-18.942153615601327,-12.141692707501042,16.17820716247564"/>
        <POINT id="C71" data="-17.414988440069585,-10.571905609328951,16.17820716247564"/>
        <POINT id="C72" data="-15.39186944761635,-12.540094082039124,19.027535291943526"/>
        <POINT id="C73" data="14.822626566398881,33.29251066957581,18.029010148648826"/>
        <POINT id="C74" data="-18.152654236072603,-0.6030830695218015,18.029010148648485"/>
        <POINT id="C75" data="26.21242026088466,3.3929310056220174,7.758104672798756"/>
        <POINT id="C76" data="32.46158875930689,9.816508651511553,7.758104672798756"/>
      </POINTS>
    </ROOF>
  </STRUCTURES>
</DATA_EXPORT>
Here is the code I have so far.
require 'rexml/document'
include REXML

def import_eagleview(file_path)
  xml = File.read(file_path)
  doc = REXML::Document.new(xml)

  roof_data = doc.root.elements['STRUCTURES'].elements[1].elements.to_a
  points = roof_data[2].elements.to_a.map { |pnt| [pnt.attributes['id'], pnt.attributes['data'].split(',').map(&:to_f)] }.to_h
  edges = {}
  entities = Sketchup.active_model.entities
  roof_data[1].elements.each do |l|
    pnts = l.attributes['path'].split(',')
    pnt1 = points[pnts[0]]
    pnt2 = points[pnts[1]]
    edge = entities.add_edges(pnt1, pnt2)
    edges[l.attributes['id']] = edge
  end
  faces = {}
  roof_data[0].elements.each do |f|
    polygon = f.elements[1]
    lines = polygon.attributes['path'].split(',').map { |l| edges[l] }.flatten
    loops = loops_from_edges(lines)
    pm = Geom::PolygonMesh.new
    a = true
    if a
      points = []
      loops.each { |l| mesh_points_from_edges(l).each { |pt| points.push(pt) } }
      points.push(points[0])
      points.each { |pnt| pm.add_point(pnt) }
      pm.add_polygon((1..(pm.points.length)).to_a)
      entities.add_faces_from_mesh(pm)
    else    
      pts = lines.map { |l| [l.start, l.end] }.flatten.uniq
      pts.each { |pt| pm.add_point(pt) }
      pm.add_point(pts[0])
      face = entities.add_face(loops[0])
    end
    1..(loops.length - 1).each { |i| entities.add_face(loops[i]) } if loops.length > 1
    faces[f.attributes['id']] = face
  end
end

def mesh_points_from_edges(edges)
  pnts = edges.map { |l| [l.start, l.end] }.flatten.uniq
  pnts.push pnts[0]
end

def loops_from_edges(edges)
  loops = []
  current_loop = []
  last = nil
  edges.each do |edge|
    unless last.nil? || [edge.start, edge.end, last.start, last.end].uniq.length < 4
      loops.push current_loop.to_a
      current_loop = []
    end
    current_loop.push edge
    last = edge
  end
  loops.push(current_loop)
  loops
end

I looked at this topic, and I think I’m close, but apparently I’m missing some small detail.

What is this statement ?

It’s a brain failure. :exploding_head: I’ve corrected the statement. Also the above code would end up in my own proper namespace which is not included in the snippet.

Okay. Just a note to remind that SU 2024 comes with bundled gems, one of which is REXML.
So, for 24+ you need not install it from the rubygems server nor your own package.

1 Like

Greetings Neil,
I took a slightly different approach to the problem (as you will see). Also, the XML data doesn’t inlcude complete information to determine which side of each face is the front or the back.

ImportXML V3.rb (5.0 KB)

Thanks. I’ll check it out. Up is all ways the front face in this case.

That seems to work well. There is one remaining face that does not have the whole cut properly.

Always facing up. I can easily fix that.

entities.grep(Sketchup::Face).each { |f| f.reverse! if f.normal.z < 0 }

Over morning coffee I had the thought that the face orientations are being lost be the way the the faces’ loops are aggregated. So I had another go at the problem and here it is.

ImportXML V4.rb (4.4 KB)

1 Like