1 /******************************************************************************
2 *
3 * Project: DXF Translator
4 * Purpose: Implements translation support for DIMENSION elements as a part
5 * of the OGRDXFLayer class.
6 * Author: Frank Warmerdam, warmerdam@pobox.com
7 *
8 ******************************************************************************
9 * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
10 * Copyright (c) 2010, Even Rouault <even dot rouault at spatialys.com>
11 * Copyright (c) 2017, Alan Thomas <alant@outlook.com.au>
12 *
13 * Permission is hereby granted, free of charge, to any person obtaining a
14 * copy of this software and associated documentation files (the "Software"),
15 * to deal in the Software without restriction, including without limitation
16 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 * and/or sell copies of the Software, and to permit persons to whom the
18 * Software is furnished to do so, subject to the following conditions:
19 *
20 * The above copyright notice and this permission notice shall be included
21 * in all copies or substantial portions of the Software.
22 *
23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 * DEALINGS IN THE SOFTWARE.
30 ****************************************************************************/
31
32 #include "ogr_dxf.h"
33 #include "cpl_conv.h"
34
35 #include <stdexcept>
36
37 CPL_CVSID("$Id: ogrdxf_dimension.cpp 8ca42e1b9c2e54b75d35e49885df9789a2643aa4 2020-05-17 21:43:40 +0200 Even Rouault $")
38
39 /************************************************************************/
40 /* PointDist() */
41 /************************************************************************/
42
PointDist(double x1,double y1,double x2,double y2)43 inline static double PointDist( double x1, double y1, double x2, double y2 )
44 {
45 return sqrt( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) );
46 }
47
48 /************************************************************************/
49 /* TranslateDIMENSION() */
50 /************************************************************************/
51
TranslateDIMENSION()52 OGRDXFFeature *OGRDXFLayer::TranslateDIMENSION()
53
54 {
55 char szLineBuf[257];
56 int nCode = 0;
57 // int nDimType = 0;
58 OGRDXFFeature *poFeature = new OGRDXFFeature( poFeatureDefn );
59 double dfArrowX1 = 0.0;
60 double dfArrowY1 = 0.0;
61 // double dfArrowZ1 = 0.0;
62 double dfTargetX1 = 0.0;
63 double dfTargetY1 = 0.0;
64 // double dfTargetZ1 = 0.0;
65 double dfTargetX2 = 0.0;
66 double dfTargetY2 = 0.0;
67 // double dfTargetZ2 = 0.0;
68 double dfTextX = 0.0;
69 double dfTextY = 0.0;
70 // double dfTextZ = 0.0;
71
72 bool bReadyForDimstyleOverride = false;
73
74 bool bHaveBlock = false;
75 CPLString osBlockName;
76 CPLString osText;
77
78 std::map<CPLString,CPLString> oDimStyleProperties;
79 poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
80
81 while( (nCode = poDS->ReadValue(szLineBuf,sizeof(szLineBuf))) > 0 )
82 {
83 switch( nCode )
84 {
85 case 2:
86 bHaveBlock = true;
87 osBlockName = szLineBuf;
88 break;
89
90 case 3:
91 // 3 is the dimension style name. We don't need to store it,
92 // let's just fetch the dimension style properties
93 poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
94 break;
95
96 case 10:
97 dfArrowX1 = CPLAtof(szLineBuf);
98 break;
99
100 case 20:
101 dfArrowY1 = CPLAtof(szLineBuf);
102 break;
103
104 case 30:
105 /* dfArrowZ1 = CPLAtof(szLineBuf); */
106 break;
107
108 case 11:
109 dfTextX = CPLAtof(szLineBuf);
110 break;
111
112 case 21:
113 dfTextY = CPLAtof(szLineBuf);
114 break;
115
116 case 31:
117 /* dfTextZ = CPLAtof(szLineBuf); */
118 break;
119
120 case 13:
121 dfTargetX2 = CPLAtof(szLineBuf);
122 break;
123
124 case 23:
125 dfTargetY2 = CPLAtof(szLineBuf);
126 break;
127
128 case 33:
129 /* dfTargetZ2 = CPLAtof(szLineBuf); */
130 break;
131
132 case 14:
133 dfTargetX1 = CPLAtof(szLineBuf);
134 break;
135
136 case 24:
137 dfTargetY1 = CPLAtof(szLineBuf);
138 break;
139
140 case 34:
141 /* dfTargetZ1 = CPLAtof(szLineBuf); */
142 break;
143
144 case 70:
145 /* nDimType = atoi(szLineBuf); */
146 break;
147
148 case 1:
149 osText = szLineBuf;
150 break;
151
152 case 1001:
153 bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
154 break;
155
156 case 1070:
157 if( bReadyForDimstyleOverride )
158 {
159 // Store DIMSTYLE override values in the dimension
160 // style property map. The nInnerCode values match the
161 // group codes used in the DIMSTYLE table.
162 const int nInnerCode = atoi(szLineBuf);
163 const char* pszProperty = ACGetDimStylePropertyName(nInnerCode);
164 if( pszProperty )
165 {
166 nCode = poDS->ReadValue(szLineBuf,sizeof(szLineBuf));
167 if( nCode == 1005 || nCode == 1040 || nCode == 1070 )
168 oDimStyleProperties[pszProperty] = szLineBuf;
169 }
170 }
171 break;
172
173 default:
174 TranslateGenericProperty( poFeature, nCode, szLineBuf );
175 break;
176 }
177 }
178 if( nCode < 0 )
179 {
180 DXF_LAYER_READER_ERROR();
181 delete poFeature;
182 return nullptr;
183 }
184 if( nCode == 0 )
185 poDS->UnreadValue();
186
187 // If osBlockName (group code 2) refers to a valid block, we can just insert
188 // that block - that should give us the correctly exploded geometry of this
189 // dimension. If this value is missing, or doesn't refer to a valid block,
190 // we will need to use our own logic to generate the dimension lines.
191 if( bHaveBlock && osBlockName.length() > 0 )
192 {
193 // Always inline the block, because this is an anonymous block that the
194 // user likely doesn't know or care about
195 try
196 {
197 OGRDXFFeature* poBlockFeature = InsertBlockInline(
198 CPLGetErrorCounter(), osBlockName,
199 OGRDXFInsertTransformer(), poFeature, apoPendingFeatures,
200 true, false );
201
202 return poBlockFeature; // may be NULL but that is OK
203 }
204 catch( const std::invalid_argument& ) {}
205 }
206
207 // Unpack the dimension style
208 const double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
209 const double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
210 const double dfExtLineExtendLength = CPLAtof(oDimStyleProperties["DIMEXE"]);
211 const double dfExtLineOffset = CPLAtof(oDimStyleProperties["DIMEXO"]);
212 const bool bWantExtLine1 = atoi(oDimStyleProperties["DIMSE1"]) == 0;
213 const bool bWantExtLine2 = atoi(oDimStyleProperties["DIMSE2"]) == 0;
214 const double dfTextHeight = CPLAtof(oDimStyleProperties["DIMTXT"]);
215 const int nUnitsPrecision = atoi(oDimStyleProperties["DIMDEC"]);
216 const bool bTextSupposedlyCentered = atoi(oDimStyleProperties["DIMTAD"]) == 0;
217 const CPLString osTextColor = oDimStyleProperties["DIMCLRT"];
218
219 /*************************************************************************
220
221 DIMENSION geometry layout
222
223 (11,21)(text center point)
224 | DimText |
225 (10,20) X<--------------------------------->X (Arrow2 - computed)
226 (Arrow1)| |
227 | |
228 | X (13,23) (Target2)
229 |
230 X (14,24) (Target1)
231
232 Given:
233 Locations Arrow1, Target1, and Target2 we need to compute Arrow2.
234
235 Steps:
236 1) Compute direction vector from Target1 to Arrow1 (Vec1).
237 2) Compute direction vector for arrow as perpendicular to Vec1 (call Vec2).
238 3) Compute Arrow2 location as intersection between line defined by
239 Vec2 and Arrow1 and line defined by Target2 and direction Vec1 (call Arrow2)
240
241 Then we can draw lines for the various components.
242
243 Note that Vec1 and Vec2 may be horizontal, vertical or on an angle but
244 the approach is as above in all these cases.
245
246 *************************************************************************/
247
248 /* -------------------------------------------------------------------- */
249 /* Step 1, compute direction vector between Target1 and Arrow1. */
250 /* -------------------------------------------------------------------- */
251 double dfVec1X = dfArrowX1 - dfTargetX1;
252 double dfVec1Y = dfArrowY1 - dfTargetY1;
253
254 // make Vec1 a unit vector
255 double dfVec1Length = PointDist(0, 0, dfVec1X, dfVec1Y);
256 if( dfVec1Length > 0.0 )
257 {
258 dfVec1X /= dfVec1Length;
259 dfVec1Y /= dfVec1Length;
260 }
261
262 /* -------------------------------------------------------------------- */
263 /* Step 2, compute the direction vector from Arrow1 to Arrow2 */
264 /* as a perpendicular to Vec1. */
265 /* -------------------------------------------------------------------- */
266 double dfVec2X = dfVec1Y;
267 double dfVec2Y = -dfVec1X;
268
269 /* -------------------------------------------------------------------- */
270 /* Step 3, compute intersection of line from target2 along */
271 /* direction vector 1, with the line through Arrow1 and */
272 /* direction vector 2. */
273 /* -------------------------------------------------------------------- */
274 double dfArrowX2 = 0.0;
275 double dfArrowY2 = 0.0;
276
277 // special case if vec1 is zero, which means the arrow and target
278 // points coincide.
279 if( dfVec1X == 0.0 && dfVec1Y == 0.0 )
280 {
281 dfArrowX2 = dfTargetX2;
282 dfArrowY2 = dfTargetY2;
283 }
284
285 // special case if vec1 is vertical.
286 else if( dfVec1X == 0.0 )
287 {
288 dfArrowX2 = dfTargetX2;
289 dfArrowY2 = dfArrowY1;
290 }
291
292 // special case if vec1 is horizontal.
293 else if( dfVec1Y == 0.0 )
294 {
295 dfArrowX2 = dfArrowX1;
296 dfArrowY2 = dfTargetY2;
297 }
298
299 else // General case for diagonal vectors.
300 {
301 // first convert vec1 + target2 into y = mx + b format: call this L1
302
303 const double dfL1M = dfVec1Y / dfVec1X;
304 const double dfL1B = dfTargetY2 - dfL1M * dfTargetX2;
305
306 // convert vec2 + Arrow1 into y = mx + b format, call this L2
307
308 const double dfL2M = dfVec2Y / dfVec2X;
309 const double dfL2B = dfArrowY1 - dfL2M * dfArrowX1;
310
311 // Compute intersection x = (b2-b1) / (m1-m2)
312
313 dfArrowX2 = (dfL2B - dfL1B) / (dfL1M-dfL2M);
314 dfArrowY2 = dfL2M * dfArrowX2 + dfL2B;
315 }
316
317 /* -------------------------------------------------------------------- */
318 /* Create geometries for the different components of the */
319 /* dimension object. */
320 /* -------------------------------------------------------------------- */
321 OGRMultiLineString *poMLS = new OGRMultiLineString();
322 OGRLineString oLine;
323
324 // Main arrow line between Arrow1 and Arrow2.
325 oLine.setPoint( 0, dfArrowX1, dfArrowY1 );
326 oLine.setPoint( 1, dfArrowX2, dfArrowY2 );
327 poMLS->addGeometry( &oLine );
328
329 // Insert default arrowheads.
330 InsertArrowhead( poFeature, "", &oLine, dfArrowheadSize * dfScale );
331 InsertArrowhead( poFeature, "", &oLine, dfArrowheadSize * dfScale, true );
332
333 // Dimension line from Target1 to Arrow1 with a small extension.
334 oLine.setPoint( 0, dfTargetX1 + dfVec1X * dfExtLineOffset,
335 dfTargetY1 + dfVec1Y * dfExtLineOffset );
336 oLine.setPoint( 1, dfArrowX1 + dfVec1X * dfExtLineExtendLength,
337 dfArrowY1 + dfVec1Y * dfExtLineExtendLength );
338 if( bWantExtLine1 && oLine.get_Length() > 0.0 ) {
339 poMLS->addGeometry( &oLine );
340 }
341
342 // Dimension line from Target2 to Arrow2 with a small extension.
343 oLine.setPoint( 0, dfTargetX2 + dfVec1X * dfExtLineOffset,
344 dfTargetY2 + dfVec1Y * dfExtLineOffset );
345 oLine.setPoint( 1, dfArrowX2 + dfVec1X * dfExtLineExtendLength,
346 dfArrowY2 + dfVec1Y * dfExtLineExtendLength );
347 if( bWantExtLine2 && oLine.get_Length() > 0.0 ) {
348 poMLS->addGeometry( &oLine );
349 }
350
351 poFeature->SetGeometryDirectly( poMLS );
352
353 PrepareLineStyle( poFeature );
354
355 /* -------------------------------------------------------------------- */
356 /* Prepare a new feature to serve as the dimension text label */
357 /* feature. We will push it onto the layer as a pending */
358 /* feature for the next feature read. */
359 /* */
360 /* The DXF format supports a myriad of options for dimension */
361 /* text placement, some of which involve the drawing of */
362 /* additional lines and the like. For now we ignore most of */
363 /* those properties and place the text alongside the dimension */
364 /* line. */
365 /* -------------------------------------------------------------------- */
366
367 // a single space suppresses labeling.
368 if( osText == " " )
369 return poFeature;
370
371 OGRDXFFeature *poLabelFeature = poFeature->CloneDXFFeature();
372
373 poLabelFeature->SetGeometryDirectly( new OGRPoint( dfTextX, dfTextY ) );
374
375 if( osText.empty() )
376 osText = "<>";
377
378 // Do we need to compute the dimension value?
379 size_t nDimensionPos = osText.find("<>");
380 if( nDimensionPos == std::string::npos )
381 {
382 poLabelFeature->SetField( "Text", TextUnescape( osText, true ) );
383 }
384 else
385 {
386 // Replace the first occurrence of <> with the dimension
387 CPLString osDimensionText;
388 FormatDimension( osDimensionText,
389 PointDist( dfArrowX1, dfArrowY1, dfArrowX2, dfArrowY2 ),
390 nUnitsPrecision );
391 osText.replace( nDimensionPos, 2, osDimensionText );
392 poLabelFeature->SetField( "Text", TextUnescape( osText, true ) );
393 }
394
395 CPLString osStyle;
396 char szBuffer[64];
397
398 osStyle.Printf("LABEL(f:\"Arial\",t:\"%s\"",
399 TextUnescape( osText.c_str(), true ).c_str());
400
401 // If the text is supposed to be centered on the line, we align
402 // it above the line. Drawing it properly would require us to
403 // work out the width of the text, which seems like too much
404 // effort for what is just a fallback renderer.
405 if( bTextSupposedlyCentered )
406 osStyle += ",p:11";
407 else
408 osStyle += ",p:5";
409
410 // Compute the text angle. Use atan to avoid upside-down text
411 const double dfTextAngle = ( dfArrowX1 == dfArrowX2 ) ?
412 -90.0 :
413 atan( (dfArrowY1 - dfArrowY2) / (dfArrowX1 - dfArrowX2) ) * 180.0 / M_PI;
414
415 if( dfTextAngle != 0.0 )
416 {
417 CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
418 osStyle += CPLString().Printf(",a:%s", szBuffer);
419 }
420
421 if( dfTextHeight != 0.0 )
422 {
423 CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g",
424 dfTextHeight * dfScale);
425 osStyle += CPLString().Printf(",s:%sg", szBuffer);
426 }
427
428 poLabelFeature->oStyleProperties["Color"] = osTextColor;
429 osStyle += ",c:";
430 osStyle += poLabelFeature->GetColor( poDS, poFeature );
431
432 osStyle += ")";
433
434 poLabelFeature->SetStyleString( osStyle );
435
436 apoPendingFeatures.push( poLabelFeature );
437
438 return poFeature;
439 }
440
441 /************************************************************************/
442 /* FormatDimension() */
443 /* */
444 /* Format a dimension number according to the current files */
445 /* formatting conventions. */
446 /************************************************************************/
447
FormatDimension(CPLString & osText,const double dfValue,int nPrecision)448 void OGRDXFLayer::FormatDimension( CPLString &osText, const double dfValue,
449 int nPrecision )
450
451 {
452 if( nPrecision < 0 )
453 nPrecision = 0;
454 else if( nPrecision > 20 )
455 nPrecision = 20;
456
457 // We could do a significantly more precise formatting if we want
458 // to spend the effort. See QCAD's rs_dimlinear.cpp and related files
459 // for example.
460
461 char szFormat[32];
462 snprintf(szFormat, sizeof(szFormat), "%%.%df", nPrecision );
463
464 char szBuffer[64];
465 CPLsnprintf(szBuffer, sizeof(szBuffer), szFormat, dfValue);
466
467 osText = szBuffer;
468 }
469