I returned from a beautiful little mountain hike to the lake, pass and mountain of Piz Lunghin.
One very special aspect of this place is that it is Europe's one and only triple watershed, with rivers flowing all the way to the North Sea, Black Sea and Mediterranean. Another special aspect of my hike was spending the night outside on the mountain, with a wonderful clear sky and the almost full moon in sub-zero temperatures (Celsius).
Returning once again to the Revit API, the SDK sample AllViews collects all views in the model, asks the user to interactively select some of them, and places those on a sheet.
Even so, the exact definition and calculation of the view coordinates and location of the view on the sheet remains somewhat obscure. Here are some furhter helful results in this area. They do not answer every possible question in full completeness, so we still have some work to do exploring this area, but hopefully they will be useful already in this incomplete form.
We recently had one question from Thomas Fink of SOFiSTiK on this, and here is another recent question on that topic with some interesting research performed by my colleague Joe Ye. I am reproducing the rather long discussion in full, to show that the issue is not trivial and the solution is not yet completely perfect:
Question: Given a certain view placed on a sheet, I am trying to retrieve the location of the view on the sheet using:
Location loc = Element.Location;This is not giving me what I need, which is the UV or XYZ location of the view placed on the sheet.
Answer: You can use the Element BoundingBox property to retrieve the Max and Min points of a viewport in the sheet. From these, you can calculate the middle point of the Max and Min point. The middle point is the viewport's centre point in sheet.
Here is some code showing to determine the centre point of a view in a sheet view. To quickly test the solution, it currently uses a hardcoded element id 157165 for the viewport in the sheet view; please change that to suit your model and needs. In addition, please pass in the sheet object as an argument in the call to Element.get_BoundingBox. In this sample, we just set the sheet view to be the active view:
[TransactionAttribute( TransactionMode.Manual )] [RegenerationAttribute( RegenerationOption.Manual )] public class GetViewPosition : IExternalCommand { public Result Execute( ExternalCommandData commandData, ref string messages, ElementSet elements ) { UIApplication app = commandData.Application; Document doc = app.ActiveUIDocument.Document; ElementId id = new ElementId( 157165 ); Element elem = doc.get_Element( id ); BoundingBoxXYZ bounding = elem.get_BoundingBox( doc.ActiveView ); string sMsg = "The Max and Min points are: \n"; sMsg += bounding.Max.X.ToString() + ", " + bounding.Max.Y.ToString() + ", " + bounding.Max.Z.ToString() + "\n"; sMsg += bounding.Min.X.ToString() + ", " + bounding.Min.Y.ToString() + ", " + bounding.Min.Z.ToString() + "\n"; XYZ xyzPosition = ( bounding.Max + bounding.Min ) / 2.0; sMsg += "view position in current sheet is:\n" + xyzPosition.X.ToString() + "," + xyzPosition.Y.ToString() + "," + xyzPosition.Z.ToString(); MessageBox.Show( sMsg ); return Result.Succeeded; } }
Response: I need to programmatically copy a legend view from one sheet to another. For this, I need to get the location of the view from the original sheet and reuse this location when adding new views to the other sheets.
For this purpose, I am using the following code:
foreach( ViewSheet chosenSheet in SelectedSheets ) { BoundingBoxXYZ xyzLocation = selectedView.get_BoundingBox( m_Doc.ActiveView ); XYZ xyzPosition = ( xyzLocation.Max + xyzLocation.Min ) / 2.0; int scale = correspondingView.Scale; chosenSheet.AddView( correspondingView, new UV( xyzPosition.X, xyzPosition.Y ) ); }
Here I am using the following variables:
- chosenSheet is the sheet where the legend will be copied.
- selectedView is the selected Viewport to be copied.
- correspondingView is the view to be copied.
Unfortunately, the views are not copied to the same location.
Answer: I created a simple project and added several view sheets. I picked the Level 1 viewport and tried to run this code, but this caused an error message saying 'Added view is already on another sheet and cannot be displayed on two sheets'.
Response: This is because you are trying to copy a "plan" view that is already placed on a sheet and place it on another sheet. Try running this code on a legend view placed on a sheet.
Answer: The point used to insert the view on the view sheet is mapping the (0,0,0) point of the source view. When you retrieve the centre point coordinates of the viewport, they do not match the (0,0,0) point of the source view. So you need to calculate the source view's original point coordinates in the view sheet from the viewpoint's centre point coordinates.
Response: I understand that I need to get the centre of the view in the ViewSheet in order to be able to calculate the shift. How can I obtain the coordinates of the centre of the view relative to the view sheet?
Answer: The View class has a very important property Outline. The Outline property will return the Max and Min point of the closest bounding box, which includes all elements in this view. The value in this Max and Min the result of the legend view's real Max and Min coordinates subdivided by the view scale. For more information about Outline property, please refer to the Revit SDK developer guide 'Revit 2011 API Developer Guide.pdf'.
Approximately, the outline Max point is mapping the same Max point of the viewport in view sheet. So we can calculate the source view's origin coordinates in the host view sheet using the following code:
BoundingBoxXYZ xyzLocation = SelectedView.get_BoundingBox( m_Doc.ActiveView ); // get the outline max and min XYZ ptMaxOutline = new XYZ( CorrespondingView.Outline.Max.U, CorrespondingView.Outline.Max.V, 0 ); // get the view's origin point's // coordinates in current view sheet. UV ptSourceViewOriginInSheet = new UV( xyzLocation.Max.X - ptMaxOutline.X, xyzLocation.Max.Y - ptMaxOutline.Y );
Here is the complete code of the Execute method:
public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) { try { UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Application app = uiapp.Application; Document doc = uidoc.Document; SelElementSet selection = uidoc.Selection.Elements; if( selection.Size == 0 ) { MessageBox.Show( "Please Select a view to be copied.", "View Coordinates", MessageBoxButtons.OK, MessageBoxIcon.Error ); return Result.Failed; } else { // Get all Sheets List AllSheets = GetAllSheets( doc ); // Get all Views in the model. // Find all views in the document by using category filter ElementCategoryFilter filter = new ElementCategoryFilter( BuiltInCategory.OST_Views ); FilteredElementCollector collector = new FilteredElementCollector( doc ); IList AllViews = collector.WherePasses( filter ) .ToElements(); foreach( Element SelectedView in selection ) { if( SelectedView.Category.Name == "Viewports" ) { String ViewName = SelectedView.get_Parameter( "View Name" ).AsString(); View CorrespondingView = null; for( int i = 0; i < AllViews.Count; i++ ) { if( AllViews[i].Name == ViewName ) { CorrespondingView = AllViews[i] as View; break; } } BoundingBoxXYZ xyzLocation = SelectedView.get_BoundingBox( doc.ActiveView ); // get the outline max and min XYZ ptMaxOutline = new XYZ( CorrespondingView.Outline.Max.U, CorrespondingView.Outline.Max.V, 0 ); // get the view's origin point's coordinates // in current view sheet. UV ptSourceViewOriginInSheet = new UV( xyzLocation.Max.X - ptMaxOutline.X, xyzLocation.Max.Y - ptMaxOutline.Y ); // for each chosen to be exported // get the views on it. foreach( ViewSheet ChosenSheet in AllSheets ) { ChosenSheet.AddView( CorrespondingView, ptSourceViewOriginInSheet ); } } } return Result.Succeeded; } } catch( Exception ex ) { MessageBox.Show( ex.Message ); return Result.Failed; } }
Response: Using this code, the location of the view on the sheet is still inaccurate. If I use this code to duplicate a view legend placed on a sheet in the same location, the legend view still appears to be shifted while duplicated on the same sheet, as shown in the following figures. Here is a view of the entire sheet:
Here is a more detailed picture of the offset between the original and copied views:
Answer: The reason for this might be that the Revit API returns the Viewport BoundingBox property value with an offset to its original outline property. The BoundingBox property is not completely accurate, so the result to calculate the legend view's origin in the sheet view cannot be so either. As far as I can tell, the offset of the Max point of the viewport's BoundingBox is enlarged 0.01 in both X and Y direction. We can take this scaling into account.
Here is some updated code with an adjustment added. You might need to further adjust the offset value according to different situations. The offset value of 0.01 is not obtained from source code, but from my empirical testing results:
foreach( Element SelectedView in selection ) { if( SelectedView.Category.Name == "Viewports" ) { string ViewName = SelectedView.get_Parameter( "View Name" ).AsString(); View CorrespondingView = null; for( int i = 0; i < AllViews.Count; i++ ) { if( AllViews[i].Name == ViewName ) { CorrespondingView = AllViews[i] as View; break; } } BoundingBoxXYZ xyzLocation = SelectedView.get_BoundingBox( doc.ActiveView ); // get the outline max and min, // offset in each direction of max point of // bounding box than the outline box. double dMaxOffset = 0.01; XYZ ptMaxOutline = new XYZ( CorrespondingView.Outline.Max.U, CorrespondingView.Outline.Max.V, 0 ); UV ptSourceViewOriginInSheet = new UV( xyzLocation.Max.X - dMaxOffset - ptMaxOutline.X, xyzLocation.Max.Y - dMaxOffset - ptMaxOutline.Y ); // for each chosen to be exported get the views on it foreach( ViewSheet ChosenSheet in AllSheets ) { ChosenSheet.AddView( CorrespondingView, ptSourceViewOriginInSheet ); } } }
Thank you very much, Joe, for this thorough exploration!
Here is another quick question and answer that might be of use in this context:
Centre Point of View
Question: I need a way to get the XYZ of the centre of a view and the XYZ of the centre of a sheet. Can I achieve this using one of the Outline, CropBox or BoundingBox properties?
Answer: It depends on what exactly you mean by 'centre of view'. I can imagine that it might be any one of the following:
- The visible centre of a view: If we pan the view, the centre coordinates change. There is currently no API exposed to get the visible centre of a view, and we do have an open wish list item to obtain this information.
- The centre point of the crop box: We can retrieve the BoundingBoxXYZ of the view through the CropBox property. Then the centre can be determined by Max and Min of the crop box.
- The coordinate centre: the coordinate centre point is returned by the View.Origin property.
For an example of defining a completely different set of view coordinates, we also once looked at cropping a 3D view to a specific element.
Is it possible to get the origin of the area of the viewport that is non-annotative? In other words, each viewport has two boundaries. The outer one is a little bigger for the sake of including annotation. But the extent of the annotation boundary can vary from sheet to sheet. Furthermore, the sheet dynamically changes the boundary according to whether it finds annotations in this outer boundary. The result is that I cannot determine where the non-annotative crop region starts (on the sheet), as get_BoundingBox.Min gives the starting point of the outer crop box.
Posted by: Thomas Otten | January 12, 2012 at 17:31
Dear Thomas,
Nope, sorry, I do not believe you currently have any access to this information.
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 18, 2012 at 04:21
I'm working on an app to duplicate a sheet (ie duplicate all views on a sheet and place them on a new sheet in the same place relative to the title block) I've got this working apart from the location of the copied views on the new sheet. They seem to have a mind of their own and end up all over the place.
The only difference to the above code is that i'm using GetBoxOutline() to get the outline cordinates for the View port and Viewport.Create() (both 2013 addition I think). Even adding in the offset value doesn't work as in one of my test sheets there are two viewports and one is moved up and one down in the copied sheet. so the placement error isn't uniform or even in the same direction for two view ports on the same sheet. I have no clue whats going on and any help would be great.
Posted by: James Hicks | May 30, 2012 at 10:45
Has this been fixed in 2014? I still can't get this to work in 2013 unless someone has further ideas the offsets appear to be different for each viewport.
Posted by: Anthony | May 07, 2013 at 02:29
Dear Anthony,
Have you taken a look at the Revit 2014 copy and paste API, and especially the new DuplicateViews SDK sample?
Cheers, Jeremy.
Posted by: Jeremy Tammik | May 08, 2013 at 02:51
Hi,
I havn't looked at 2014 yet either however I did find a way around this in 2013. It's a bit long and "hacky" but basicaly by placing the view ports as above and then working out what correction is need.
I did this by returning the Outline of both the new and reference viewports (using GetBoxOutline()) and then working out the distances between the min x and y points of the outlines and fixed points on their respective sheets (I used the View.Outline.Min.U (orV). I then used the difference between the distances to create a "bespoke" correction per placement.
I hope this makes some sort of sense.
Posted by: James Hicks | May 15, 2013 at 11:36
Dear James,
That makes absolute sense, and sounds totally brilliant.
Congratulations on solving that!
Thank you very much for sharing and describing such an effective 'hicks-hack' :-)
Sorry, couldn't resist, hope you don't mind!
Cheers, Jeremy.
Posted by: Jeremy Tammik | May 15, 2013 at 11:53
I don’t mind at all, I’m glad I could contribute something!
I'd just like to say thank you for all your blog posts here as they have been invaluable to me as I have been writing various addins for our company to use. I’m looking forward to migrating them all over to 2014 soon and seeing what I can do with the new futures... When our IT department final put 2014 on our system...
Thanks again,
James
Posted by: James Hicks | May 17, 2013 at 07:50
:-)
Posted by: Jeremy Tammik | May 17, 2013 at 08:23
Hi Jermey,
My requirement is very similar to placing legends from one sheet to another.I have come across James Hicks reply but my apologies that I could not really understand it.Can you please help me give an idea how to implement it.BTW,I am new to Revit API.
Thanks
Posted by: Madhuri | November 23, 2014 at 13:44
Dear Madhuri,
To begin with, if you are new to the Revit API, I can recommend two things:
1. Take a look at the getting started material and all the tutorials to get a good hang of all aspects:
http://thebuildingcoder.typepad.com/blog/about-the-author.html#2
2. In general, if a feature is not available in the Revit product manually through the user interface, then the Revit API will not provide it either.
Therefore, before thinking about possibly automating any task, it is important to explore the possibilities and develop an optimal solution through the user interface first. For that, I would suggest asking an application engineer, product usage expert, or product support for help on finding a suitable workflow and laying down best practices.
To address your need, I would not recommend trying to reproduce James' approach, if, as he says, it's long and "hacky".
A number of new view APIs have been added since that was written.
To start with, look at Steve Mycynek's and Scott Conover's classes at Autodesk University 2012 and 2013, respectively:
CP3133 - Using the Autodesk Revit Schedule and View APIs
DV1823 - A 2014 Update to the Autodesk Revit Schedule and View APIs
I hope this helps.
Cheers, Jeremy.
Posted by: Jeremy Tammik | November 24, 2014 at 15:35
Dear Jeremy,
Hope You are fine
I'm trying to change the location of "Label Outline" in the viewport, In Visual studio I see Maximum point and Minimum point take the new values I set, But in Revit, no change!
Please Advice,
Posted by: Modar Mayya | June 16, 2015 at 02:52