Branch data Line data Source code
1 : : // CAPartitionVG class
2 : :
3 : : #include <vector>
4 : : #include <utility>
5 : : #include <algorithm>
6 : :
7 : : #include "CAPartitionVG.hpp"
8 : : #include "TDUniqueId.hpp"
9 : : #include "CastTo.hpp"
10 : : #include "RefFace.hpp"
11 : : #include "RefEdge.hpp"
12 : : #include "RefVertex.hpp"
13 : : #include "PartitionEntity.hpp"
14 : : #include "PartitionTool.hpp"
15 : : #include "BasicTopologyEntity.hpp"
16 : : #include "PartitionCurve.hpp"
17 : : #include "PartitionSurface.hpp"
18 : : #include "CAVirtualVG.hpp"
19 : : #include "MergeTool.hpp"
20 : :
21 : : struct my_sort : public std::binary_function< std::pair<double, int>, std::pair<double, int>, bool> {
22 : 0 : bool operator() (std::pair<double, int> const& x, std::pair<double, int> const & y ) { return x.first < y.first; }
23 : : };
24 : :
25 : 15852 : CubitAttrib* CAPartitionVG_creator(RefEntity* entity, const CubitSimpleAttrib &p_csa)
26 : : {
27 [ + - ]: 15852 : return new CAPartitionVG(entity, p_csa);
28 : : }
29 : :
30 : 15852 : CAPartitionVG::CAPartitionVG(RefEntity *owner, const CubitSimpleAttrib &simple_attrib)
31 [ + - ][ + - ]: 15852 : : CubitAttrib(owner)
32 : : {
33 : 15852 : numPC = 0;
34 : 15852 : numPS = 0;
35 : :
36 [ + - ][ - + ]: 15852 : if(!simple_attrib.isEmpty())
37 : : {
38 : : // generate a simple attribute containing the data in this CA
39 [ # # ]: 0 : const std::vector<int> &i_list = simple_attrib.int_data_list();
40 : :
41 : 0 : int ioffset = 0;
42 : :
43 : : // now the integers
44 : : // numVP, numVC
45 [ # # ]: 0 : numPC = i_list[ioffset++];
46 [ # # ]: 0 : numPS = i_list[ioffset++];
47 : :
48 : : // numBdyCurves
49 : 0 : int temp, i, sum = 0;
50 [ # # ]: 0 : for (i = numPS; i > 0; i--) {
51 [ # # ]: 0 : temp = i_list[ioffset++];
52 [ # # ]: 0 : numBdyCurves.append(temp);
53 : 0 : sum += temp;
54 : : }
55 : :
56 : : // vgUIDs: 3 for each PC, numPS+sum for PS
57 [ # # ]: 0 : for (i = 3*numPC+sum+numPS; i > 0; i--)
58 [ # # ][ # # ]: 0 : vgUIDs.append(i_list[ioffset++]);
59 : :
60 : :
61 : : // If the CubitSimpleAttrib already exists,
62 : : // then this attribute is already written
63 [ # # ]: 0 : has_written(CUBIT_TRUE);
64 : : }
65 : 15852 : }
66 : :
67 : 15852 : CubitStatus CAPartitionVG::update()
68 : : {
69 : : /*
70 : : // this attribute behaves in a peculiar way: it detects whether the owner
71 : : // is itself a partition entity, and if so, adds data about this partition
72 : : // entity to the underlying entity
73 : :
74 : : if (hasUpdated) return CUBIT_SUCCESS;
75 : :
76 : : assert(attrib_owner() != 0);
77 : :
78 : : TopologyEntity *topo_entity = CAST_TO(attrib_owner(), TopologyEntity);
79 : : assert(topo_entity != 0);
80 : : DLIList<TopologyBridge*> bridge_list;
81 : : topo_entity->bridge_manager()->get_bridge_list( bridge_list );
82 : : for( int i = bridge_list.size(); i--; )
83 : : {
84 : : TopologyBridge *topo_bridge = bridge_list.get_and_step();
85 : : PartitionEntity *partition_entity = CAST_TO(topo_bridge, PartitionEntity);
86 : :
87 : : if (partition_entity == NULL) {
88 : : // this entity isn't a partition entity - if this entity doesn't have any virtual
89 : : // entities registered, set delete flag, then exit
90 : : if (numPC == 0 && numPS == 0)
91 : : delete_attrib(CUBIT_TRUE);
92 : : else {
93 : : PRINT_INFO("Keeping CA_PARTITION_VG for %s %d\n",
94 : : attrib_owner()->class_name(), attrib_owner()->id());
95 : : hasUpdated = CUBIT_TRUE;
96 : : }
97 : :
98 : : continue;
99 : : }
100 : :
101 : : // ok, we have a partition entity; first get the underlying entity, and a CAPVG
102 : : // for that entity
103 : : BasicTopologyEntity* bte_ptr = partition_entity->get_underlying_BTE_ptr();
104 : : if (!bte_ptr) {
105 : : PRINT_ERROR("Couldn't find bound_to\n");
106 : : return CUBIT_FAILURE;
107 : : }
108 : :
109 : : CAPartitionVG *other_CAPVG = (CAPartitionVG *) bte_ptr->get_cubit_attrib(CA_PARTITION_VG);
110 : :
111 : : // if that other CAPVG's written flag is set, it's an old one from a
112 : : // previous write and needs to be reset
113 : : if (other_CAPVG->has_written() == CUBIT_TRUE) {
114 : : other_CAPVG->reset();
115 : : other_CAPVG->has_written(CUBIT_FALSE);
116 : : }
117 : :
118 : : // now put virtual geometry-specific data on the attribute
119 : : PartitionCurve *partition_curve = CAST_TO(partition_entity, PartitionCurve);
120 : : PartitionSurface *partition_surface = CAST_TO(partition_entity, PartitionSurface);
121 : :
122 : : if (partition_curve != NULL) {
123 : : other_CAPVG->add_pcurve(partition_curve);
124 : : other_CAPVG->delete_attrib(CUBIT_FALSE);
125 : : }
126 : :
127 : : else if (partition_surface != NULL) {
128 : : other_CAPVG->add_psurface(partition_surface);
129 : : other_CAPVG->delete_attrib(CUBIT_FALSE);
130 : : }
131 : :
132 : : else {
133 : : PRINT_ERROR("Shouldn't get here in CAPartitionVG::update.\n");
134 : : return CUBIT_FAILURE;
135 : : }
136 : : }
137 : :
138 : : hasUpdated = CUBIT_TRUE;
139 : : if (numPC == 0 && numPS == 0) delete_attrib(CUBIT_TRUE);
140 : :
141 : : return CUBIT_SUCCESS;
142 : : */
143 : 15852 : delete_attrib(CUBIT_TRUE);
144 : 15852 : return CUBIT_SUCCESS;
145 : : }
146 : :
147 : :
148 : 0 : CubitStatus CAPartitionVG::reset()
149 : : {
150 : 0 : numPC = 0;
151 : 0 : numPS = 0;
152 : :
153 : 0 : vgUIDs.clean_out();
154 : 0 : numBdyCurves.clean_out();
155 : :
156 : 0 : return CUBIT_SUCCESS;
157 : : }
158 : :
159 : 15852 : CubitSimpleAttrib CAPartitionVG::cubit_simple_attrib()
160 : : {
161 : : // generate a simple attribute containing the data in this CA
162 [ + - ]: 15852 : std::vector<CubitString> cs_list;
163 [ + - ][ + - ]: 31704 : std::vector<double> d_list;
164 [ + - ][ + - ]: 31704 : std::vector<int> i_list;
165 : :
166 : : // first the string
167 [ + - ][ + - ]: 15852 : cs_list.push_back(att_internal_name());
[ + - ][ + - ]
168 : :
169 : : // now the integers
170 : : // numVP, numVC
171 [ + - ]: 15852 : i_list.push_back(numPC);
172 [ + - ]: 15852 : i_list.push_back(numPS);
173 : :
174 : : // numBdyCurves
175 : : int i;
176 [ + - ][ - + ]: 15852 : for (i = numBdyCurves.size(); i > 0; i--)
177 [ # # ][ # # ]: 0 : i_list.push_back(numBdyCurves.get_and_step());
178 : :
179 : : // vgUIDs
180 [ + - ]: 15852 : vgUIDs.reset();
181 [ + - ][ - + ]: 15852 : for (i = vgUIDs.size(); i > 0; i--)
182 [ # # ][ # # ]: 0 : i_list.push_back(vgUIDs.get_and_step());
183 : :
184 [ + - ][ + - ]: 31704 : return CubitSimpleAttrib(&cs_list, &d_list, &i_list);
185 : : }
186 : :
187 : 0 : CubitStatus CAPartitionVG::actuate()
188 : : {
189 : : // actuate this CA
190 : :
191 : : // actuate partition VG attributes on next-lower order entities
192 [ # # ]: 0 : RefEntity *owner = CAST_TO(attrib_owner(), RefEntity);
193 [ # # ][ # # ]: 0 : if (owner->dimension() > 1) {
194 [ # # ]: 0 : DLIList<RefEntity*> lower_entities;
195 [ # # ]: 0 : owner->get_child_ref_entities(lower_entities);
196 : :
197 : : // don't check return values here - there may be other CA's hanging
198 : : // around unactuated, but we may still be able to actuate later
199 [ # # ][ # # ]: 0 : CubitAttribUser::actuate_cubit_attrib(lower_entities, CA_PARTITION_VG);
[ # # ]
200 [ # # ][ # # ]: 0 : CubitAttribUser::actuate_cubit_attrib(lower_entities, CA_VIRTUAL_VG);
[ # # ][ # # ]
201 : : }
202 : :
203 : : //actuate it
204 : :
205 : : // if this is an edge, now partition it
206 [ # # ]: 0 : RefEdge *owner_edge = CAST_TO(owner, RefEdge);
207 [ # # ]: 0 : RefFace *owner_face = CAST_TO(owner, RefFace);
208 : :
209 : : //have to get some of the data from CAVirtualVG in order to partition
210 : : //get CA_VIRTUAL_VG attrib associated with this entity
211 [ # # ]: 0 : DLIList<CubitAttrib*> vg_attribs;
212 [ # # ][ # # ]: 0 : attrib_owner()->find_cubit_attrib_type( CA_VIRTUAL_VG, vg_attribs );
213 : 0 : CAVirtualVG *ca_vg_ptr = NULL;
214 [ # # ][ # # ]: 0 : if( vg_attribs.size() != 0 )
215 [ # # ][ # # ]: 0 : ca_vg_ptr = CAST_TO( vg_attribs.get(), CAVirtualVG );
216 : :
217 [ # # ][ # # ]: 0 : DLIList<RefFace*> new_faces;
218 [ # # ][ # # ]: 0 : DLIList<RefEdge*> new_edges;
219 : :
220 : : int i,j,k;
221 [ # # ]: 0 : if (owner_edge) //if an edge has been partitioned
222 : : {
223 [ # # ]: 0 : DLIList<RefVertex*> new_vertices;
224 [ # # ][ # # ]: 0 : DLIList<RefEdge*> new_edges;
225 [ # # ][ # # ]: 0 : DLIList<CubitVector*> split_points;
226 : :
227 [ # # ]: 0 : if( !ca_vg_ptr ) //if NO virtual geometry has been used to partiton this curve
228 : : {
229 : : //get vertices of curve
230 [ # # ]: 0 : DLIList<RefEntity*> lower_entities;
231 [ # # ]: 0 : owner->get_child_ref_entities(lower_entities);
232 : :
233 : : //get the points that are not owned by a vertex
234 [ # # ]: 0 : vgUIDs.reset();
235 [ # # ][ # # ]: 0 : DLIList<RefVertex*> vertices;
236 [ # # ][ # # ]: 0 : for(i=vgUIDs.size()/3; i--;)
237 : : {
238 [ # # ][ # # ]: 0 : ToolDataUser *tdu = TDUniqueId::find_td_unique_id(vgUIDs.get_and_step());
239 [ # # ]: 0 : RefVertex *s_vert = CAST_TO( tdu, RefVertex );
240 [ # # ][ # # ]: 0 : tdu = TDUniqueId::find_td_unique_id(vgUIDs.get_and_step());
241 [ # # ]: 0 : RefVertex *e_vert = CAST_TO( tdu, RefVertex );
242 : :
243 : : //get vertices owned by this curve
244 [ # # ][ # # ]: 0 : if( !lower_entities.move_to( s_vert ) )
[ # # ]
245 [ # # ]: 0 : vertices.append_unique( s_vert );
246 [ # # ][ # # ]: 0 : if( !lower_entities.move_to( e_vert ) )
[ # # ]
247 [ # # ]: 0 : vertices.append_unique( e_vert );
248 : :
249 [ # # ]: 0 : vgUIDs.step();
250 : : }
251 : : //convert vertices to vectors to split curve
252 [ # # ][ # # ]: 0 : for(i=vertices.size(); i--;)
253 : : {
254 [ # # ]: 0 : RefVertex *cur_vertex = vertices.get_and_step();
255 [ # # ][ # # ]: 0 : split_points.append( new CubitVector( cur_vertex->coordinates() ) );
[ # # ]
256 : : }
257 : :
258 : : //partition the curve with these split points
259 [ # # ]: 0 : split_points.reset();
260 : : PartitionTool::instance()->partition( owner_edge, split_points,
261 [ # # ][ # # ]: 0 : new_vertices, new_edges );
262 : : //may need to merge some vertices
263 [ # # ]: 0 : vertices += new_vertices;
264 [ # # ][ # # ]: 0 : MergeTool::instance()->merge_refvertices( vertices );
[ # # ]
265 : :
266 : : }
267 : : else //virtual geometry HAS been used to partiton this curve
268 : : {
269 [ # # ]: 0 : split_points = ca_vg_ptr->posVector;
270 [ # # ]: 0 : DLIList<int>vertex_unique_ids = ca_vg_ptr->vgUIDs;
271 [ # # ][ # # ]: 0 : std::vector< std::pair<double, int> > list_of_pairs;
272 : :
273 : : //before partitioning edge, reorder split points, from lowest u to
274 : : //highest u; we use a pair so that the corresponding uids are reordered as well
275 [ # # ][ # # ]: 0 : for( i=split_points.size(); i--;)
276 : : {
277 [ # # ]: 0 : CubitVector *split_point = split_points.get_and_step();
278 [ # # ]: 0 : double u_param = owner_edge->u_from_position( *split_point );
279 [ # # ]: 0 : int uuid = vertex_unique_ids.get_and_step();
280 [ # # ]: 0 : std::pair<double, int> my_pair;
281 : 0 : my_pair.first = u_param;
282 : 0 : my_pair.second = uuid;
283 [ # # ]: 0 : list_of_pairs.push_back( my_pair );
284 : : }
285 : :
286 [ # # ][ # # ]: 0 : std::sort(list_of_pairs.begin(), list_of_pairs.end(), my_sort() );
[ # # ]
287 : :
288 : : //partition the curve with these split points
289 [ # # ]: 0 : split_points.reset();
290 : : PartitionTool::instance()->partition( owner_edge, split_points,
291 [ # # ][ # # ]: 0 : new_vertices, new_edges );
292 : :
293 [ # # ][ # # ]: 0 : assert( list_of_pairs.size() == (unsigned int)new_vertices.size() );
[ # # ]
294 : : //associate the vertex uuids with the new vertices
295 [ # # ]: 0 : new_vertices.reset();
296 [ # # ]: 0 : vertex_unique_ids.reset();
297 [ # # ]: 0 : std::vector< std::pair<double, int> >::iterator iter = list_of_pairs.begin();
298 [ # # ][ # # ]: 0 : for( i=new_vertices.size(); i--; )
299 : : {
300 [ # # ][ # # ]: 0 : new TDUniqueId( new_vertices.get_and_step(), (*iter).second );
[ # # ][ # # ]
[ # # ]
301 [ # # ]: 0 : iter++;
302 [ # # ]: 0 : }
303 : : }
304 : :
305 : : //associate the curve uuids with the new curve
306 [ # # ]: 0 : new_edges.reset();
307 : : RefEdge* ref_edge;
308 [ # # ]: 0 : vgUIDs.reset();
309 [ # # ][ # # ]: 0 : for( i=new_edges.size(); i--; )
310 : : {
311 [ # # ]: 0 : ref_edge = new_edges.get_and_step();
312 : : //get uuids of start and end vertices on curve
313 [ # # ]: 0 : RefVertex *start_vertex = ref_edge->start_vertex();
314 [ # # ]: 0 : RefVertex *end_vertex = ref_edge->end_vertex();
315 : :
316 [ # # ][ # # ]: 0 : int s_vert_uuid = TDUniqueId::get_unique_id( start_vertex );
317 [ # # ][ # # ]: 0 : int e_vert_uuid = TDUniqueId::get_unique_id( end_vertex );
318 : :
319 [ # # ][ # # ]: 0 : for( j=vgUIDs.size(); j--;)
320 : : {
321 [ # # ]: 0 : int s_uuid = vgUIDs.get_and_step();
322 [ # # ]: 0 : int e_uuid = vgUIDs.get_and_step();
323 [ # # ][ # # ]: 0 : if( (s_vert_uuid == s_uuid && e_vert_uuid == e_uuid ) ||
[ # # ]
324 [ # # ]: 0 : (e_vert_uuid == s_uuid && s_vert_uuid == e_uuid ) )
325 : : {
326 [ # # ][ # # ]: 0 : new TDUniqueId( ref_edge, vgUIDs.get_and_step() );
[ # # ][ # # ]
327 : 0 : break;
328 : : }
329 : : else
330 [ # # ]: 0 : vgUIDs.step();
331 : : }
332 [ # # ]: 0 : }
333 : : }
334 [ # # ]: 0 : else if (owner_face) //partition a surface
335 : : {
336 [ # # ]: 0 : DLIList<RefFace*> input_faces;
337 [ # # ]: 0 : input_faces.append( owner_face );
338 [ # # ][ # # ]: 0 : DLIList<CubitVector*> segments;
339 : :
340 : : //for each virtual curve used to partition surface
341 [ # # ]: 0 : ca_vg_ptr->numVCPoints.reset();
342 [ # # ]: 0 : ca_vg_ptr->posVector.reset();
343 [ # # ]: 0 : ca_vg_ptr->vgUIDs.step( ca_vg_ptr->numVV );
344 [ # # ]: 0 : for( i=ca_vg_ptr->numVC; i--;)
345 : : {
346 : : //get coordinates of start/end vertices of virtual curve
347 [ # # ][ # # ]: 0 : ToolDataUser *tdu = TDUniqueId::find_td_unique_id(ca_vg_ptr->vgUIDs.get_and_step());
348 [ # # ]: 0 : RefVertex *s_vert = CAST_TO( tdu, RefVertex );
349 [ # # ][ # # ]: 0 : tdu = TDUniqueId::find_td_unique_id(ca_vg_ptr->vgUIDs.get_and_step());
350 [ # # ]: 0 : RefVertex *e_vert = CAST_TO( tdu, RefVertex );
351 [ # # ]: 0 : DLIList<RefVertex*> vertices;
352 : :
353 : : CubitVector *vec1, *vec2;
354 [ # # ]: 0 : if( s_vert)
355 : : {
356 [ # # ][ # # ]: 0 : vec1 = new CubitVector(s_vert->coordinates());
357 [ # # ]: 0 : vertices.append( s_vert );
358 : : }
359 : :
360 : : else
361 [ # # ][ # # ]: 0 : vec1 = new CubitVector( *(ca_vg_ptr->posVector.get() ));
[ # # ]
362 : :
363 [ # # ]: 0 : segments.append( vec1 );
364 : :
365 [ # # ]: 0 : if( e_vert)
366 : : {
367 [ # # ][ # # ]: 0 : vec2 = new CubitVector(e_vert->coordinates());
368 [ # # ]: 0 : vertices.append( e_vert );
369 : : }
370 : : else
371 [ # # ][ # # ]: 0 : vec2 = new CubitVector( *(ca_vg_ptr->posVector.get() ));
[ # # ]
372 : :
373 : : //append any intermediate segments
374 [ # # ][ # # ]: 0 : for( j=ca_vg_ptr->numVCPoints.get_and_step(); j--; )
375 [ # # ][ # # ]: 0 : segments.append( ca_vg_ptr->posVector.get_and_step() );
376 : :
377 [ # # ]: 0 : segments.append( vec2 );
378 : : //partition the surf
379 [ # # ]: 0 : new_edges.clean_out();
380 [ # # ]: 0 : new_faces.clean_out();
381 [ # # ]: 0 : PartitionTool::instance()->insert_edge( input_faces, segments,
382 [ # # ]: 0 : new_faces, new_edges);
383 : :
384 : : //may need to merge some vertices
385 [ # # ][ # # ]: 0 : DLIList<RefVertex*> verts_to_merge;
386 [ # # ][ # # ]: 0 : for( j=new_edges.size(); j--;)
387 : : {
388 [ # # ][ # # ]: 0 : verts_to_merge.append( new_edges.get()->start_vertex() );
[ # # ]
389 [ # # ][ # # ]: 0 : verts_to_merge.append( new_edges.get()->end_vertex() );
[ # # ]
390 : : }
391 [ # # ]: 0 : verts_to_merge += vertices;
392 [ # # ]: 0 : verts_to_merge.uniquify_unordered();
393 : :
394 [ # # ][ # # ]: 0 : MergeTool::instance()->merge_refvertices( verts_to_merge );
395 : :
396 : : //give new edge uuids
397 [ # # ]: 0 : new_edges.reset();
398 [ # # ][ # # ]: 0 : for( j=new_edges.size(); j--;)
399 [ # # ][ # # ]: 0 : new TDUniqueId( new_edges.get_and_step(), ca_vg_ptr->vgUIDs.get_and_step() );
[ # # ][ # # ]
[ # # ]
400 : :
401 : 0 : delete vec1;
402 : 0 : delete vec2;
403 [ # # ]: 0 : segments.clean_out();
404 : :
405 : :
406 : : //associate the face uuids with the new faces
407 [ # # ]: 0 : new_faces.reset();
408 : : RefFace* ref_face;
409 [ # # ]: 0 : vgUIDs.reset();
410 [ # # ][ # # ]: 0 : for( j=new_faces.size(); j--; )
411 : : {
412 [ # # ]: 0 : ref_face = new_faces.get_and_step();
413 : :
414 : : //get uuids of each edge in surface
415 [ # # ]: 0 : DLIList<RefEdge*> edges;
416 [ # # ]: 0 : ref_face->ref_edges( edges );
417 [ # # ][ # # ]: 0 : DLIList<int> edge_uuids;
418 : :
419 [ # # ]: 0 : edges.reset();
420 [ # # ][ # # ]: 0 : for( k=edges.size(); k--;)
421 [ # # ][ # # ]: 0 : edge_uuids.append( TDUniqueId::get_unique_id( edges.get_and_step() ));
[ # # ][ # # ]
422 : :
423 : : //look at all the groups of boundary edges of each surface
424 [ # # ]: 0 : vgUIDs.reset();
425 [ # # ][ # # ]: 0 : for( k=numBdyCurves.size(); k--;)
426 : : {
427 : : int kk;
428 [ # # ]: 0 : DLIList<int> bdy_curve_uuids;
429 [ # # ]: 0 : int num_bdy_curves = numBdyCurves.get_and_step();
430 [ # # ][ # # ]: 0 : if( edge_uuids.size() != num_bdy_curves )
431 : 0 : continue;
432 [ # # ]: 0 : for( kk=num_bdy_curves; kk--;)
433 [ # # ][ # # ]: 0 : bdy_curve_uuids.append( vgUIDs.get_and_step());
434 : :
435 : 0 : CubitBoolean match = CUBIT_TRUE;
436 : 0 : kk=num_bdy_curves;
437 [ # # ][ # # ]: 0 : while( kk && match )
438 : : {
439 : 0 : kk--;
440 [ # # ][ # # ]: 0 : if( !bdy_curve_uuids.move_to( edge_uuids.get_and_step() ) )
[ # # ]
441 : : {
442 : 0 : match = CUBIT_FALSE;
443 : 0 : break;
444 : : }
445 : : }
446 [ # # ]: 0 : if( match )
447 : : {
448 [ # # ][ # # ]: 0 : new TDUniqueId( ref_face, vgUIDs.get() );
[ # # ][ # # ]
449 : 0 : break;
450 : : }
451 [ # # ][ # # ]: 0 : vgUIDs.step();
[ # # # ]
452 : 0 : }
453 [ # # ]: 0 : }
454 : :
455 [ # # ]: 0 : input_faces.clean_out();
456 [ # # ]: 0 : input_faces += new_faces;
457 : :
458 [ # # ][ # # ]: 0 : }
459 : : }
460 : :
461 : 0 : hasActuated = CUBIT_TRUE;
462 : :
463 : : // otherwise, we're done
464 [ # # ]: 0 : return CUBIT_SUCCESS;
465 [ + - ][ + - ]: 6364 : }
466 : :
467 : :
468 : :
|