1 /******************************************************************************
2  *
3  * Project:  DWG Translator
4  * Purpose:  Implements translation support for DIMENSION elements as a part
5  *           of the OGRDWGLayer class.
6  * Author:   Frank Warmerdam, warmerdam@pobox.com
7  *
8  ******************************************************************************
9  * Copyright (c) 2011, Frank Warmerdam <warmerdam@pobox.com>
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  ****************************************************************************/
29 
30 #include "ogr_dwg.h"
31 #include "cpl_conv.h"
32 
33 CPL_CVSID("$Id: ogrdwg_dimension.cpp 7e07230bbff24eb333608de4dbd460b7312839d0 2017-12-11 19:08:47Z Even Rouault $")
34 
35 /************************************************************************/
36 /*                         TranslateDIMENSION()                         */
37 /************************************************************************/
38 
TranslateDIMENSION(OdDbEntityPtr poEntity)39 OGRFeature *OGRDWGLayer::TranslateDIMENSION( OdDbEntityPtr poEntity )
40 
41 {
42     OdDbDimensionPtr poDim = OdDbDimension::cast( poEntity );
43     OGRFeature *poFeature = new OGRFeature( poFeatureDefn );
44 
45     double dfHeight = CPLAtof(poDS->GetVariable("$DIMTXT", "2.5"));
46     OdGePoint3d oTextPos, oTarget1, oTarget2, oArrow1;
47     CPLString osText;
48 
49     TranslateGenericProperties( poFeature, poEntity );
50 
51 /* -------------------------------------------------------------------- */
52 /*      Generic Dimension stuff.                                        */
53 /* -------------------------------------------------------------------- */
54     osText = (const char *) poDim->dimensionText();
55 
56     oTextPos = poDim->textPosition();
57 
58 /* -------------------------------------------------------------------- */
59 /*      Specific based on the subtype.                                  */
60 /* -------------------------------------------------------------------- */
61     OdRxClass *poClass = poEntity->isA();
62     const OdString osName = poClass->name();
63     const char *pszEntityClassName = (const char *) osName;
64 
65     if( EQUAL(pszEntityClassName,"AcDbRotatedDimension") )
66     {
67         OdDbRotatedDimensionPtr poRDim = OdDbDimension::cast( poEntity );
68 
69         oTarget2 = poRDim->xLine1Point();
70         oTarget1 = poRDim->xLine2Point();
71         oArrow1 = poRDim->dimLinePoint();
72     }
73 
74     else if( EQUAL(pszEntityClassName,"AcDbAlignedDimension") )
75     {
76         OdDbAlignedDimensionPtr poADim = OdDbDimension::cast( poEntity );
77 
78         oTarget2 = poADim->xLine1Point();
79         oTarget1 = poADim->xLine2Point();
80         oArrow1 = poADim->dimLinePoint();
81     }
82 
83 /*************************************************************************
84 
85    DIMENSION geometry layout
86 
87                   (11,21)(text center point)
88         |          DimText                  |
89 (10,20) X<--------------------------------->X (Arrow2 - computed)
90 (Arrow1)|                                   |
91         |                                   |
92         |                                   X (13,23) (Target2)
93         |
94         X (14,24) (Target1)
95 
96 Given:
97   Locations Arrow1, Target1, and Target2 we need to compute Arrow2.
98 
99 Steps:
100  1) Compute direction vector from Target1 to Arrow1 (Vec1).
101  2) Compute direction vector for arrow as perpendicular to Vec1 (call Vec2).
102  3) Compute Arrow2 location as intersection between line defined by
103     Vec2 and Arrow1 and line defined by Target2 and direction Vec1 (call Arrow2)
104 
105 Then we can draw lines for the various components.
106 
107 Note that Vec1 and Vec2 may be horizontal, vertical or on an angle but
108 the approach is as above in all these cases.
109 
110 *************************************************************************/
111 
112 /* -------------------------------------------------------------------- */
113 /*      Step 1, compute direction vector between Target1 and Arrow1.    */
114 /* -------------------------------------------------------------------- */
115     double dfVec1X, dfVec1Y;
116 
117     dfVec1X = (oArrow1.x - oTarget1.x);
118     dfVec1Y = (oArrow1.y - oTarget1.y);
119 
120 /* -------------------------------------------------------------------- */
121 /*      Step 2, compute the direction vector from Arrow1 to Arrow2      */
122 /*      as a perpendicular to Vec1.                                     */
123 /* -------------------------------------------------------------------- */
124     double dfVec2X, dfVec2Y;
125 
126     dfVec2X = dfVec1Y;
127     dfVec2Y = -dfVec1X;
128 
129 /* -------------------------------------------------------------------- */
130 /*      Step 3, compute intersection of line from target2 along         */
131 /*      direction vector 1, with the line through Arrow1 and            */
132 /*      direction vector 2.                                             */
133 /* -------------------------------------------------------------------- */
134     double dfL1M, dfL1B, dfL2M, dfL2B;
135     double dfArrowX2, dfArrowY2;
136 
137     // special case if vec1 is vertical.
138     if( dfVec1X == 0.0 )
139     {
140         dfArrowX2 = oTarget2.x;
141         dfArrowY2 = oArrow1.y;
142     }
143 
144     // special case if vec2 is horizontal.
145     else if( dfVec1Y == 0.0 )
146     {
147         dfArrowX2 = oArrow1.x;
148         dfArrowY2 = oTarget2.y;
149     }
150 
151     else // General case for diagonal vectors.
152     {
153         // first convert vec1 + target2 into y = mx + b format: call this L1
154 
155         dfL1M = dfVec1Y / dfVec1X;
156         dfL1B = oTarget2.y - dfL1M * oTarget2.x;
157 
158         // convert vec2 + Arrow1 into y = mx + b format, call this L2
159 
160         dfL2M = dfVec2Y / dfVec2X;
161         dfL2B = oArrow1.y - dfL2M * oArrow1.x;
162 
163         // Compute intersection x = (b2-b1) / (m1-m2)
164 
165         dfArrowX2 = (dfL2B - dfL1B) / (dfL1M-dfL2M);
166         dfArrowY2 = dfL2M * dfArrowX2 + dfL2B;
167     }
168 
169 /* -------------------------------------------------------------------- */
170 /*      Compute the text angle.                                         */
171 /* -------------------------------------------------------------------- */
172     double dfAngle = atan2(dfVec2Y,dfVec2X) * 180.0 / M_PI;
173 
174 /* -------------------------------------------------------------------- */
175 /*      Rescale the direction vectors so we can use them in             */
176 /*      constructing arrowheads.  We want them to be about 3% of the    */
177 /*      length of line on which the arrows will be drawn.               */
178 /* -------------------------------------------------------------------- */
179 #define VECTOR_LEN(x,y) sqrt( (x)*(x) + (y)*(y) )
180 #define POINT_DIST(x1,y1,x2,y2)  VECTOR_LEN((x2-x1),(y2-y1))
181 
182     double dfBaselineLength = POINT_DIST(oArrow1.x,oArrow1.y,
183                                          dfArrowX2,dfArrowY2);
184     double dfTargetLength = dfBaselineLength * 0.03;
185     double dfScaleFactor;
186 
187     // recompute vector 2 to ensure the direction is regular
188     dfVec2X = (dfArrowX2 - oArrow1.x);
189     dfVec2Y = (dfArrowY2 - oArrow1.y);
190 
191     // vector 1
192     dfScaleFactor = dfTargetLength / VECTOR_LEN(dfVec1X,dfVec1Y);
193     dfVec1X *= dfScaleFactor;
194     dfVec1Y *= dfScaleFactor;
195 
196     // vector 2
197     dfScaleFactor = dfTargetLength / VECTOR_LEN(dfVec2X,dfVec2Y);
198     dfVec2X *= dfScaleFactor;
199     dfVec2Y *= dfScaleFactor;
200 
201 /* -------------------------------------------------------------------- */
202 /*      Create geometries for the different components of the           */
203 /*      dimension object.                                               */
204 /* -------------------------------------------------------------------- */
205     OGRMultiLineString *poMLS = new OGRMultiLineString();
206     OGRLineString oLine;
207 
208     // main arrow line between Arrow1 and Arrow2
209     oLine.setPoint( 0, oArrow1.x, oArrow1.y );
210     oLine.setPoint( 1, dfArrowX2, dfArrowY2 );
211     poMLS->addGeometry( &oLine );
212 
213     // dimension line from Target1 to Arrow1 with a small extension.
214     oLine.setPoint( 0, oTarget1.x, oTarget1.y );
215     oLine.setPoint( 1, oArrow1.x + dfVec1X, oArrow1.y + dfVec1Y );
216     poMLS->addGeometry( &oLine );
217 
218     // dimension line from Target2 to Arrow2 with a small extension.
219     oLine.setPoint( 0, oTarget2.x, oTarget2.y );
220     oLine.setPoint( 1, dfArrowX2 + dfVec1X, dfArrowY2 + dfVec1Y );
221     poMLS->addGeometry( &oLine );
222 
223     // add arrow1 arrow head.
224 
225     oLine.setPoint( 0, oArrow1.x, oArrow1.y );
226     oLine.setPoint( 1,
227                     oArrow1.x + dfVec2X*3 + dfVec1X,
228                     oArrow1.y + dfVec2Y*3 + dfVec1Y );
229     poMLS->addGeometry( &oLine );
230 
231     oLine.setPoint( 0, oArrow1.x, oArrow1.y );
232     oLine.setPoint( 1,
233                     oArrow1.x + dfVec2X*3 - dfVec1X,
234                     oArrow1.y + dfVec2Y*3 - dfVec1Y );
235     poMLS->addGeometry( &oLine );
236 
237     // add arrow2 arrow head.
238 
239     oLine.setPoint( 0, dfArrowX2, dfArrowY2 );
240     oLine.setPoint( 1,
241                     dfArrowX2 - dfVec2X*3 + dfVec1X,
242                     dfArrowY2 - dfVec2Y*3 + dfVec1Y );
243     poMLS->addGeometry( &oLine );
244 
245     oLine.setPoint( 0, dfArrowX2, dfArrowY2 );
246     oLine.setPoint( 1,
247                     dfArrowX2 - dfVec2X*3 - dfVec1X,
248                     dfArrowY2 - dfVec2Y*3 - dfVec1Y );
249     poMLS->addGeometry( &oLine );
250 
251     poFeature->SetGeometryDirectly( poMLS );
252 
253     PrepareLineStyle( poFeature );
254 
255 /* -------------------------------------------------------------------- */
256 /*      Is the layer disabled/hidden/frozen/off?                        */
257 /* -------------------------------------------------------------------- */
258     CPLString osLayer = poFeature->GetFieldAsString("Layer");
259 
260     int bHidden =
261         EQUAL(poDS->LookupLayerProperty( osLayer, "Hidden" ), "1");
262 
263 /* -------------------------------------------------------------------- */
264 /*      Work out the color for this feature.                            */
265 /* -------------------------------------------------------------------- */
266     int nColor = 256;
267 
268     if( oStyleProperties.count("Color") > 0 )
269         nColor = atoi(oStyleProperties["Color"]);
270 
271     // Use layer color?
272     if( nColor < 1 || nColor > 255 )
273     {
274         const char *pszValue = poDS->LookupLayerProperty( osLayer, "Color" );
275         if( pszValue != nullptr )
276             nColor = atoi(pszValue);
277     }
278 
279     if( nColor < 1 || nColor > 255 )
280         nColor = 8;
281 
282 /* -------------------------------------------------------------------- */
283 /*      Prepare a new feature to serve as the dimension text label      */
284 /*      feature.  We will push it onto the layer as a pending           */
285 /*      feature for the next feature read.                              */
286 /* -------------------------------------------------------------------- */
287 
288     // a single space suppresses labeling.
289     if( osText == " " )
290         return poFeature;
291 
292     OGRFeature *poLabelFeature = poFeature->Clone();
293 
294     poLabelFeature->SetGeometryDirectly( new OGRPoint( oTextPos.x, oTextPos.y ) );
295 
296     // Do we need to compute the dimension value?
297     if( osText.empty() )
298     {
299         FormatDimension( osText, POINT_DIST( oArrow1.x, oArrow1.y,
300                                              dfArrowX2, dfArrowY2 ) );
301     }
302 
303     CPLString osStyle;
304     char szBuffer[64];
305     char* pszComma = nullptr;
306 
307     osStyle.Printf("LABEL(f:\"Arial\",t:\"%s\",p:5",osText.c_str());
308 
309     if( dfAngle != 0.0 )
310     {
311         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfAngle);
312         pszComma = strchr(szBuffer, ',');
313         if (pszComma)
314             *pszComma = '.';
315         osStyle += CPLString().Printf(",a:%s", szBuffer);
316     }
317 
318     if( dfHeight != 0.0 )
319     {
320         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfHeight);
321         pszComma = strchr(szBuffer, ',');
322         if (pszComma)
323             *pszComma = '.';
324         osStyle += CPLString().Printf(",s:%sg", szBuffer);
325     }
326 
327     const unsigned char *pabyDWGColors = ACGetColorTable();
328 
329     snprintf( szBuffer, sizeof(szBuffer), ",c:#%02x%02x%02x",
330               pabyDWGColors[nColor*3+0],
331               pabyDWGColors[nColor*3+1],
332               pabyDWGColors[nColor*3+2] );
333     osStyle += szBuffer;
334 
335     if( bHidden )
336         osStyle += "00";
337 
338     osStyle += ")";
339 
340     poLabelFeature->SetStyleString( osStyle );
341 
342     apoPendingFeatures.push( poLabelFeature );
343 
344     return poFeature;
345 }
346 
347 /************************************************************************/
348 /*                          FormatDimension()                           */
349 /*                                                                      */
350 /*      Format a dimension number according to the current files        */
351 /*      formatting conventions.                                         */
352 /************************************************************************/
353 
FormatDimension(CPLString & osText,double dfValue)354 void OGRDWGLayer::FormatDimension( CPLString &osText, double dfValue )
355 
356 {
357     int nPrecision = atoi(poDS->GetVariable("$LUPREC","4"));
358     char szFormat[32];
359     char szBuffer[64];
360 
361     // we could do a significantly more precise formatting if we want
362     // to spend the effort.  See QCAD's rs_dimlinear.cpp and related files
363     // for example.
364 
365     snprintf(szFormat, sizeof(szFormat), "%%.%df", nPrecision );
366     CPLsnprintf(szBuffer, sizeof(szBuffer), szFormat, dfValue);
367     char* pszComma = strchr(szBuffer, ',');
368     if (pszComma)
369         *pszComma = '.';
370     osText = szBuffer;
371 }
372