001/*
002 *   IGESGeomReader -- A class that can read and write an IGES formatted geometry file.
003 *
004 *   Copyright (C) 2010-2016, Joseph A. Huwaldt
005 *   All rights reserved.
006 *
007 *   This library is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   This library is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *   Lesser General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public License
018 *   along with this program; if not, write to the Free Software
019 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
020 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
021 */
022package geomss.geom.reader;
023
024import geomss.geom.*;
025import geomss.geom.nurbs.BasicNurbsCurve;
026import geomss.geom.nurbs.ControlPoint;
027import geomss.geom.nurbs.CurveUtils;
028import geomss.geom.nurbs.NurbsCurve;
029import geomss.geom.reader.iges.*;
030import jahuwaldt.js.param.Parameter;
031import java.io.File;
032import java.io.IOException;
033import java.io.PrintWriter;
034import java.io.RandomAccessFile;
035import java.nio.charset.StandardCharsets;
036import java.text.MessageFormat;
037import java.util.List;
038import java.util.Locale;
039import static java.util.Objects.isNull;
040import static java.util.Objects.nonNull;
041import static java.util.Objects.requireNonNull;
042import javax.measure.quantity.Length;
043import javax.measure.unit.SI;
044import javax.measure.unit.Unit;
045import javolution.context.StackContext;
046import javolution.util.FastMap;
047import javolution.util.FastTable;
048
049/**
050 * A {@link GeomReader} for reading and writing geometry to an IGES
051 * formatted transfer file. This implementation ignores many the IGES entity types at this
052 * time.
053 *
054 * <p> Modified by: Joseph A. Huwaldt </p>
055 *
056 * @author Joseph A. Huwaldt, Date: August 21, 2010
057 * @version September 9, 2016
058 */
059public class IGESGeomReader extends AbstractGeomReader {
060
061    //  A brief description of the data read by this reaader.
062    private static final String DESCRIPTION = RESOURCES.getString("igesDescription");
063
064    //  The preferred file extension for files of this reader's type.
065    public static final String EXTENSION = "igs";
066
067    /**
068     * A mapping of IGES Entity objects to geometry elements. This is used to prevent
069     * multiple instances of the same element being written to an IGES transfer.
070     */
071    private FastMap<GeomElement, Entity> elementEntityMap;
072
073    /**
074     * Returns a string representation of the object. This will return a brief description
075     * of the format read by this reader.
076     *
077     * @return A brief description of the format read by this reader.
078     */
079    @Override
080    public String toString() {
081        return DESCRIPTION;
082    }
083
084    /**
085     * Returns the preferred file extension (not including the ".") for files of this
086     * GeomReader's type.
087     *
088     * @return The preferred file extension for files of this readers type.
089     */
090    @Override
091    public String getExtension() {
092        return EXTENSION;
093    }
094
095    /**
096     * Method that determines if this reader can read geometry from the specified input
097     * file.
098     *
099     * @param inputFile The input file containing the geometry to be read in.
100     * @return GeomReader.NO if the file format is not recognized by this reader.
101     *         GeomReader.YES if the file format is definitely recognized by this reader.
102     *         GeomReader.MAYBE if the file format might be readable by this reader, but
103     *         that can't easily be determined without actually reading the file.
104     * @throws java.io.IOException If there is a problem reading from the specified
105     * file.
106     */
107    @Override
108    public int canReadData(File inputFile) throws IOException {
109
110        int response = NO;
111        if (inputFile.isFile() && inputFile.exists()) {
112            String name = inputFile.getName();
113            name = name.toLowerCase().trim();
114            if (name.endsWith(".igs") || name.endsWith(".iges"))
115                response = YES;
116        }
117
118        return response;
119    }
120
121    /**
122     * Reads in an IGES geometry file from the specified input file and returns a
123     * {@link GeometryList} object that contains the geometry from the file.
124     *
125     * @param inputFile The input file containing the geometry to be read in. May not
126     *                  be null.
127     * @return A {@link GeometryList} object containing the geometry read in from the
128     *         file. If the file has no readable geometry in it, then this list will have
129     *         no elements in it (will have a size() of 0).
130     * @throws IOException If there is a problem reading the specified file.
131     */
132    @Override
133    public GeometryList read(File inputFile) throws IOException {
134        requireNonNull(inputFile);
135        _warnings.clear();
136
137        //  Create an empty geometry list.
138        GeometryList output = GeomList.newInstance();
139
140        //  IGES files are required to be in ASCII with U.S. style number formatting.
141        //  Get the default locale and save it.  Then change the default locale to US.
142        Locale defLocale = Locale.getDefault();
143        Locale.setDefault(Locale.US);
144
145        // Create a reader to access the IGES ASCII file.
146        try (RandomAccessFile in = new RandomAccessFile(inputFile, "r")) {
147
148            //  Use the IGES library to do the actual reading of the file.
149            Part part = new Part();
150            part.read(in);
151
152            //  Store any error/warning messages.
153            _warnings.addAll(part.getErrors());
154
155            //  Get a list of all the entities read in from the IGES file.
156            List<Entity> entities = part.getEntities();
157
158            //  Copy the geometry from the IGES Part to the output list.
159            for (Entity entity : entities) {
160                if (entity instanceof GeomSSEntity) {
161                    //  We only care about entities that have GeomSS geometry in them.
162                    GeomSSEntity geomEntity = (GeomSSEntity)entity;
163
164                    //  Only include entities that are not subsets of other entities (like lists).
165                    if (!geomEntity.isUsedInList()) {
166
167                        //  Ignore blanked and subordinate entities.
168                        //if (geomEntity.blankedStatus() < 1 && geomEntity.subordStatus() < 1) {
169                        //  Add the geometry to our output list.
170                        GeomElement geom = geomEntity.getGeomElement(GTransform.IDENTITY);
171                        if (geom != null)
172                            output.add(geom);
173                        else {
174                            _warnings.add("GeomEntity has no GeomElement: " + geomEntity);
175                            System.out.println(_warnings.get(_warnings.size()-1));
176                        }
177
178                        /*
179                         * } else { //  Add a note that this was skipped to the
180                         * warning list. StringBuffer msg = new StringBuffer();
181                         * msg.append(" ");
182                         * msg.append(RESOURCES.getString("skippedWarning"));
183                         * msg.append(" "); msg.append(geomEntity.getHeader());
184                         * msg.append(", blanked = ");
185                         * msg.append(geomEntity.blankedStatus()); msg.append(",
186                         * subordStatus = ");
187                         * msg.append(geomEntity.subordStatus());
188                         * _warnings.add(msg.toString());
189                         }
190                         */
191                    }
192                }
193            }
194
195            //  If all we have is a GeomList, just output that.
196            if (output.size() == 1 && output.get(0) instanceof GeomList)
197                output = (GeomList)output.get(0);
198
199            //  Store some of the IGES meta-data in the output list's user data.
200            String str = part.getStartSection().toString();
201            if (!str.equals(""))
202                output.putUserData("IGESStartSection", str);
203            GlobalSection global = part.getGlobalSection();
204            output.putUserData("IGESGrain", global.getGrainParameter());
205            str = global.getProductName();
206            if (!str.equals("")) {
207                output.putUserData("IGESProductName", str);
208                output.setName(str);
209            }
210            str = global.getFileName();
211            if (!str.equals(""))
212                output.putUserData("IGESFileName", str);
213            str = global.getPreprocesorVersion();
214            if (!str.equals(""))
215                output.putUserData("IGESPreprocesorVersion", str);
216            str = global.getDateTime();
217            if (!str.equals(""))
218                output.putUserData("IGESDateTime", str);
219            str = global.getModDateTime();
220            if (!str.equals(""))
221                output.putUserData("IGESModDateTime", str);
222            str = global.getAuthor();
223            if (!str.equals("") && !str.equals("NULL"))
224                output.putUserData("IGESAuthor", str);
225            str = global.getOrganization();
226            if (!str.equals("") && !str.equals("NULL"))
227                output.putUserData("IGESOrganization", str);
228
229            //  Replace any generic lists with more specific ones if possible.
230            output = refineLists(output);
231
232        } finally {
233            //  Restore the default locale.
234            Locale.setDefault(defLocale);
235        }
236
237        return output;
238    }
239
240    /**
241     * Recursively replace any generic lists with more specific ones if possible.
242     */
243    private GeometryList refineLists(GeometryList list) {
244
245        //  First look for any sub-lists.
246        int size = list.size();
247        for (int i = 0; i < size; ++i) {
248            GeomElement element = (GeomElement)list.get(i);
249            if (element instanceof GeometryList) {
250                GeometryList geomList = (GeometryList)element;
251                GeometryList newList = refineLists(geomList);
252                if (newList != geomList)
253                    list.set(i, newList);
254            }
255        }
256
257        //  Is this a "PointString"?
258        boolean isType = true;
259        for (int i = 0; i < size; ++i) {
260            GeomElement element = (GeomElement)list.get(i);
261            if (!(element instanceof GeomPoint)) {
262                isType = false;
263                break;
264            }
265        }
266        if (isType) {
267            PointString newList = PointString.newInstance(list.getName());
268            newList.putAllUserData(list.getAllUserData());
269            newList.addAll(list);
270            return newList;
271        }
272
273        //  Is this a "PointArray"?
274        isType = true;
275        for (int i = 0; i < size; ++i) {
276            GeomElement element = (GeomElement)list.get(i);
277            if (!(element instanceof PointString)) {
278                isType = false;
279                break;
280            }
281        }
282        if (isType) {
283            PointArray newList = PointArray.newInstance(list.getName());
284            newList.putAllUserData(list.getAllUserData());
285            newList.addAll(list);
286            return newList;
287        }
288
289        //  Is this a "PointComponent"?
290        isType = true;
291        for (int i = 0; i < size; ++i) {
292            GeomElement element = (GeomElement)list.get(i);
293            if (!(element instanceof PointArray)) {
294                isType = false;
295                break;
296            }
297        }
298        if (isType) {
299            PointComponent newList = PointComponent.newInstance(list.getName());
300            newList.putAllUserData(list.getAllUserData());
301            newList.addAll(list);
302            return newList;
303        }
304
305        //  Is this a "PointVehicle"?
306        isType = true;
307        for (int i = 0; i < size; ++i) {
308            GeomElement element = (GeomElement)list.get(i);
309            if (!(element instanceof PointComponent)) {
310                isType = false;
311                break;
312            }
313        }
314        if (isType) {
315            PointVehicle newList = PointVehicle.newInstance(list.getName());
316            newList.putAllUserData(list.getAllUserData());
317            newList.addAll(list);
318            return newList;
319        }
320
321        return list;
322    }
323
324    /**
325     * Returns <code>true</code>. This reader can write some entity types to an IGES file.
326     *
327     * @return true
328     */
329    @Override
330    public boolean canWriteData() {
331        return true;
332    }
333
334    /**
335     * Writes out a geometry file for the geometry contained in the supplied
336     * {@link GeometryList} object. If the input geometry is not 3D, it will be forced to
337     * be 3D (by padding if there are too few or by truncating additional dimensions).
338     *
339     * @param outputFile The output File to which the geometry is to be written. May not
340     *                   be null.
341     * @param geometry   The {@link GeometryList} object containing the geometry to be
342     *                   written out. May not be null.
343     * @throws IOException If there is a problem writing to the specified file.
344     */
345    @Override
346    public void write(File outputFile, GeometryList geometry) throws IOException {
347        requireNonNull(outputFile);
348        _warnings.clear();
349        if (!geometry.containsGeometry()) {
350            _warnings.add(RESOURCES.getString("noGeometryWarning"));
351            return;
352        }
353
354        //  IGES files are required to be in ASCII with U.S. style number formatting.
355        //  Get the default locale and save it.  Then change the default locale to US.
356        Locale defLocale = Locale.getDefault();
357        Locale.setDefault(Locale.US);
358
359        //  Create a PrintWriter that writes to the specified ASCII file.
360        StackContext.enter();
361        try (PrintWriter writer = new PrintWriter(outputFile, StandardCharsets.US_ASCII.name())) {
362
363            //  Create an IGES Part to contain the exchange geometry.
364            Part part = new Part();
365            GlobalSection global = part.getGlobalSection();
366
367            //  Set the units being used for this geometry.
368            Unit<Length> unit = geometry.getUnit();
369            global.setUnit(unit);
370
371            //  Look for IGES meta-data in the input list.
372            Object udata = geometry.getUserData("IGESStartSection");
373            if (nonNull(udata))
374                part.getStartSection().setStartSection((String)udata);
375            udata = geometry.getUserData("IGESGrain");
376            if (nonNull(udata)) {
377                Parameter<Length> grain = (Parameter)udata;
378                global.setGrain(grain.getValue(unit));
379            }
380            udata = geometry.getUserData("IGESProductName");
381            if (nonNull(udata))
382                global.setProductName((String)udata);
383            udata = geometry.getUserData("IGESModDateTime");
384            if (nonNull(udata))
385                global.setModDateTime((String)udata);
386            udata = geometry.getUserData("IGESAuthor");
387            if (nonNull(udata))
388                global.setAuthor((String)udata);
389            udata = geometry.getUserData("IGESOrganization");
390            if (nonNull(udata))
391                global.setOrganization((String)udata);
392
393            //  Set the file name.
394            global.setFileName(outputFile.getName());
395
396            //  Recurse through list and create entities for as much of the input geometry as possible.
397            elementEntityMap = new FastMap();
398            List<Entity> entities = part.getEntities();
399            Parameter<Length> grain = global.getGrainParameter().times(100);
400            Parameter<Length> tol = Parameter.valueOf(0.001, SI.METER).to(unit);
401            if (grain.isGreaterThan(tol))
402                tol = grain;
403            addGeomList(tol, part, 1, entities, geometry);
404
405            //  Write out the exchange file.
406            part.write(writer);
407
408            //  Store any error/warning messages.
409            _warnings.addAll(part.getErrors());
410
411            elementEntityMap.clear();
412
413        } finally {
414            StackContext.exit();
415
416            //  Restore the default locale.
417            Locale.setDefault(defLocale);
418        }
419
420    }
421
422    /**
423     * Recursively add a list of geometry objects to the Part's entity list.
424     *
425     * @param tol        The tolerance to use when converting curves to NURBS.
426     * @param part       The part that the entities are associated with.
427     * @param DEnum      The line count from the start of the Directory Entry Section for
428     *                   the next entry (odd number).
429     * @param entityList The list of entities being aggregated.
430     * @param geometry   The list of GeomElement objects to be added to the entity list.
431     * @return The next DEnum after adding this list of geometry elements.
432     */
433    private int addGeomList(Parameter<Length> tol, Part part, int DEnum, List<Entity> entityList, GeometryList geometry) {
434        boolean elementAdded = false;       //  Prevent adding empty lists.
435        FastTable<Entity> childEntities = FastTable.newInstance();
436
437        int size = geometry.size();
438        for (int i = 0; i < size; ++i) {
439            GeomElement element = (GeomElement)geometry.get(i);
440
441            //  First make sure this element has not already been added to the transfer.
442            Entity e = elementEntityMap.get(element);
443            if (nonNull(e)) {
444                //  Element is already a part of this transfer, but make a part of this list.
445                elementAdded = true;
446                childEntities.add(e);
447                continue;
448            }
449
450            //  Deal with curves that are subranged onto a surface as a special case.
451            if (element instanceof SubrangeCurve) {
452                SubrangeCurve subCrv = (SubrangeCurve)element;
453                int numEntities = childEntities.size();
454                if (subCrv.getChild() instanceof Surface) {
455                    DEnum = handleCurveOnSurface(subCrv, DEnum, part, entityList, tol, childEntities);
456                }
457                if (numEntities != childEntities.size())
458                    elementAdded = true;
459                else
460                    _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
461                            element.getClass().getName(), element.getID()));
462                continue;
463            }
464
465            //  Deal with a subrange surface as a special case.
466            if (element instanceof SubrangeSurface) {
467                SubrangeSurface subSrf = (SubrangeSurface)element;
468                int numEntities = childEntities.size();
469                DEnum = handleSubrangeSurface(subSrf, DEnum, part, entityList, tol, childEntities);
470                if (numEntities != childEntities.size())
471                    elementAdded = true;
472                else
473                    _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
474                            element.getClass().getName(), element.getID()));
475                continue;
476            }
477
478            //  Try to create an Entity for this element.
479            e = EntityFactory.create(part, DEnum, element);
480            if (nonNull(e)) {
481                entityList.add(e);
482                childEntities.add(e);
483                elementEntityMap.put(element, e);
484                elementAdded = true;
485                DEnum += 2;             //  Each Directory Entry takes 2 lines.
486
487            } else if (element instanceof GeometryList) {
488                //  It's a list, so recurse down into it.
489                int oldDEnum = DEnum;
490                DEnum = addGeomList(tol, part, DEnum, entityList, (GeometryList)element);
491                if (oldDEnum != DEnum) {
492                    elementAdded = true;
493                    childEntities.add(entityList.get(entityList.size() - 1));
494                }
495
496            } else
497                _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
498                        element.getClass().getName(), element.getID()));
499
500        }   //  Next i
501
502        //  Create an entity for the overall list.
503        if (elementAdded) {
504            Entity e = EntityFactory.createList(part, DEnum, childEntities, geometry.getName());
505            if (nonNull(e)) {
506                entityList.add(e);
507                DEnum += 2;             //  Each Directory Entry takes 2 lines.
508            }
509        }
510
511        FastTable.recycle(childEntities);
512
513        return DEnum;
514    }
515
516    /**
517     * Handles the special case of a subrange curve on a surface.
518     *
519     * @param subCrv        The subrange curve on a surface to be transferred.
520     * @param DEnum         The current DE pointer value.
521     * @param part          The part this curve is associated with.
522     * @param entityList    The list of all the entities being transferred.
523     * @param tol           The tolerance to use when converting curves to NURBS.
524     * @param childEntities The entities that are a part of the current geometry list
525     *                      being transferred.
526     * @return The DEnum incremented to account for the new entities added to the transfer
527     *         by this method.
528     */
529    private int handleCurveOnSurface(SubrangeCurve subCrv, int DEnum, Part part, List<Entity> entityList, Parameter<Length> tol, FastTable<Entity> childEntities) {
530
531        //  Try to create an Entity for the surface that the curve is on.
532        Surface Ssrf = (Surface)subCrv.getChild();
533
534        //  First see if the Ssrf has been added to the transfer already.
535        Entity Se = elementEntityMap.get(Ssrf);
536        if (isNull(Se)) {
537            //  Entity has not already been stored, so create a new one.
538            Se = EntityFactory.create(part, DEnum, Ssrf);
539            if (nonNull(Se)) {
540                entityList.add(Se);
541                DEnum += 2;
542            }
543        }
544        if (isNull(Se)) {
545            _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
546                    Ssrf.getClass().getName(), Ssrf.getID()));
547            return DEnum;     //  Failed to create the transfer.
548        }
549        int Sptr = Se.getDENum();
550
551        //  Store a mapping of the Ssrf surface to the Se entity in case it is used again.
552        elementEntityMap.put(Ssrf, Se);
553
554        //  Now add the (u,v) parametric curve.
555        NurbsCurve Bcrv = subCrv.getParPosition().toNurbs(tol);
556        //  Must change to transfer units or scaling will be incorrect.
557        Bcrv = changeCurveUnits(Bcrv, part.getGlobalSection().getUnit());
558        int Bptr = DEnum;
559        Entity Be = EntityFactory.create(part, Bptr, Bcrv);
560        if (isNull(Be)) {
561            _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
562                    Bcrv.getClass().getName(), Bcrv.getID()));
563            return DEnum;   //  Failed to create transfer.
564        }
565        entityList.add(Be);
566        elementEntityMap.put(Bcrv, Be);
567        DEnum += 2;
568
569        //  Now add the real-space curve.
570        Curve Ccrv = subCrv.toNurbs(tol);   //  Convert to NURBS.
571        int Cptr = DEnum;
572        Entity Ce = EntityFactory.create(part, Cptr, Ccrv);
573        if (nonNull(Ce)) {
574            entityList.add(Ce);
575            DEnum += 2;
576
577            //  Finally, add the curve on surface entity.
578            Entity crvOnSrfe = new Entity142_CurveOnSurface(part, DEnum, subCrv.getName(), Sptr, Bptr, Cptr);
579            entityList.add(crvOnSrfe);
580            DEnum += 2;
581            if (nonNull(childEntities))
582                childEntities.add(crvOnSrfe);
583            elementEntityMap.put(subCrv, crvOnSrfe);
584
585        } else
586            _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
587                    Ccrv.getClass().getName(), Ccrv.getID()));
588
589        return DEnum;
590    }
591
592    /**
593     * Handles the special case of a subrange or trimmed surface.
594     *
595     * @param subSrf        The subrange surface being transferred.
596     * @param DEnum         The current DE pointer value.
597     * @param part          The part this curve is associated with.
598     * @param entityList    The list of all the entities being transferred.
599     * @param tol           The tolerance to use when converting curves to NURBS.
600     * @param childEntities The entities that are a part of the current geometry list
601     *                      being transferred.
602     * @return The DEnum incremented to account for the new entities added to the transfer
603     *         by this method.
604     */
605    private int handleSubrangeSurface(SubrangeSurface subSrf, int DEnum, Part part, List<Entity> entityList, Parameter<Length> tol, FastTable<Entity> childEntities) {
606        //  Get the surface being trimmed.
607        Surface Ssrf = (Surface)subSrf.getChild();
608
609        //  First see if the Ssrf has been added to the transfer already.
610        Entity Se = elementEntityMap.get(Ssrf);
611        if (isNull(Se)) {
612            //  Entity has not already been stored, so create a new one.
613            Se = EntityFactory.create(part, DEnum, Ssrf);
614            if (nonNull(Se)) {
615                entityList.add(Se);
616                DEnum += 2;
617            }
618        }
619        if (isNull(Se)) {
620            _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
621                    Ssrf.getClass().getName(), Ssrf.getID()));
622            return DEnum;     //  Failed to create the transfer.
623        }
624        int Sptr = Se.getDENum();
625
626        //  Get the parametric position surface (assumed to be a TFI surface -- what if it isn't?).
627        Surface parSrf = subSrf.getParPosition();
628
629        //  Get the 4 boundary parametric space curves oriented in a counter-clockwise direction.
630        NurbsCurve[] parBoundaryCrvs = new NurbsCurve[4];
631        parBoundaryCrvs[0] = parSrf.getS0Curve().toNurbs(tol);
632        parBoundaryCrvs[1] = parSrf.getT1Curve().toNurbs(tol);
633        parBoundaryCrvs[2] = parSrf.getS1Curve().reverse().toNurbs(tol);
634        parBoundaryCrvs[3] = parSrf.getT0Curve().reverse().toNurbs(tol);
635
636        //  Add each of the parametric boundary curves to the transfer.
637        Unit<Length> unit = part.getGlobalSection().getUnit();
638        FastTable<Integer> segDEPtrs = FastTable.newInstance();
639        for (int i = 0; i < 4; ++i) {
640            NurbsCurve crvi = parBoundaryCrvs[i];
641
642            //  Must change to transfer units or scaling will be incorrect.
643            crvi = changeCurveUnits(crvi, unit);
644
645            Entity segE = EntityFactory.create(part, DEnum, crvi);
646            if (isNull(segE)) {
647                _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
648                        crvi.getClass().getName(), crvi.getID()));
649                return DEnum;   //  Failed to create transfer.
650            }
651            entityList.add(segE);
652            segDEPtrs.add(DEnum);
653            DEnum += 2;
654        }
655
656        //  Add a composite curve made up of the parametric segments to the transfer.
657        Entity Be = new Entity102_CompositeCurve(part, DEnum, null, segDEPtrs);
658        entityList.add(Be);
659        int Bptr = DEnum;
660        DEnum += 2;
661        FastTable.recycle(segDEPtrs);
662
663        //  Create a real-space composite boundary curve to adhear to the contract for Entity 142.
664        NurbsCurve[] boundaryCrvs = new NurbsCurve[4];
665        boundaryCrvs[0] = subSrf.getS0Curve().toNurbs(tol);
666        boundaryCrvs[1] = subSrf.getT1Curve().toNurbs(tol);
667        boundaryCrvs[2] = subSrf.getS1Curve().reverse().toNurbs(tol);
668        boundaryCrvs[3] = subSrf.getT0Curve().reverse().toNurbs(tol);
669        NurbsCurve Ccrv = CurveUtils.connectCurves(boundaryCrvs);
670
671        //  Add the composite boundary curve to the transfer.
672        Entity Ce = EntityFactory.create(part, DEnum, Ccrv);
673        if (isNull(Ce)) {
674            _warnings.add(MessageFormat.format(RESOURCES.getString("igesSkipUnsupported"),
675                    Ccrv.getClass().getName(), Ccrv.getID()));
676            return DEnum;   //  Failed to create transfer.
677        }
678        entityList.add(Ce);
679        int Cptr = DEnum;
680        DEnum += 2;
681
682        //  Add a curve on surface entity to the transfer using the composite curve.
683        Entity Oe = new Entity142_CurveOnSurface(part, DEnum, null, Sptr, Bptr, Cptr);
684        entityList.add(Oe);
685        int PTO = DEnum;
686        DEnum += 2;
687
688        //  Finally, add the trimmed surface to the transfer.
689        Entity trimSrfe = new Entity144_TrimmedSurface(part, DEnum, subSrf, Sptr, PTO);
690        entityList.add(trimSrfe);
691        DEnum += 2;
692        if (nonNull(childEntities))
693            childEntities.add(trimSrfe);
694        elementEntityMap.put(subSrf, trimSrfe);
695
696        return DEnum;
697    }
698
699    /**
700     * Return a new curve with the same numerical values as the 1st curve, but with the
701     * units changed to those specified.
702     *
703     * @param crv  The curve to have the units changed.
704     * @param unit The new unit for the output curve.
705     * @return A curve with the same numerical values as the "crv", but with the units
706     *         changed to "unit".
707     */
708    private static NurbsCurve changeCurveUnits(NurbsCurve crv, Unit<Length> unit) {
709        StackContext.enter();
710        try {
711
712            List<ControlPoint> oldCPList = crv.getControlPoints();
713            FastTable<ControlPoint> newCPList = FastTable.newInstance();
714            for (ControlPoint cp : oldCPList) {
715                Point p = cp.getPoint();
716                double w = cp.getWeight();
717                Point pnew = Point.valueOf(p.getValue(0), p.getValue(1), 0, unit);
718                newCPList.add(ControlPoint.valueOf(pnew, w));
719            }
720            crv = BasicNurbsCurve.newInstance(newCPList, crv.getKnotVector());
721            return StackContext.outerCopy((BasicNurbsCurve)crv);
722
723        } finally {
724            StackContext.exit();
725        }
726    }
727
728    /**
729     * This method always returns <code>true</code> as IGES files do encode the units that
730     * are being used though the list of available units is limited.
731     *
732     * @return this implementation always returns true
733     * @see #setFileUnits(javax.measure.unit.Unit) 
734     */
735    @Override
736    public boolean isUnitAware() {
737        return true;
738    }
739}