Ruby to fetch discrete locations of interpolated surface

Given a interpolated surface (made with toposhaper from a point cloud) and a x/y grid of points, how would one fetch the z location of each x/y location with Ruby? There is bunch of points so the work should be done with Ruby.

Surface follows.
Laser scanned.skp (990.6 KB)

And code to generate (a part of the) x/y grid follows.


model = Sketchup.active_model

entities = model.active_entities

arr =
[[-18.m, 54.m, 33.845.m],
[-15.m, 54.m, 33.785.m],
[-18.m, 51.m, 34.015.m],
[-15.m, 51.m, 33.705.m],
[-12.m, 51.m, 33.475.m],
[-9.m, 51.m, 33.335.m],
[-6.m, 51.m, 33.345.m],
[-3.m, 51.m, 33.115.m]]

arr.each {|a|
entities.add_cpoint(Geom::Point3d.new([a[0],a[1],0]))

}

If you already have the ‘point-cloud’ then it will have XYZ values.
The ‘arr’ you gave has the Z value specified, as the third value of each of the points set in each XYZ array. e.g.

[-18.m, 54.m, 33.845.m]

Where the Z is 33.845.m

You brief example of code will add a guide-point at each of the points tested, but at Z == 0
i.e. flattened in the Z…
Is that what you want ???
But perhaps I don’t understand the question ??
Try and explain it better…

1 Like
z_array = entities.grep(Sketchup::ConstructionPoint).map(&:position).map(&:z)

or to break it down

guide_pts = entities.grep(Sketchup::ConstructionPoint)
pt_array  = cpts.map {|cpt| cpt.position }
z_array   = pt_array.map {|pt| pt.z }

REFERENCE:

My apologies for not describing the problem with sufficient detail.

I have two point clouds. The first set is laser scanned from the air, whereas the second I have measured myself. The set of laser scanned points is very large, whereas my own set has only ~100 points.

I would like to know how the points that I have measured myself compare with the set of laser scanned points. The x/y locations differ of the two sets do not match. My strategy is to make a toposhaper surface out of the laser scanned points and find the z-value of the surface on the x/y locations of my own set of points.

Surface: Surface.skp (1.2 MB)

Code, which does not do what it should:

model = Sketchup.active_model
ents = model.active_entities
 
arr = 
[[-3.m, 27.m, 32.675.m],
[-6.m, 27.m, 32.655.m],
[-9.m, 27.m, 32.515.m]]

arr.each {|a| ents.add_cpoint(Geom::Point3d.new(a))}

cpts = ents.grep(Sketchup::ConstructionPoint)
pt_array  = cpts.map {|cpt| cpt.position }
z_array   = pt_array.map {|pt| pt.z }

puts z_array

No need to add these confusing cpoints - if you want to reuse some existing cpoints collect their points first.
Use pt_array, iterate its points in turn - pt - and project a ray test downwards*. See it it hits the surface, and is so you have the point where it hits it. Then collect the pt.z and the hit.z and report the difference ?

Thanks! This was exactly what I was looking for. Would you be so kind to point me in the right direction on how to export the nested array arr and while doing so how to convert to meters?

arr = 
[[0, 0, 33.845.m, nil],
[-15.m, 54.m, 33.785.m, nil],
[-18.m, 51.m, 34.015.m, nil],
[-15.m, 51.m, 33.705.m, nil]]

arr.each{|a|
ray = [Geom::Point3d.new(a.x, a.y, 0), Geom::Vector3d.new(a.x, a.y, 100.m)]
a[3] = model.raytest(ray, false)[0].z
}

No sure why your ‘arr’ contains arrays of 4 elements - with the last ‘nil’.
You could simply write them as xyz point-arrays ?
Tip: learn to use ``` code tags around your snippets…

arr = [ [0, 0, 0], [0, 0, 33.845.m], [-15.m, 54.m, 33.785.m], [-18.m, 51.m, 34.015.m], [-15.m, 51.m, 33.705.m] ]

Then you only need a simple vertical upwards vector - e.g. Z_AXIS…

results = []
ve = Z_AXIS
arr.each{|pt|
  pt.z=0 # reset point's z here
  ray = [pt, ve]
  hit = model.raytest(ray, false)
  results << hit[0].z.to_m if hit # z in meters whatever model's units are
}
puts results

Thanks again! The reason is that the objective is to compare z values, if they are close then the measurement match. Hence the following. Is there some simple way to export to the array res to CSV?

arr = 
[[-15.m, 54.m, 33.785.m],
[-18.m, 51.m, 34.015.m],
[-15.m, 51.m, 33.705.m]]

res = []
ve = Z_AXIS

arr.each{|pt|
  ray = [[pt.x,pt.y,0], ve]
  hit = model.raytest(ray, false)
  z_hit = hit[0].z if hit
  diff = pt.z.to_m - z_hit.to_m
  res.push([pt.x.to_m, pt.y.to_m, pt.z.to_m, z_hit.to_m, diff])
}

p res

Try something like this (untested) …

# You may wish to use UI.savepanel to create a path.
csv_path = "C:/some/path/to/results.csv"

File.open(csv_path, File::WRONLY|File::CREAT|File::TRUNC, 0644) do |file|
  file.rewind
  res.each { |line|
    file.write("#{line.join(',')}\n")
  }
  file.flush
end

As Dan says there are several ways to write collected data out into a file.
CSV is just CommaSeparatedVariables…
See how Dan joins each item in the array with commas then writes that line to the new file, with a final \n newline character…


Incidentally, you might want to trap for a 'miss' ``` hit = model.raytest(ray, false) next unless hit z_hit = hit[0].z if hit ``` The next simply skips the point - or you can write the miss to file some other way ?