Satellite Images and Instance Transforms

Things have slowed down a bit in the discussion forum recently, like in many other parts of the world. I am very glad I have a garden to go out and sit in, and springtime and sunshine to enjoy. I hope you are doing well and remaining healthy also! Topics for today:

Transforming Symbol Geometry to Instance Placement

A fundamental question reappeared in a new form in the Revit API discussion forum thread on using PlanarFace GetEdgesAsCurveLoops and FilledRegion Create together, easily resolved by applying the instance placement transformation to transform the symbol geometry from the family definition coordinate system to the real-world instance placement in the project space:

Question: I'm trying to create a plugin that will allow me to create filled regions just by selecting a face.

I use Selection.PickObject( ObjectType.Face ), convert that reference to a PlanarFace type, and then use GetEdgesAsCurveLoops to get the collection of CurveLoops and pass that to FilledRegion Create to create my filled region.

However, it's resulting in unexpected behaviour. If I click on the face of a roof surface, it creates the filled region exactly over the face like I want it to. When I click the face of a family instance, it still creates the filled region – but instead of creating it over the face of the family instance, it creates the instance at the project origin of the document.

Here are images illustrating the two scenarios:

Highlighting the roof face and selecting it applies the FilledRegion like expected:

Roof face and filled region

However, when I click this electrical equipment face...

Electrical equipment face

It creates the filled region over at the project origin:

Filled region at project origin

Here is the relevant code snippet:

  Reference faceRef = sel.PickObject( ObjectType.Face );

  GeometryObject geoObj = doc.GetElement( faceRef )
    .GetGeometryObjectFromReference( faceRef );

  PlanarFace moduleFace = geoObj as PlanarFace;
  IList<CurveLoop> faceEdges = moduleFace.GetEdgesAsCurveLoops();

  FilledRegion fillRegion = FilledRegion.Create( doc, 
    stringFillType.Id, currentView.Id, faceEdges );

Does anyone know how to get around this? Thanks!

Answer: The family instance reuses the geometry defined for the family symbol by applying a transform to it.

The symbol geometry is generally close to the origin.

Every instance needs a different transformation, depending on where the instance is placed in the model.

You need to apply the family instance transformation to the geometry to transform it from the family symbol definition coordinates to the project model coordinates where the instance has been placed:

The roof element is different, since it is modelled in place using a sketch.

Response: Awesome! This worked! Thank you much Jeremy!

For anyone interested, here is how I edited the code based on Jeremy's suggestion:

  Reference faceRef = sel.PickObject( ObjectType.Face );

  GeometryObject geoObj = doc.GetElement( faceRef )
    .GetGeometryObjectFromReference( faceRef );

  Instance moduleInstance = doc.GetElement( faceRef ) as Instance;
  Transform moduleTransform = moduleInstance.GetTotalTransform();

  PlanarFace moduleFace = geoObj as PlanarFace;
  IList<CurveLoop> faceEdges = moduleFace.GetEdgesAsCurveLoops();

  foreachCurveLoop loop in faceEdges )
  {
    loop.Transform( moduleTransform );
  }

  FilledRegion fillRegion = FilledRegion.Create( doc, 
    stringFillType.Id, currentView.Id, faceEdges );

Many thanks to Chris for raising the issue and confirming the solution!

Importing and Displaying Satellite Images

Revitalizer came up with a very nice suggestion for importing and displaying satellite images:

Question: I'm building an add-in for Revit and I would like to be able to import and display third-party satellite imagery in order to place buildings in their 'real' position. I would like to be able to do this in a 3D view, but I don't know how.

The user workflow for my add-in is this:

Essentially, my question is exactly the same as Google maps image in 3D view, but instead doing that programmatically/automatically through an add-in. In that thread, a suggestion is made to create a decal with the desired image, but this does not seem to be supported through the API.

Another approach I found is to use PostCommand to create and place decals, but these commands are apparently only executed after exiting the API context and only one at a time. As my add-in aims to perform a whole bunch of functionalities in one go, this seems ill-suited for my use case. It seems to be possible to chain a bunch of PostCommands, but this is a little 'hacky' and not recommended, especially for commercial use.

Am I overlooking some existing functionality? Is my use case just not supported in current Revit? I'm new to programming for Revit, so it's very possible I've missed something.

I'm running / programming for Revit 2019 on Windows 10.

Answer: What about creating a new material, setting its texture path, then making a TopoSurface and assigning the material to it, cf. modifying material visual appearance?

I don't know how to adjust the UV mapping for the TopoSurface, but if it worked, you would see your satellite image in 3D.

Response: Thanks to all for the replies! It took some time to try out the proposed solution (accessing AppearanceElements is convoluted!), so that's why it took me this long to reply.

In the end, though I had to work around some weird quirks with the API, adding the image as a texture to a topography through a material works great.

Thanks again for the suggestion, I couldn't have made it work without it.

Many thanks to Harm for raising this and to Revitalizer for the good suggestion.

Free Time? Learn Things! Free Code Camp!

I have repeatedly pointed out the incredible programming training resources offered by Quincy Larson and freecodecamp.org.

In case you happen to have more time on your hands than usual these days, you might want to see it as an opportunity to learn to code from home with the Coronavirus quarantine developer skill handbook.

By the way, I very much enjoyed some of Quincy's recent quotes of the week: