Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2D Extract information for autocalibration from DWG file #695

Open
10 of 11 tasks
FJThiel opened this issue Aug 28, 2024 · 14 comments
Open
10 of 11 tasks

2D Extract information for autocalibration from DWG file #695

FJThiel opened this issue Aug 28, 2024 · 14 comments

Comments

@FJThiel
Copy link

FJThiel commented Aug 28, 2024

Description

Extend the bouncer to extract information from DWG files that can be used for the automatic calibration with drawings down the line.

Goals

  • The required data for autocalibration is extracted.
  • The data is written to the database in the correct format so .io can make use of it.

Tasks

  • Extract matrix from the data.
  • Calculate the calibration vectors for 2D and 3D
  • Pass the vectors to the database and store them in the correct format.
  • Update CMake so the /Zc:wchar_t- flag is set on SVG Export but not on bouncer proper
  • Update CMake to build the svg export library to the same place as bouncer
  • Update bouncer to load our module instead of ODAs one
  • Add unit tests
  • Make sure Travis works
  • Fix svg width/height tags as well in our exporter

Related Resources

Product Ticket: #381

@FJThiel
Copy link
Author

FJThiel commented Aug 29, 2024

@sebjf @carmenfan

In the database format we have a field for createdBy. I was wondering whether we would want to fill that with the name of the user uploading the model (and setting off the processing) or just with "bouncer" or "autocalibration" to make clear that this record was not user generated.

{
    horizontal: {
        model: [[0,0,0], [1,1,1]], //2 3d points to make a vector
        drawing: [[0,0], [1,1]] //2 2d points to make a vector
    },
    verticalRange: [ 0, 10],
    units: "m"
}

@carmenfan
Copy link
Member

@sebjf @carmenfan

In the database format we have a field for createdBy. I was wondering whether we would want to fill that with the name of the user uploading the model (and setting off the processing) or just with "bouncer" or "autocalibration" to make clear that this record was not user generated.

{
    horizontal: {
        model: [[0,0,0], [1,1,1]], //2 3d points to make a vector
        drawing: [[0,0], [1,1]] //2 2d points to make a vector
    },
    verticalRange: [ 0, 10],
    units: "m"
}

actually can we just omit the field? we can just assume it's auto generated if there's no author

@FJThiel
Copy link
Author

FJThiel commented Aug 29, 2024

First-time mongo user here: the database will not be upset with me if I don't send anything for a column of the collection?

I guess I can just send an empty string if that's the case.

@carmenfan
Copy link
Member

@FJThiel you don't need to send anything. Mongo is a noSQL database and is flexible with its schema 😉

FJThiel added a commit that referenced this issue Aug 30, 2024
@FJThiel
Copy link
Author

FJThiel commented Sep 2, 2024

@sebjf
This might be unrelated to this issue, but I noticed that the SVG output for some DWG files is not along one axis as I would expect and instead taken from an angle (see image below). Is this behaviour expected?

image

FJThiel added a commit that referenced this issue Sep 2, 2024
…el density so both sets of vectors are in the same unit.
FJThiel added a commit that referenced this issue Sep 2, 2024
@FJThiel
Copy link
Author

FJThiel commented Sep 2, 2024

Also: two assumptions for sanity checking:

  1. I thought We probably want the 2D vectors in the same unit as the 3D vectors. By default, the matrix from the view converts from 3d coordinates in the unit of the document (e.g. mm) to pixel values. However, since it is a vector graphic, I figured that anything we do with it later is most likely more reliant on the 2D coordinate space than pixels, so it makes sense to convert them in the bouncer since we have easy access to the pixel density attribute of the viewer there.

  2. The vectors will later be used to convert from coordinates in the y-up format (like Unity's LHS CS). The vectors in the bouncer would be in the format that AutoCAD and such use, which has the z-axis as up. If everything after the bouncer works with the Unity system because it interfaces with the viewer, then the calibration vectors should also be in that format. Not that it matters currently because the current two reference points are (0,0,0) and (1,0,0), who do not change at all if y and z are switched, but I would like to get the math right.

Both are not large changes if I am wrong.

@FJThiel
Copy link
Author

FJThiel commented Sep 6, 2024

Leaving this here since it looks like I am not finishing this before going on leave and in case somebody picks this up while I am away:

Horizontal calibration should be fine, though is not tested yet. I was currently working on creating myself a bunch of debug outputs to investigate the block structure for the vertical calibration. After our discussion last Tuesday, I found that the individual elements (called Entities) are grouped by blocks and stored in the block table of the database.
Going off the documentation, there are functions available to query the extents and origin of a block or even the whole table and I was just about to try that out when IT was finally available to install the autodesk tools and it looks like I won't have the time to run it before leaving.

If we can get indeed the extents of the table, I think that could be a good starting point for the vertical calibration. Maybe the values can even be taken as is. If that does not work, one should try next to get the extents of the individual blocks. If they are available, the overall extents can be calculated from that.

Also interesting, though probably out of scope: the blocks have a second method that offers best-fit extents after applying a transformation matrix. This could become interesting should we want the vertical calibration relative to the view. At the moment, I see no purpose for this, because we are only allowing top-down and can just look at the z-axis of the extents, but should that change in the future, this might come in handy.

I will make one last push with my experimental code. Mind, it has not run yet, so some tinkering might be necessary. The area with the test code is bracketed by \Test and \Test end, so it's easy to remove.

FJThiel added a commit that referenced this issue Sep 6, 2024
…ation of the draw strucutre. Just in case somebody has to pick this up while I am away.
@carmenfan carmenfan assigned sebjf and unassigned FJThiel Sep 10, 2024
@sebjf
Copy link
Contributor

sebjf commented Sep 11, 2024

Some notes for documentation:

Validation

To start with, validate the mapping between the test files. We can get this from Revit by exporting a floor view to a DWG.

See this article for a primer on coordinate systems in Revit: https://revitpure.com/blog/13-tips-to-understand-revit-base-points-and-coordinate-system

We can use the Annotate -> Spot Coordinate tool to inspect the coordinates of points in Revit, setting the Spot Coordinate Base to Internal Origin via the Edit Type menu. Similarly, using the ID command in AutoCAD, we can see the coordinates of the corresponding point. From these, we see that the DWGs are exported relative to the Internal Coordinate system of Revit (this we knew from prior investigations.)

image

WCS (World Coordinate System)

In bouncer, the OdGsView object contains a matrix describing the transform from the WCS of the DWG, to the SVG.

However, there are additional transforms in 3D that must be considered.

3D Viewer -> Project Coordinates

The import pipeline applies a number of transforms in 3D at each stage.

1

When importing Revit files, the method establishProjectTranslation will move vertices relative to the file's nominated coordinate system before populating a MeshNode. This means that this offset is not part of the worldOffset.

There is also a scale that transforms between the values read by ODA and the Internal Coordinate system.

To read the Internal Coordinate system directly from Revit when importing, ignore the activeOrigin and alignedLocation in establishProjectTranslation.

To get Revit to export with these set to Identity, move the Project Base Point to be aligned with the Internal Origin, and reset any Shared Coordinates.

2

The next is the Scene transform. This may apply a further scale according to the units. It will also apply the following transformation matrix:

1, 0, 0, 0,
0, 0, 1, 0,
0, -1, 0, 0,
0, 0, 0, 1

Revit uses a right-handed coordinate system. The above transform swaps the z and y axes, and inverts the resulting y-axis. The result is that the model is in a left-handed coordinate system, where the y-axis of Revit becomes the -z (backwards) axis of the viewer.

Unity

Unity uses a left-handed coordinate system, expecting z to point into the screen.

This is achieved by applying a scale of [0,0,-1] at the root of all models at runtime, which effectively inverts the z-axis again for everything received from the database, so it points forwards. This is Unity's local coordinate system.

3

Finally, there is the worldOffset. This is set to the minimum of the bounding box for the import, and stored in the Revision. The worldOffset is typically transformed into the local coordinate system of whatever is using it.

Summary

The diagrams below show the three coordinate systems involved (as seen from the same perspective of a model)

image

Conversions

When moving through Unity's public API (e.g. through the pick point alert), Unity will convert between Repo coordinates by inverting the z-axis. This inverts the transform made at the root of the scene graph and gives the result in the coordinate system used by the database.

This is not the same as project coordinates however, as would be considered by Revit.

To transform between project coordinates and Unity's coordinates, the y and z coordinates are swapped (which is what happens in the CoordViewer script).

image

Implications for calibration

Handedness

When drawings are imported via ODA, the OdGsView contains a matrix that maps between the WCS and the SVG.

The first thing to consider is that the SVG y-axis is flipped compared to the WCS.

This transform is impossible to recover from the single vector approach. However, we don't want to store the calibration in the Project Coordinates, but instead in Repo coordinates for consistency with the database. Conveniently, from the diagrams we can see that this coordinate system is consistent with the handedness of SVG coordinate system when looking down.

So, to generate the calibration pairs, we generate an arbitrary vector, get the corresponding SVG location, and then transform the original 3D vector into the repo coordinate system (mirroring what would happen to any 3D data from a file defined in the same coordinate system.) In practice, we can simply define the vector in the x-axis only, as this doesn't change with the handedness transform.

Validation

To test this out before the backend integration is done, we can get the frontend to print the calibration it tries to write at the end of the wizard to console.

image

{"calibration":"calibrated","units":"mm","horizontal":{"model":[[7072.0209913897415,2699.9042459883585,15395.533457811041],[13048.651850764742,2699.9647928633585,15399.193614061041]],"drawing":[[572.1124263317749,657.8088510040919],[722.4659785032582,657.8088510040919]]},"verticalRange":[2537.9999491133585,4937.9999491133585]}

Then the calibration can be recovered, e.g. with a script like below:

function [A] = getCalibration(s)

S = jsondecode(s);

M = S.horizontal.model(:,[1 3])';
D = S.horizontal.drawing';

% To go from p in 3D to 2D
% 1. Get p relative to vm start
% 2. Rotate by by vm->vd
% 3. Scale by vd/vm
% 4. Apply the offset of vd

% (Intuitively, this is the set of steps that are needed to go from vm
% to vd themselves.)

O0 = -M(:,1);

vm = M(:,2) - M(:,1);
vd = D(:,2) - D(:,1);

sm = norm(vm);
sd = norm(vd);

dm = vm / sm;
dd = vd / sd;

t = atan2(dd(2),dd(1)) - atan2(dm(2),dm(1)); % Model to drawing
R = [cos(t) -sin(t) 0; sin(t) cos(t) 0; 0 0 1];

s = sd / sm;

O1 = D(:,1);

% put into matrix form

T0 = eye(3);
T0(1:2,3) = O0;

T1 = R;

T2 = eye(3) * s;
T2(3,3) = 1;

T3 = eye(3);
T3(1:2,3) = O1;

A = T3 * T2 * T1 * T0;

end

Providing the above calibration gets us a matrix to go from 3D to 2D.

We can compare matrices produced by the parameters from the wizard, to those written to the database by bouncer:

image

As well as use this to transform various points to check against expected positions in the SVG:

A * [-4988 -2899 1]'
=
          269.113809058747
          203.279340068046
                         1

image

@sebjf
Copy link
Contributor

sebjf commented Sep 11, 2024

A few remarks on the above:

  • If Revit has a Project Base Point that is not aligned with the coordinate system, then the auto calibration won't work with that model, because the 3D geometry will be in the wrong place.
    This might actually be a fairly big problem - we can't just stop moving it because then pins, or offsets within federations between different revisions will no longer be consistent. However because the transform applied was never written anywhere we don't any way to get back to Project Coordinates (and therefore Repo coordinates). Revit does have the option to use Shared Coordinates as the base when exporting DWGs, which we should investigate.

  • Similarly, if the import options cause bouncer to apply a scale to the imported geometry, there will be a mismatch.

  • The DGN importer applies an offset. We need to check what the impact of this is before enabling DGN support (DGN support is otherwise complete)

  • Revit always exports to DWGs relative to its Internal Coordinate System. The DWGs do not contain Project Base Points - either as metadata or even drawing elements.

sebjf added a commit that referenced this issue Sep 11, 2024
@sebjf
Copy link
Contributor

sebjf commented Sep 11, 2024

Exporting a DWG from Revit does not contain elevation data by default. Though it possible as a test to add it with the Change command:

Type in the CHANGE command.
Type ALL to select all objects and then Enter.
Type P for Properties.
Type E for Elevation.
Type 0 and then a final Enter to end the command.
Note: This will not work on complex objects such as 3D objects.

@sebjf
Copy link
Contributor

sebjf commented Sep 12, 2024

After our discussion last Tuesday, I found that the individual elements (called Entities) are grouped by blocks and stored in the block table of the database.

As Felix found, all visual (geometric) elements of a drawing are entities. The extents of a given entity in the WCS can be found from its drawable.

(As can be seen below in the OdaDgnApp sample, this can be retrieved without having to dig into the individual types of primitive).

Using this we can get a vertical range for all drawings, and set the base of the floor, or the entire vertical range from these.

sebjf added a commit that referenced this issue Sep 12, 2024
sebjf added a commit that referenced this issue Sep 12, 2024
@FJThiel
Copy link
Author

FJThiel commented Sep 17, 2024

@sebjf

Thanks for taking this over and catching my errors such as the assumption that the DB would contain data in the Unity CS.

Maybe you can clarify a few things that I don't quite understand:

1. Initial Offset

One import problem you highlight is:

If Revit has a Project Base Point that is not aligned with the coordinate system, then the auto calibration won't work with that model, because the 3D geometry will be in the wrong place.
This might actually be a fairly big problem - we can't just stop moving it because then pins, or offsets within federations between different revisions will no longer be consistent. However because the transform applied was never written anywhere we don't any way to get back to Project Coordinates (and therefore Repo coordinates). Revit does have the option to use Shared Coordinates as the base when exporting DWGs, which we should investigate.

However, further up you wrote:

When importing Revit files, the method establishProjectTranslation will move vertices relative to the file's nominated coordinate system before populating a MeshNode. This means that this offset is not part of the worldOffset.

There is also a scale that transforms between the values read by ODA and the Internal Coordinate system.

To read the Internal Coordinate system directly from Revit when importing, ignore the activeOrigin and alignedLocation in establishProjectTranslation.

To get Revit to export with these set to Identity, move the Project Base Point to be aligned with the Internal Origin, and reset any Shared Coordinates.

The way I read this is that, while we don't get the actual offset, we can query both the aligned points (after the application of the offset) and their original coordinates in the internal coordinate system. Shouldn't that be enough to derive the transformation or are we talking different offsets here?

2. Elevation vs. Height

This is probably just a terminology thing, but I wanted to check anyway. You write that DWG does not contain elevation data by default, but then we go and get the 3d coordinates from the elements anyway. Am I correct to assume that elevation does not mean whether there is a height component in the coordinates but is an overall vertical offset applied to the model based on real-world data?

3. Table extents vs. Element Extents

I noticed that you do not query the geometry extents off the whole table, but build them yourselves from the individual extents. Am I correct to assume that the table extents do not provide the correct information?

4. Default Floor Height.

You wrote:
calibration.verticalRange = { (float)zmin, scaleFactorFromMetres(units) * FLOOR_HEIGHT_M };

Shouldn't this be
calibration.verticalRange = { (float)zmin, (float)zmin + scaleFactorFromMetres(units) * FLOOR_HEIGHT_M };
at least to ensure that the max value is always above zmin?

@sebjf
Copy link
Contributor

sebjf commented Sep 17, 2024

Hi @FJThiel,

The way I read this is that, while we don't get the actual offset, we can query both the aligned points (after the application of the offset) and their original coordinates in the internal coordinate system. Shouldn't that be enough to derive the transformation or are we talking different offsets here?

My comments above are lacking in context a bit...

It turns out the Project Base Point is actually not important at all, and the interesting point is the Survey Point.

The Survey Point in Revit is the 'public' origin of the coordinate system. It is possible to export a DWG relative to this, and this is also what we import geometry relative to when importing via ODA. Revit in practice has an internal coordinate system, but when importing or exporting the best practice is to use the Survey Point.

The coordinate system relative to the Survey Point is what we would know as Project Coordinates, and this is the same as Repo Coordinates in the database (with the axes swapped as above).

we can query both the aligned points

The problem was that we don't store the points in the RepoScene, we just get geometry relative to the survey point, so we don't know what the original offset was between Project Coordinates and Internal Coordinates in 3D for any existing files.

However, this turns out not to be a problem because any properly managed project will have all its files relative to the Survey Point anyway, and that's what all Containers are imported relative to.

Am I correct to assume that elevation does not mean whether there is a height component in the coordinates but is an overall vertical offset applied to the model based on real-world data?

Yes, DWGs are a 3D file format, but they don't contain "elevation" in their metadata, and it seems tools do not write the Z coordinate by default, though its available for primitives to use in AutoCAD.

Am I correct to assume that the table extents do not provide the correct information?

Yes!

Shouldn't this be

It should be! Good catch.

Though, we discussed yesterday and vertical calibration is being moved elsewhere so all this is moot 😄

@FJThiel
Copy link
Author

FJThiel commented Sep 17, 2024

@sebjf
Well, might be moot for this feature, but I am sure I will profit down the line from a better understanding of how the different coordinate systems interact with each other, so I appreciate the response. 😄

sebjf added a commit that referenced this issue Sep 17, 2024
sebjf added a commit that referenced this issue Sep 17, 2024
sebjf added a commit that referenced this issue Sep 17, 2024
sebjf added a commit that referenced this issue Sep 17, 2024
sebjf added a commit that referenced this issue Sep 23, 2024
sebjf added a commit that referenced this issue Sep 23, 2024
sebjf added a commit that referenced this issue Sep 24, 2024
sebjf added a commit that referenced this issue Sep 24, 2024
sebjf added a commit that referenced this issue Sep 25, 2024
sebjf added a commit that referenced this issue Sep 25, 2024
sebjf added a commit that referenced this issue Sep 25, 2024
…to use our svgexporter which was inteferring on travis
sebjf added a commit that referenced this issue Sep 25, 2024
sebjf added a commit that referenced this issue Sep 27, 2024
carmenfan added a commit that referenced this issue Oct 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants