2
0
Эх сурвалжийг харах

- IFC: slight optimization, take less memory.
# IFC: try to make normals point outwards, if possible.
# IFC: implement recursive polygon merging, but leave it disabled since it seems to fail on very complicated polygons with multiple, nested boundaries.

git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@1007 67173fc5-114c-0410-ac8e-9d2fd5bffc1f

aramis_acg 14 жил өмнө
parent
commit
78b44c3aed

+ 168 - 122
code/IFCLoader.cpp

@@ -333,8 +333,13 @@ void IFCImporter::InternReadFile( const std::string& pFile,
 	EXPRESS::ConversionSchema schema;
 	IFC::GetSchema(schema);
 
+	// tell the reader which entity types to track with special care
+	static const char* const types_to_track[] = {
+		"ifcsite", "ifcbuilding", "ifcproject"
+	};
+
 	// feed the IFC schema into the reader and pre-parse all lines
-	STEP::ReadFile(*db, schema);
+	STEP::ReadFile(*db, schema, types_to_track);
 
 	const STEP::LazyObject* proj =  db->GetObject("ifcproject");
 	if (!proj) {
@@ -731,6 +736,116 @@ bool ProcessPolyloop(const IFC::IfcPolyLoop& loop, TempMesh& meshout, Conversion
 	return false;
 }
 
+// ------------------------------------------------------------------------------------------------
+void FixupFaceOrientation(TempMesh& result)
+{
+	aiVector3D vavg;
+	BOOST_FOREACH(aiVector3D& v, result.verts) {
+		vavg += v;
+	}
+
+	// fix face orientation - try at least.
+	vavg /= static_cast<float>( result.verts.size() );
+
+	size_t c = 0;
+	BOOST_FOREACH(unsigned int cnt, result.vertcnt) {
+		if (cnt>2){
+			const aiVector3D& thisvert = result.verts[c];
+			const aiVector3D normal((thisvert-result.verts[c+1])^(thisvert-result.verts[c+2]));
+			if (normal*(thisvert-vavg) < 0) {
+				std::reverse(result.verts.begin()+c,result.verts.begin()+cnt+c);
+			}
+		}
+		c += cnt;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+void RecursiveMergeBoundaries(TempMesh& final_result, const TempMesh& in, const TempMesh& boundary, std::vector<aiVector3D>& normals, const aiVector3D& nor_boundary)
+{
+	ai_assert(in.vertcnt.size() >= 1);
+	ai_assert(boundary.vertcnt.size() == 1);
+	std::vector<unsigned int>::const_iterator end = in.vertcnt.end(), begin=in.vertcnt.begin(), iit, best_iit;
+
+	TempMesh out;
+
+	// iterate through all other bounds and find the one for which the shortest connection
+	// to the outer boundary is actually the shortest possible.
+	size_t vidx = 0, best_vidx_start = 0;
+	size_t best_ofs, best_outer = boundary.verts.size();
+	float best_dist = 1e10;
+	for(std::vector<unsigned int>::const_iterator iit = begin; iit != end; vidx += *iit++) {
+		
+		for(size_t vofs = 0; vofs < *iit; ++vofs) {
+			const aiVector3D& v = in.verts[vidx+vofs];
+
+			for(size_t outer = 0; outer < boundary.verts.size(); ++outer) {
+				const aiVector3D& o = boundary.verts[outer];
+				const float d = (o-v).SquareLength();
+
+				if (d < best_dist) {
+					best_dist = d;
+					best_ofs = vofs;
+					best_outer = outer;
+					best_iit = iit;
+					best_vidx_start = vidx;
+				}
+			}		
+		}
+	}
+
+	ai_assert(best_outer != boundary.verts.size());
+
+	// now that we collected all vertex connections to be added, build the output polygon
+	size_t cnt = boundary.verts.size();
+	for(size_t outer = 0; outer < boundary.verts.size(); ++outer) {
+		const aiVector3D& o = boundary.verts[outer];
+		out.verts.push_back(o);
+
+		if (outer == best_outer) {
+			for(size_t i = best_ofs; i < *best_iit; ++i) {
+				out.verts.push_back(in.verts[best_vidx_start + i]);
+			}
+
+			// we need the first vertex of the inner polygon twice as we return to the
+			// outer loop through the very same connection through which we got there.
+			for(size_t i = 0; i <= best_ofs; ++i) {
+				out.verts.push_back(in.verts[best_vidx_start + i]);
+			}
+
+			// reverse face winding if the normal of the sub-polygon points in the
+			// same direction as the normal of the outer polygonal boundary
+			if (normals[std::distance(begin,best_iit)] * nor_boundary > 0) {
+				std::reverse(out.verts.rbegin(),out.verts.rbegin()+*best_iit+1);
+			}
+
+			// also append a copy of the initial insertion point to be able to continue the outer polygon
+			out.verts.push_back(o);
+			cnt += *best_iit+2;
+		}
+	}
+	out.vertcnt.push_back(cnt);
+
+	if (in.vertcnt.size() > 1) {
+		// Recursively apply the same algorithm if there are more boundaries to merge. The
+		// current implementation is relatively inefficient, though.
+		
+		TempMesh temp;
+		
+		// drop the boundary that we just processed
+		const size_t dist = std::distance(begin, best_iit);
+		TempMesh remaining = in;
+		remaining.vertcnt.erase(remaining.vertcnt.begin() + dist);
+		remaining.verts.erase(remaining.verts.begin()+best_ofs,remaining.verts.begin()+best_ofs+*best_iit);
+
+		normals.erase(normals.begin() + dist);
+		RecursiveMergeBoundaries(temp,remaining,out,normals,nor_boundary);
+
+		final_result.Append(temp);
+	}
+	else final_result.Append(out);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Note: meshout may be modified even though the merged polygon is copied to `result`!
 void MergePolygonBoundaries(TempMesh& result, TempMesh& meshout, size_t master_bounds = -1) 
@@ -754,7 +869,6 @@ void MergePolygonBoundaries(TempMesh& result, TempMesh& meshout, size_t master_b
 	result.verts.reserve(meshout.verts.size()+meshout.vertcnt.size()*2+result.verts.size());
 	size_t outer_polygon_start = 0;
 
-
 	// compute proper normals for all polygons
 	size_t max_vcount = 0;
 	std::vector<unsigned int>::iterator outer_polygon = meshout.vertcnt.end(), begin=meshout.vertcnt.begin(), end=outer_polygon,  iit;
@@ -767,6 +881,8 @@ void MergePolygonBoundaries(TempMesh& result, TempMesh& meshout, size_t master_b
 	std::vector<aiVector3D> normals;
 	normals.reserve( meshout.vertcnt.size() );
 
+	// `NewellNormal()` currently has a relatively strange interface and need to 
+	// re-structure things a bit to meet them.
 	size_t vidx = 0;
 	for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) {
 		for(size_t vofs = 0, cnt = 0; vofs < *iit; ++vofs) {
@@ -784,7 +900,7 @@ void MergePolygonBoundaries(TempMesh& result, TempMesh& meshout, size_t master_b
 		NewellNormal<4,4,4>(normals.back(),*iit,&temp[0],&temp[1],&temp[2]);
 	}
 
-	// see if one of the polygons is a IfcFaceOuterBound - treats this as the outer boundary.
+	// see if one of the polygons is a IfcFaceOuterBound (in which case master_bounds is not `-1`).
 	// sadly we can't rely on it, the docs say 'At most one of the bounds shall be of the type IfcFaceOuterBound' 
 	if (master_bounds != -1) {
 		outer_polygon = begin + master_bounds;
@@ -844,10 +960,7 @@ next_loop:
 					const size_t start = (v-o).SquareLength() > (vnext-o).SquareLength() ? vofs :  next;
 					std::vector<aiVector3D>::iterator inbase = in.begin()+vidx, it = std::copy(inbase+start, inbase+*iit,tmp.begin());
 					std::copy(inbase, inbase+start,it);
-
-					//if(start == next) {
-						std::reverse(tmp.begin(),tmp.end());
-					//}
+					std::reverse(tmp.begin(),tmp.end());
 
 					in.insert(in.begin()+outer_polygon_start+(outer+1)%*outer_polygon,tmp.begin(),tmp.end());
 					vidx += outer_polygon_start<vidx ? *iit : 0;
@@ -865,86 +978,36 @@ next_loop:
 		}
 	}
 
-	typedef boost::tuple<std::vector<unsigned int>::iterator, unsigned int, unsigned int> InsertionPoint;
-	std::vector< std::vector<InsertionPoint> > insertions(*outer_polygon, std::vector<InsertionPoint>());
-
-	// iterate through all other polyloops and find points in the outer polyloop that are close
-	vidx = 0;
-	for(iit = begin; iit != end; vidx += *iit++) {
-		if (iit == outer_polygon || !*iit) {
-			continue;
-		}
-
-		size_t best_ofs,best_outer = *outer_polygon;
-		float best_dist = 1e10;
-		for(size_t vofs = 0; vofs < *iit; ++vofs) {
-			const aiVector3D& v = in[vidx+vofs];
-
-			for(size_t outer = 0; outer < *outer_polygon; ++outer) {
-				const aiVector3D& o = in[outer_polygon_start+outer];
-				const float d = (o-v).SquareLength();
-
-				if (d < best_dist) {
-					best_dist = d;
-					best_ofs = vofs;
-					best_outer = outer;
-				}
-			}		
-		}
-
-		ai_assert(best_outer != *outer_polygon);
-
-		// we will later insert a hidden connection line right after the closest point in the outer polygon
-		insertions[best_outer].push_back(boost::make_tuple(iit,vidx,best_ofs));
-	}
-
-	// now that we collected all vertex connections to be added, build the output polygon
-	size_t cnt = *outer_polygon;
-	for(size_t outer = 0; outer < *outer_polygon; ++outer) {
-		const aiVector3D& o = meshout.verts[outer_polygon_start+outer];
-		result.verts.push_back(o);
-
-		const std::vector<InsertionPoint>& insvec = insertions[outer];
-		BOOST_FOREACH(const InsertionPoint& ins,insvec) {
-			if (!*ins.get<0>()) {
-				continue;
-			}
+	// extract the outer boundary and move it to a separate mesh
+	TempMesh boundary;
+	boundary.vertcnt.resize(1,*outer_polygon);
+	boundary.verts.resize(*outer_polygon);
 
-			for(size_t i = ins.get<2>(); i < *ins.get<0>(); ++i) {
-				result.verts.push_back(in[ins.get<1>() + i]);
-			}
+	std::vector<aiVector3D>::iterator b = in.begin()+outer_polygon_start;
+	std::copy(b,b+*outer_polygon,boundary.verts.begin());
+	in.erase(b,b+*outer_polygon);
 
-			// we need the first vertex of the inner polygon twice as we return to the
-			// outer loop through the very same connection through which we got there.
-			for(size_t i = 0; i <= ins.get<2>(); ++i) {
-				result.verts.push_back(in[ins.get<1>() + i]);
-			}
-
-			// reverse face winding if the normal of the sub-polygon points in the
-			// same direction as the normal of the outer polygonal boundary
-			if (normals[std::distance(begin,ins.get<0>())] * normals[std::distance(begin,outer_polygon)] > 0) {
-				std::reverse(result.verts.rbegin(),result.verts.rbegin()+*ins.get<0>()+1);
-			}
+	std::vector<aiVector3D>::iterator norit = normals.begin()+std::distance(meshout.vertcnt.begin(),outer_polygon);
+	const aiVector3D nor_boundary = *norit;
+	normals.erase(norit);
+	meshout.vertcnt.erase(outer_polygon);
 
-			// also append a copy of the initial insertion point to be able to continue the outer polygon
-			result.verts.push_back(o);
-			cnt += *ins.get<0>()+2;
-		}
-	}
-	result.vertcnt.push_back(cnt);
+	// keep merging the closest inner boundary with the outer boundary until no more boundaries are left
+	RecursiveMergeBoundaries(result,meshout,boundary,normals,nor_boundary);
 }
 
+
 // ------------------------------------------------------------------------------------------------
 void ProcessConnectedFaceSet(const IFC::IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv)
 {
 	BOOST_FOREACH(const IFC::IfcFace& face, fset.CfsFaces) {
-		TempMesh meshout;
-
 		size_t ob = -1, cnt = 0;
+		//TempMesh meshout;
 		BOOST_FOREACH(const IFC::IfcFaceBound& bound, face.Bounds) {
 			
+			// XXX implement proper merging for polygonal loops
 			if(const IFC::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<IFC::IfcPolyLoop>()) {
-				if(ProcessPolyloop(*polyloop, meshout, conv)) {
+				if(ProcessPolyloop(*polyloop, result,conv)) {
 					if(bound.ToPtr<IFC::IfcFaceOuterBound>()) {
 						ob = cnt;
 					}
@@ -956,18 +1019,19 @@ void ProcessConnectedFaceSet(const IFC::IfcConnectedFaceSet& fset, TempMesh& res
 				continue;
 			}
 
-			if(!IsTrue(bound.Orientation)) {
+			/*if(!IsTrue(bound.Orientation)) {
 				size_t c = 0;
 				BOOST_FOREACH(unsigned int& i, meshout.vertcnt) {
 					std::reverse(meshout.verts.begin() + cnt,meshout.verts.begin() + cnt + c);
 					cnt += c;
 				}
-			}
-			
+			}*/
+
 		}
 
-		MergePolygonBoundaries(result,meshout,ob);
+		//MergePolygonBoundaries(result,meshout,ob);
 	}
+	FixupFaceOrientation(result);
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1068,30 +1132,6 @@ bool ProcessProfile(const IFC::IfcProfileDef& prof, TempMesh& meshout, Conversio
 	return true;
 }
 
-// ------------------------------------------------------------------------------------------------
-void FixupFaceOrientation(TempMesh& result)
-{
-	aiVector3D vavg;
-	BOOST_FOREACH(aiVector3D& v, result.verts) {
-		vavg += v;
-	}
-
-	// fix face orientation - try at least.
-	vavg /= static_cast<float>( result.verts.size() );
-
-	size_t c = 0;
-	BOOST_FOREACH(unsigned int cnt, result.vertcnt) {
-		if (cnt>2){
-			const aiVector3D& thisvert = result.verts[c];
-			const aiVector3D normal((thisvert-result.verts[c+1])^(thisvert-result.verts[c+2]));
-			if (normal*(thisvert-vavg) < 0) {
-				std::reverse(result.verts.begin()+c,result.verts.begin()+cnt+c);
-			}
-		}
-		c += cnt;
-	}
-}
-
 // ------------------------------------------------------------------------------------------------
 void ProcessRevolvedAreaSolid(const IFC::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
 {
@@ -1271,7 +1311,7 @@ aiVector2D ProjectPositionVectorOntoPlane(const aiVector3D& x, const ProjectionI
 }
 
 // ------------------------------------------------------------------------------------------------
-void TriangulatePart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedField& field, const std::vector< BoundingBox >& bbs, 
+void QuadrifyPart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedField& field, const std::vector< BoundingBox >& bbs, 
 	std::vector<aiVector2D>& out)
 {
 	if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) {
@@ -1323,7 +1363,7 @@ void TriangulatePart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedFie
 			const float ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y);
 			if (ys - ylast) {
 				// Divide et impera!
-				TriangulatePart( aiVector2D(xs,ylast), aiVector2D(xe,ys) ,field,bbs,out);
+				QuadrifyPart( aiVector2D(xs,ylast), aiVector2D(xe,ys) ,field,bbs,out);
 			}
 
 			// the following are the window vertices
@@ -1349,12 +1389,12 @@ void TriangulatePart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedFie
 	}
 	if (ylast < pmax.y) {
 		// Divide et impera!
-		TriangulatePart( aiVector2D(xs,ylast), aiVector2D(xe,pmax.y) ,field,bbs,out);
+		QuadrifyPart( aiVector2D(xs,ylast), aiVector2D(xe,pmax.y) ,field,bbs,out);
 	}
 
 	// Divide et impera! - now for the whole rest
 	if (pmax.x-xe) {
-		TriangulatePart(aiVector2D(xe,pmin.y), pmax ,field,bbs,out);
+		QuadrifyPart(aiVector2D(xe,pmin.y), pmax ,field,bbs,out);
 	}
 }
 
@@ -1581,7 +1621,7 @@ bool TryAddOpenings_Triangulate(const std::vector<TempOpening>& openings,const s
 
 	std::vector<aiVector2D> outflat;
 	outflat.reserve(openings.size()*4);
-	TriangulatePart(aiVector2D(0.f,0.f),aiVector2D(1.f,1.f),field,bbs,outflat);
+	QuadrifyPart(aiVector2D(0.f,0.f),aiVector2D(1.f,1.f),field,bbs,outflat);
 	ai_assert(!(outflat.size() % 4));
 
 	//FixOuterBoundaries(outflat,contour_flat);
@@ -1652,12 +1692,14 @@ void ProcessExtrudedAreaSolid(const IFC::IfcExtrudedAreaSolid& solid, TempMesh&
 	
 	// compute the normal vectors for all opening polygons
 	if (conv.apply_openings) {
-		// it is essential to apply the openings in the correct spatial order. The direction
-		// doesn't matter, but we would screw up if we started with e.g. a door in between
-		// two windows.
-		std::sort(conv.apply_openings->begin(),conv.apply_openings->end(),DistanceSorter(min));
-		nors.reserve(conv.apply_openings->size());
+		if (!conv.settings.useCustomTriangulation) {
+			// it is essential to apply the openings in the correct spatial order. The direction
+			// doesn't matter, but we would screw up if we started with e.g. a door in between
+			// two windows.
+			std::sort(conv.apply_openings->begin(),conv.apply_openings->end(),DistanceSorter(min));
+		}
 
+		nors.reserve(conv.apply_openings->size());
 		BOOST_FOREACH(TempOpening& t,*conv.apply_openings) {
 			TempMesh& bounds = *t.profileMesh.get();
 		
@@ -2387,8 +2429,9 @@ aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, Conve
 		if(!conv.collect_openings) {
 			conv.apply_openings = &openings;
 		}
-		ProcessProductRepresentation(el,nd.get(),subnodes,conv);
 
+
+		ProcessProductRepresentation(el,nd.get(),subnodes,conv);
 		conv.apply_openings = conv.collect_openings = NULL;
 
 		if (subnodes.size()) {
@@ -2414,19 +2457,22 @@ void ProcessSpatialStructures(ConversionData& conv)
 	// process all products in the file. it is reasonable to assume that a
 	// file that is relevant for us contains at least a site or a building.
 	const STEP::DB::ObjectMapByType& map = conv.db.GetObjectsByType();
-	STEP::DB::ObjectMapRange range = map.equal_range("ifcsite");
 
-	if (range.first == map.end()) {
-		range = map.equal_range("ifcbuilding");
-		if (range.first == map.end()) {
-			// no site, no building - try all ids. this will take ages, but it should rarely happen.
-			range = STEP::DB::ObjectMapRange(map.begin(),map.end());
+	ai_assert(map.find("ifcsite") != map.end());
+	const STEP::DB::ObjectSet* range = &map.find("ifcsite")->second;
+
+	if (range->empty()) {
+		ai_assert(map.find("ifcbuilding") != map.end());
+		range = &map.find("ifcbuilding")->second;
+		if (range->empty()) {
+			// no site, no building -  fail;
+			IFCImporter::ThrowException("no root element found (expected IfcBuilding or preferably IfcSite)");
 		}
 	}
 
 	
-	for(;range.first != range.second; ++range.first) {
-		const IFC::IfcSpatialStructureElement* const prod = (*range.first).second->ToPtr<IFC::IfcSpatialStructureElement>();
+	BOOST_FOREACH(const STEP::LazyObject* lz, *range) {
+		const IFC::IfcSpatialStructureElement* const prod = lz->ToPtr<IFC::IfcSpatialStructureElement>();
 		if(!prod) {
 			continue;
 		}
@@ -2454,7 +2500,7 @@ void ProcessSpatialStructures(ConversionData& conv)
 	}
 
 
-	IFCImporter::ThrowException("Failed to determine primary site element");
+	IFCImporter::ThrowException("failed to determine primary site element");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 19 - 8
code/STEPFile.h

@@ -804,7 +804,7 @@ namespace STEP {
 	class DB
 	{
 		friend DB* ReadFileHeader(boost::shared_ptr<IOStream> stream);
-		friend void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme);
+		friend void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme,const char* const* arr, size_t len);
 		friend class LazyObject;
 
 	public:
@@ -812,12 +812,12 @@ namespace STEP {
 		// objects indexed by ID
 		typedef std::map<uint64_t,boost::shared_ptr<const LazyObject> > ObjectMap;
 
-		// objects indexed by their declarative type
-		typedef std::step_unordered_multimap<std::string, const LazyObject* > ObjectMapByType;
-		typedef std::pair<ObjectMapByType::const_iterator,ObjectMapByType::const_iterator> ObjectMapRange;
+		// objects indexed by their declarative type, but only for those that we truly want
+		typedef std::set< const LazyObject*> ObjectSet;
+		typedef std::map<std::string, ObjectSet > ObjectMapByType;
 
 		// references - for each object id the ids of all objects which reference it
-		typedef std::multimap<uint64_t, uint64_t > RefMap;
+		typedef std::step_unordered_multimap<uint64_t, uint64_t > RefMap;
 		typedef std::pair<RefMap::const_iterator,RefMap::const_iterator> RefMapRange;
 
 	private:
@@ -872,8 +872,8 @@ namespace STEP {
 		// get an arbitrary object out of the soup with the only restriction being its type.
 		const LazyObject* GetObject(const std::string& type) const {
 			const ObjectMapByType::const_iterator it = objects_bytype.find(type);
-			if (it != objects_bytype.end()) {
-				return (*it).second;
+			if (it != objects_bytype.end() && (*it).second.size()) {
+				return *(*it).second.begin();
 			}
 			return NULL;
 		}
@@ -919,13 +919,24 @@ namespace STEP {
 
 		void InternInsert(boost::shared_ptr<const LazyObject> lz) {
 			objects[lz->id] = lz;
-			objects_bytype.insert(std::make_pair(lz->type,lz.get()));
+
+			const ObjectMapByType::iterator it = objects_bytype.find( lz->type );
+			if (it != objects_bytype.end()) {
+				(*it).second.insert(lz.get());
+			}
 		}
 
 		void SetSchema(const EXPRESS::ConversionSchema& _schema) {
 			schema = &_schema;
 		}
 
+		
+		void SetTypesToTrack(const char* const* types, size_t N) {
+			for(size_t i = 0; i < N;++i) {
+				objects_bytype[types[i]] = ObjectSet();
+			}
+		}
+
 		HeaderInfo& GetHeader() {
 			return header;
 		}

+ 2 - 1
code/STEPFileReader.cpp

@@ -161,9 +161,10 @@ STEP::DB* STEP::ReadFileHeader(boost::shared_ptr<IOStream> stream)
 
 
 // ------------------------------------------------------------------------------------------------
-void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme)
+void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme,const char* const* arr, size_t len)
 {
 	db.SetSchema(scheme);
+	db.SetTypesToTrack(arr,len);
 
 	const DB::ObjectMap& map = db.GetObjects();
 	LineSplitter& splitter = db.GetSplitter();

+ 5 - 1
code/STEPFileReader.h

@@ -56,7 +56,11 @@ namespace STEP {
 	// --------------------------------------------------------------------------
 	// 2) read the actual file contents using a user-supplied set of
 	//    conversion functions to interpret the data.
-	void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme);
+	void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const* arr, size_t len);
+	template <size_t N> inline void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const (&arr)[N]) {
+		return ReadFile(db,scheme,arr,N);
+	}
+	
 
 } // ! STEP
 } // ! Assimp