Entity Highlight and Fit to Zoom
Marcellus Zebra
12/2/2011 9:18 AM


Hi, 1) Is possible (End exists examples) implements entity highlight on mouse over? 2) How to implement a 'Fit to screen' function that maximize zoom for current control size? I want to use this function at loading time to show correctly the dwg and also add a 'Fit to screen' button as user tool. Thank's very mutch, 
rammi
12/2/2011 2:17 PM


Hi Marc, regarding your second question Marcellus Zebra wrote: 2) How to implement a 'Fit to screen' function that maximize zoom for current control size? I want to use this function at loading time to show correctly the dwg and also add a 'Fit to screen' button as user tool. That is simple. Calculate the bounds of the current view, possibly including a transformation matrix if the view is rotated or projected perspectively. Use C# Code: int width, height; // view size DxfModel model; // the model Matrix4D transform; // current transformation used for displaying the model BoundsCalculator boundsCalculator = new BoundsCalculator(graphicsConfig); boundsCalculator.GetBounds(model, transform); Bounds3D bounds = boundsCalculator.Bounds; if (bounds.Initialized) { const int margin = 1; // margin around border Matrix4D scaleTransform = DxfUtil.GetScaleTransform( bounds.Corner1, // lower left model bounds.Corner2, // upper right model bounds.Center, // center model new Point3D(margin, height  margin  1, 0d), // lower left screen new Point3D(width  margin  1, margin, 0d), // upper right screen new Point3D(0.5*(height 1), 0.5*(width  1), 0d) // center screen ); // new transform transform = scaleTransform * transform; } The only tricky part is the 1 on width and height, which is because eg you want to map the right border of the model to the rightmost pixel column, and that has address width1 and not width. Many people get that wrong. The above code is centering the view, and you can it use something similar to center on anything else, as long as you know its bounds or how to calculate them. BoundsCalculator will help in many cases. 
Wout
12/5/2011 11:02 AM


Hi, Assuming you are working with Win Forms: About 1), there is currently no canned solution to this, but there have been a few enhancements in CadLib 4.0 this week that make this easier. First you have to find out which entity the mouse is over, you can use the EntitySelector for this (already exists). Then you can use the new WireframeGraphicsCache class that's a drawables container for entities. You'll have to change the WireframeGraphicsCache.Config to use a fixed foreground color. C# Code: WireframeGraphicsCache wireframeGraphicsCache = new WireframeGraphicsCache(false, true); The cache object has a method IList<IWireframeDrawable> GetDrawables(RenderedEntityInfo renderedEntityInfo), in which you can pass the rendered entity info you got from the EntitySelector. So now you have the drawables. Now call GDIGraphics3D.CreateGraphicsFactory(), and use this to draw the entity drawables on. C# Code: IWireframeGraphicsFactory graphicsFactory = gdiGraphics3D.CreateGraphicsFactory(); foreach (IWireframeDrawable drawable in drawablesSelection) { drawable.Draw(graphicsFactory); } The code is fairly new, and the api might change a little in the near future. As I'm writing this, it might be nice to have a way to override a color, so the WireframeGraphicsCache can be used for filling the GDIGraphics3D drawables as well (saves a bit of duplicate work). E.g. (future): C# Code: WireframeGraphicsCache wireframeGraphicsCache = new WireframeGraphicsCache(false, true); DxfLayout layout = model.ActiveLayout; DxfEntityCollection entities; Matrix4D vportTransform = Matrix4D.Identity; DateTime start = DateTime.UtcNow; if (layout == null  model.Header.ShowModelSpace) { DxfVPort activeVPort = model.VPorts.GetActiveVPort(); if (activeVPort != null) { vportTransform = activeVPort.GetTransform(new Size2D(ClientSize.Width, ClientSize.Height)) * vportTransform; } vportTransform = Matrix4D.Identity; wireframeGraphicsCache.CreateDrawables(model, vportTransform); } else { wireframeGraphicsCache.CreateDrawables(model, layout, null); } IWireframeGraphicsFactory graphicsFactory = gdiGraphics3D.CreateGraphicsFactory(); foreach (IWireframeDrawable drawable in wireframeGraphicsCache.Drawables) { drawable.Draw(graphicsFactory); }  Wout 
Marcellus Zebra
12/5/2011 11:17 AM


Hi rammi, C# Code: public void FitToScreen() { int width = this.ClientRectangle.Width, height = this.ClientRectangle.Height; // view size //DxfModel model; // the model Matrix4D transform = CalculateTo2DTransform(); // current transformation used for displaying the model BoundsCalculator boundsCalculator = new BoundsCalculator(); boundsCalculator.GetBounds(model, transform); Bounds3D bounds = boundsCalculator.Bounds; if (bounds.Initialized) { const int margin = 1; // margin around border Matrix4D scaleTransform = WW.Cad.Base.DxfUtil.GetScaleTransform( bounds.Corner1, // lower left model bounds.Corner2, // upper right model bounds.Center, // center model new Point3D(margin, height  margin  1, 0d), // lower left screen new Point3D(width  margin  1, margin, 0d), // upper right screen new Point3D(0.5 * (height  1), 0.5 * (width  1), 0d) // center screen ); // new transform transform = scaleTransform * transform; //My Code gdiGraphics3D.To2DTransform = transform; Invalidate(); } } And I tested it with attatched dwg. 1) Fitted image is fplipped! Thank's marc. 
rammi
12/5/2011 1:22 PM


Hi Marcellus, my fault, not yours. I mixed together two examples, but didn't test the code. Use C# Code: Matrix4D scaleTransform = WW.Cad.Base.DxfUtil.GetScaleTransform( bounds.Corner1, // upper left model (transform inverts Y!) bounds.Corner2, // lower right model (transform inverts Y!) bounds.Center, // center model new Point3D(margin, margin, 0d), // upper left screen new Point3D(width  margin  1, height  margin  1, 0d), // lower right screen new Point3D(0.5 * (height  1), 0.5 * (width  1), 0d) // center screen ); Background: Sorry for the confusion, 
Marcellus Zebra
12/5/2011 2:10 PM


Thank's very mutch rammi. I apply calculated transofr with gdiGraphics3D.To2DTransform = transform; but now if I pan or zom all start from previous view! Thank's 
rammi
12/6/2011 5:39 PM


Yes, if panning and zooming use the standard code of the examples each of the operations will set To2DTransform without regards of the intermediate transformation. So you'd have to rewrite that, too. Please excuse, but in order to be more general I'll elaborate a bit now, hopefully giving more users the chance to brew their own implementation. Linear Algebra M = M_{n} * ... * M_{2} * M_{1} You'll note that the indexes are running backward, the reason for that is that CadLib is using column orders, so the order in which transformations in the chain above are applied is right to left. screen space ← M_{n} ← ... ← M_{2} ← M_{1} ← model space (you could go the other way round if you invert each transformation) One main property of matrixes and transformations is that their multiplication (chaining) is not commutative, so in general for two matixes A and B: A * B ≠ B * A No you want to change the transformation M, and this changes usually either happen in screen space (eg panning three pixels to the right) or in model space (panning from one entity to another). It's crucial to be clear about this space in which the change happens. If a changing transformation C happens in screen space, this means that the previous transformation is multiplied from the left, so you get M_{new} = C_{screen} * M If it happens in model space, it is multiplied from the right: M_{new} = M * C_{model} But how to apply this to a chain of transformations? As said before, you'll usually have simpler transformations put together in your chain, and if C is eg a translation, you'll want to combine it with the translation matrix in your chain. Let's call this matrix of interest T, multiply all matrixes left of it together to get a matrix L, multiply all matrixes right of it together to get a matrix R, we have the situation M = L * T * R This is still perfectly general, but in some case L or R might be the indenty matrix, and could be left away. Now back to a transformation happening in screen space: M_{new} = C * M = C * L * T * R This is what we have. But what we want is M_{new} = L * T_{new} * R i.e. only T is influenced by C, the rest stays as it was before. So we have to solve this equation C * L * T * R = L * T_{new} * R Remember that we cannot just exchange C and L on the left side, because that is not allowed for matrixes. But we can multiply with the inverse of R (i.e. R^{1}) from right on both sides, which will remove R from the equation, because R * R^{1} is the identity matrix: C * L * T = L * T_{new} Now we use the same trick to remove L from the right side of the equation: multiply both sides with L^{1} from left (and switch sides to get a more pleasing equation): T_{new} = L^{1} * C * L * T That is how we calculate a new matrix T_{new} in our chain when a transformation happens in screen space:
T_{new} = T * R * C * R^{1} A Simple Example Not lets assume that our chain of simple transformations consists of a translation matrix T(x,y,z) and a scaling matrix S(s,s,s) (s is just here because we know that the y axis is inverting its direction between model and screen). This is suffice for most 2D programs. What else: screen coordinates always run from 0 to screenWidth1 for x and 0 to screenHeight1 for y, and the screen center sc has coordinates (in 3D) Only thing left to decide is the order in which we want to apply the simple operations T and S. There is not much difference, but some operation may be slightly easier one way or the other. Here we'll first use T, then S. So our complete To2DTransform M becomes Region to screen We don't want to scale differently in x and y, because it would distort the view, so we'll have to use the same scaling in both directions. And to keep everything visible we have to choose the smaller of both scalings: The translation is a bit more difficult. It's easier to do in two steps. First we will move the bounding box so its center is at the origin of the coordinate system. This is done with the translation This gives the complete transformation as M = T_{screen} * S * T_{bbox} Comparing with the desired form M = S * T we can again use the equation like we did before S * T = T_{screen} * S * T_{bbox} and get T = S^{1} * T_{screen} * S * T_{bbox} Panning by mouse M_{new} = T_{mouse} * M = T_{mouse} * S * T = S * T_{new} and can calculate the new translation matrix T_{new} = S^{1} * T_{mouse} * S * T Zooming around a given screen coordinate Scaling is always happening around the origin of the coordinate system, so in order to keep the desired mouse operation fix we have do a bit more steps:
M_{new} = T(m_{x}, m_{y}, 0) * S(f) * T(m_{x}, m_{y}, 0) * M = T(m_{x}, m_{y}, 0) * S(f) * T(m_{x}, m_{y}, 0) * S * T Because the change includes both a scaling and a translation we'll have to calculate a new scaling S_{new} and a new translation T_{new}: We have to apply our old trick from above more than once, but if we do we'll get The End Good luck, 
JoseM
11/1/2016 7:47 PM


Hello Marc If you want to reset the object transform this is my code: C# Code: int width = this.ClientRectangle.Width; int height = this.ClientRectangle.Height; //Reset transform transformationProvider.ResetTransforms(); //Reset Events (Opcional) I don't know if it's necessary. zoomWheelInteractor.ResetState(); rectZoomInteractor.ResetState(); panInteractor.ResetState(); Matrix4D transform = CalculateTo2DTransform(); //Transformacion actual del dibujo por pantalla del modelo BoundsCalculator boundsCalculator = new BoundsCalculator(); boundsCalculator.GetBounds(model, transform); Bounds3D bounds = boundsCalculator.Bounds; if (bounds.Initialized) { const int margin = 5; Matrix4D scaleTransform = WW.Cad.Base.DxfUtil.GetScaleTransform( bounds.Corner1, // upper left model (transform inverts Y!) bounds.Corner2, // lower right model (transform inverts Y!) bounds.Center, // center model new Point3D(margin, margin, 0d), // upper left screen new Point3D(width  margin  1, height  margin  1, 0d), // lower right screen new Point3D(0.5 * (width  1), 0.5 * (height  1), 0d) // center screen ); // new transform transform = scaleTransform * transform; //My Code gdiGraphics3D.To2DTransform = transform; from2DTransform = gdiGraphics3D.To2DTransform.GetInverse(); //CachÃ© Invalidate();

sarge
3/29/2017 6:53 PM


Hi, I knew this thread is old but I didn't find my issue. At first some images: Most code is from the exmaples with some fitting to my purposes. I think the problem will come from the transformation provider and the scaling. For now it's nearly impossible to find that point where it happens (using VS2017CE) maybe with the ep edtion it esay to find .. Rammi mentioned above "Yes, if panning and zooming use the standard code of the examples each of the operations will set To2DTransform without regards of the intermediate transformation. So you'd have to rewrite that, too." I think this snippet is the cause of my problem: C# Code: private Matrix4D CalculateTo2DTransform() { transformationProvider.ViewWindow = GetClientRectangle2D(); Matrix4D to2DTransform = Matrix4D.Identity; if (model != null && bounds != null) to2DTransform = transformationProvider.CompleteTransform; gdiGraphics3D.To2DTransform = to2DTransform; from2DTransform = gdiGraphics3D.To2DTransform.GetInverse(); return to2DTransform; } Any how I have to avoid that "correct value" in gdiGrahics3D will changed by the "wrong value" from transformprovider. But I don't have any idea how to achive this. I suggestion, sample code or (non math) explaination would help me alot. kind regards 
Wout
3/29/2017 7:02 PM


Which TransformationProvider are you using? The 2D one or the 3D? The 3D has some margin around the drawing by default to allow for 3D rotation within the window.  Wout 