/** \file cgroup.c * Compound tracks: Group * */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "compound.h" #include "cundo.h" #include "custom.h" #include "fileio.h" #include "i18n.h" #include "tbezier.h" #include "tcornu.h" #include "common.h" #include "messages.h" #include "param.h" #include "shrtpath.h" #include "track.h" #include "utility.h" /***************************************************************************** * * Ungroup / Group * */ static int log_group=-1; static dynArr_t pathPtr_da; #define pathPtr(N) DYNARR_N( char, pathPtr_da, N ) static char groupManuf[STR_SIZE]; static char groupDesc[STR_SIZE]; static char groupPartno[STR_SIZE]; static char groupTitle[STR_SIZE]; static int groupCompoundCount = 0; extern TRKTYP_T T_BZRTRK; extern TRKTYP_T T_BZRLIN; extern TRKTYP_T T_CORNU; typedef struct { int segInx; EPINX_T segEP; int inx; track_p trk; } mergePt_t; static dynArr_t mergePt_da; #define mergePt(N) DYNARR_N( mergePt_t, mergePt_da, N ) static void AddMergePt( int segInx, EPINX_T segEP ) { int inx; mergePt_t * mp; for ( inx=0; inxsegInx == segInx && mp->segEP == segEP ) return; } DYNARR_APPEND( mergePt_t, mergePt_da, 10 ); mp = &mergePt(mergePt_da.cnt-1); mp->segInx = segInx; mp->segEP = segEP; mp->inx = mergePt_da.cnt-1; LOG( log_group, 2, ( " MergePt: %d.%d\n", segInx, segEP ) ); } static EPINX_T FindEP( EPINX_T epCnt, trkEndPt_p endPts, coOrd pos ) { DIST_T dist; EPINX_T ep; for ( ep=0; epinx == mpInx ) mp->inx = map[segInx]; } } static void GroupCopyTitle( char * title ) { char *mP, *nP, *pP; int mL, nL, pL; ParseCompoundTitle( title, &mP, &mL, &nP, &nL, &pP, &pL ); if ( strncmp( nP, "Ungrouped ", 10 ) == 0 ) { nP += 10; nL -= 10; } if ( ++groupCompoundCount == 1 ) { strncpy( groupManuf, mP, mL ); groupManuf[mL] = '\0'; strncpy( groupDesc, nP, nL ); groupDesc[nL] = '\0'; strncpy( groupPartno, pP, pL ); groupPartno[pL] = '\0'; } else { if ( mL != (int)strlen( groupManuf ) || strncmp( groupManuf, mP, mL ) != 0 ) groupManuf[0] = '\0'; if ( nL != (int)strlen( groupDesc ) || strncmp( groupDesc, nP, nL ) != 0 ) groupDesc[0] = '\0'; if ( pL != (int)strlen( groupPartno ) || strncmp( groupPartno, pP, pL ) != 0 ) groupPartno[0] = '\0'; } } EXPORT void UngroupCompound( track_p trk ) { struct extraData *xx = GetTrkExtraData(trk); struct extraData *xx1; trkSeg_p sp; track_p trk0, trk1; int segCnt, segInx, segInx1; EPINX_T ep, epCnt, epCnt1=0, segEP, segEP1, eps[2]; char * cp; coOrd pos, orig, size; ANGLE_T angle; int inx; int off; mergePt_t * mp; trkEndPt_p epp; segProcData_t segProcData; static dynArr_t refCount_da; #define refCount(N) DYNARR_N( int, refCount_da, N ) typedef struct { track_p trk; EPINX_T ep[2]; } segTrack_t; #define segTrack(N) DYNARR_N( segTrack_t, segTrack_da, N ) static dynArr_t segTrack_da; segTrack_t * stp, * stp1; BOOL_T turnoutChanged; DYNARR_RESET( mergePt_t, mergePt_da ); DYNARR_RESET( int, refCount_da ); DYNARR_RESET( segTrack_t, segTrack_da ); GroupCopyTitle( xtitle(xx) ); #ifdef LATER for ( sp=sq=xx->segs; sp<&xx->segs[xx->segCnt]; sp++ ) { if ( IsSegTrack(sp) ) { *sq = *sp; sq++; } else { trk1 = MakeDrawFromSeg( xx->orig, xx->angle, sp ); if ( trk1 ) { SetTrkBits( trk1, TB_SELECTED ); DrawNewTrack( trk1 ); } } } if ( GetTrkEndPtCnt(trk) <= 0 ) { UndoDelete( trk ); return; } #endif LOG( log_group, 1, ( "Ungroup( T%d )\n", GetTrkIndex(trk) ) ); epCnt = GetTrkEndPtCnt(trk); for ( segCnt=0; segCntsegCnt&&IsSegTrack(&xx->segs[segCnt]); segCnt++ ); ASSERT( (epCnt==0) == (segCnt==0) ); turnoutChanged = FALSE; if ( epCnt > 0 ) { turnoutChanged = TRUE; /* 1: collect EPs */ DYNARR_SET( trkEndPt_t, tempEndPts_da, epCnt ); DYNARR_SET( segTrack_t, segTrack_da, segCnt ); memset( segTrack_da.ptr, 0, segCnt * sizeof segTrack(0) ); for ( ep=0; eppos = GetTrkEndPos( trk, ep ); epp->angle = GetTrkEndAngle( trk, ep ); Rotate( &epp->pos, xx->orig, -xx->angle ); epp->pos.x -= xx->orig.x; epp->pos.y -= xx->orig.y; epp->track = GetTrkEndTrk( trk, ep ); if ( epp->track ) epp->index = GetEndPtConnectedToMe( epp->track, trk ); else epp->index = -1; LOG( log_group, 1, ( " EP%d = [%0.3f %0.3f] A%0.3f T%d.%d\n", ep, epp->pos.x, epp->pos.y, epp->angle, epp->track?GetTrkIndex(epp->track):-1, epp->track?epp->index:-1 ) ); } /* 3: Count number of times each segment is referenced * If the refcount differs between adjacent segments * add segment with smaller count to mergePts * Treat EndPts as a phantom segment with inx above segCnt * Path ends that don't map onto a real EndPt (bumpers) get a fake EP */ DYNARR_SET( int, refCount_da, segCnt+epCnt ); memset( refCount_da.ptr, 0, refCount_da.cnt * sizeof *(int*)0 ); cp = (char *)xx->paths; while ( cp[0] ) { cp += strlen(cp)+1; while ( cp[0] ) { GetSegInxEP( cp[0], &segInx, &segEP ); pos = GetSegEndPt( xx->segs+segInx, segEP, FALSE, NULL ); segInx1 = FindEP( tempEndPts_da.cnt, &tempEndPts(0), pos ); if ( segInx1 >= 0 ) { segInx1 += segCnt; refCount(segInx1)++; } else { DYNARR_APPEND( trkEndPt_t, tempEndPts_da, 10 ); DYNARR_APPEND( int, refCount_da, 10 ); epp = &tempEndPts(tempEndPts_da.cnt-1); epp->pos = pos; epp->angle = 0; segInx1 = refCount_da.cnt-1; refCount(segInx1) = 2; } segEP1 = 0; while ( cp[0] ) { GetSegInxEP( cp[0], &segInx, &segEP ); refCount(segInx)++; if ( refCount(segInx) > refCount(segInx1) ) AddMergePt( segInx, segEP ); if ( refCount(segInx1) > refCount(segInx) ) AddMergePt( segInx1, segEP1 ); segInx1 = segInx; segEP1 = 1-segEP; cp++; } GetSegInxEP( cp[-1], &segInx, &segEP ); pos = GetSegEndPt( xx->segs+segInx, 1-segEP, FALSE, NULL ); segInx = FindEP( tempEndPts_da.cnt, &tempEndPts(0), pos ); if ( segInx >= 0 ) { segInx += segCnt; refCount(segInx)++; } else { DYNARR_APPEND( trkEndPt_t, tempEndPts_da, 10 ); DYNARR_APPEND( int, refCount_da, 10 ); epp = &tempEndPts(tempEndPts_da.cnt-1); epp->pos = pos; epp->angle = 0; segInx = refCount_da.cnt-1; refCount(segInx) = 2; } if ( refCount(segInx) > refCount(segInx1) ) { AddMergePt( segInx, 0 ); } cp++; } cp++; } epCnt1 = tempEndPts_da.cnt; /* 4: For each path element, map segment to a mergePt if the adjacent segment * and EP is a mergePt * If segment is already mapped then merge mergePts */ DYNARR_SET( int, refCount_da, segCnt ); memset( refCount_da.ptr, -1, segCnt * sizeof *(int*)0 ); cp = (char *)xx->paths; while ( cp[0] ) { cp += strlen(cp)+1; while ( cp[0] ) { GetSegInxEP( cp[0], &segInx, &segEP ); pos = GetSegEndPt( xx->segs+segInx, segEP, FALSE, NULL ); /*REORIGIN1( pos, xx->angle, xx->orig );*/ segInx1 = FindEP( tempEndPts_da.cnt, &tempEndPts(0), pos ); if ( segInx1 >= 0 ) { segInx1 += segCnt; } segEP1 = 0; while ( cp[0] ) { GetSegInxEP( cp[0], &segInx, &segEP ); if ( segInx1 >= 0 ) { for ( inx=0; inxsegInx == segInx1 && mp->segEP == segEP1 ) { SegOnMP( segInx, mp->inx, segCnt, &refCount(0) ); } if ( mp->segInx == segInx && mp->segEP == segEP ) { SegOnMP( segInx1, mp->inx, segCnt, &refCount(0) ); } } } segInx1 = segInx; segEP1 = 1-segEP; cp++; } GetSegInxEP( cp[-1], &segInx, &segEP ); pos = GetSegEndPt( xx->segs+segInx, 1-segEP, FALSE, NULL ); /*REORIGIN1( pos, xx->angle, xx->orig );*/ segInx = FindEP( tempEndPts_da.cnt, &tempEndPts(0), pos ); if ( segInx >= 0 ) { segInx += segCnt; for ( inx=0; inxsegInx == segInx && mp->segEP == 0 ) { SegOnMP( segInx1, mp->inx, segCnt, &refCount(0) ); } } } cp++; } cp++; } /* 5: Check is all segments are on the same mergePt, which means there is nothing to do */ if ( mergePt_da.cnt > 0 ) { for ( segInx=0; segInxsegCnt ) { /* no non-track segments to remove */ return; } } } } /* 6: disconnect, undraw, remove non-track segs, return if there is nothing else to do */ wDrawDelayUpdate( mainD.d, TRUE ); if ( turnoutChanged ) { for ( ep=0; eptrack ) { DrawEndPt( &mainD, epp->track, epp->index, wDrawColorWhite ); DrawEndPt( &mainD, trk, ep, wDrawColorWhite ); DisconnectTracks( trk, ep, epp->track, epp->index ); } } } UndrawNewTrack(trk); for ( sp=xx->segs; sp<&xx->segs[xx->segCnt]; sp++ ) { if ( ! IsSegTrack(sp) ) { trk1 = MakeDrawFromSeg( xx->orig, xx->angle, sp ); if ( trk1 ) { SetTrkBits( trk1, TB_SELECTED ); DrawNewTrack( trk1 ); } } } if ( !turnoutChanged ) { if ( epCnt <= 0 ) { trackCount--; UndoDelete( trk ); } else { UndoModify( trk ); xx->segCnt = segCnt; DrawNewTrack( trk ); } wDrawDelayUpdate( mainD.d, FALSE ); return; } /* 7: for each valid mergePt, create a new turnout */ for ( inx=0; inxinx != inx ) continue; DYNARR_RESET( trkSeg_t, tempSegs_da ); DYNARR_SET( trkEndPt_t, tempEndPts_da, epCnt1 ); DYNARR_RESET( char, pathPtr_da ); for ( segInx=0; segInxsegs[segInx]; sprintf( message, "P%d", segInx ); off = pathPtr_da.cnt; DYNARR_SET( char, pathPtr_da, off+(int)strlen(message)+4 ); strcpy( &pathPtr(off), message ); off = pathPtr_da.cnt-3; pathPtr(off+0) = (char)tempSegs_da.cnt; pathPtr(off+1) = '\0'; pathPtr(off+2) = '\0'; for ( ep=0; ep<2; ep++ ) { pos = GetSegEndPt( xx->segs+segInx, ep, FALSE, &angle ); segEP = FindEP( epCnt1, &tempEndPts(0), pos ); if ( segEP >= 0 && segEP >= epCnt && segEP < epCnt1 ) { /* was a bumper: no EP */ eps[ep] = -1; continue; } REORIGIN1( pos, xx->angle, xx->orig ); angle = NormalizeAngle( xx->angle+angle ); eps[ep] = FindEP( tempEndPts_da.cnt-epCnt1, &tempEndPts(epCnt1), pos ); if ( eps[ep] < 0 ) { DYNARR_APPEND( trkEndPt_t, tempEndPts_da, 10 ); eps[ep] = tempEndPts_da.cnt-1-epCnt1; epp = &tempEndPts(tempEndPts_da.cnt-1); memset( epp, 0, sizeof *epp ); epp->pos = pos; epp->angle = angle; } } segTrack(segInx).ep[0] = eps[0]; segTrack(segInx).ep[1] = eps[1]; } } DYNARR_SET( char, pathPtr_da, pathPtr_da.cnt+1 ); pathPtr(pathPtr_da.cnt-1) = '\0'; if ( tempSegs_da.cnt == 0 ) { AbortProg( "tempSegs_da.cnt == 0" ); continue; } GetSegBounds( zero, 0, tempSegs_da.cnt, &tempSegs(0), &orig, &size ); orig.x = -orig.x; orig.y = -orig.y; MoveSegs( tempSegs_da.cnt, &tempSegs(0), orig ); Rotate( &orig, zero, xx->angle ); orig.x = xx->orig.x - orig.x; orig.y = xx->orig.y - orig.y; trk1 = NewCompound( T_TURNOUT, 0, orig, xx->angle, xx->title, tempEndPts_da.cnt-epCnt1, &tempEndPts(epCnt1), pathPtr_da.cnt, &pathPtr(0), tempSegs_da.cnt, &tempSegs(0) ); xx1 = GetTrkExtraData(trk1); xx1->ungrouped = TRUE; SetTrkVisible( trk1, TRUE ); SetTrkBits( trk1, TB_SELECTED ); for ( segInx=0; segInxtrk = trk1; } /* 8: for remaining segments, create simple tracks */ for ( segInx=0; segInx= 0 ) continue; SegProc( SEGPROC_NEWTRACK, xx->segs+segInx, &segProcData ); SetTrkScale( segProcData.newTrack.trk, GetTrkScale(trk) ); SetTrkBits( segProcData.newTrack.trk, TB_SELECTED ); MoveTrack( segProcData.newTrack.trk, xx->orig ); RotateTrack( segProcData.newTrack.trk, xx->orig, xx->angle ); segTrack(segInx).trk = segProcData.newTrack.trk; segTrack(segInx).ep[0] = segProcData.newTrack.ep[0]; segTrack(segInx).ep[1] = segProcData.newTrack.ep[1]; } /* 9: reconnect tracks */ cp = (char *)xx->paths; while ( cp[0] ) { cp += strlen(cp)+1; while ( cp[0] ) { /* joint EP to this segment */ GetSegInxEP( cp[0], &segInx, &segEP ); stp = &segTrack(segInx); ep = FindEP( epCnt, &tempEndPts(0), GetSegEndPt( xx->segs+segInx, segEP, FALSE, NULL ) ); if ( ep >= 0 ) { epp = &tempEndPts(ep); if ( epp->track ) { ConnectTracks( stp->trk, stp->ep[segEP], epp->track, epp->index ); DrawEndPt( &mainD, epp->track, epp->index, GetTrkColor(epp->track,&mainD) ); epp->track = NULL; } } stp1 = stp; segEP1 = 1-segEP; cp++; while ( cp[0] ) { GetSegInxEP( cp[0], &segInx, &segEP ); stp = &segTrack(segInx); trk0 = GetTrkEndTrk( stp->trk, stp->ep[segEP] ); trk1 = GetTrkEndTrk( stp1->trk, stp1->ep[segEP1] ); if ( trk0 == NULL ) { if ( trk1 != NULL ) AbortProg( "ungroup: seg half connected" ); ConnectTracks( stp->trk, stp->ep[segEP], stp1->trk, stp1->ep[segEP1] ); } else { if ( trk1 != stp->trk || stp1->trk != trk0 ) AbortProg( "ungroup: last seg not connected to curr" ); } stp1 = stp; segEP1 = 1-segEP; cp++; } /* joint EP to last segment */ ep = FindEP( epCnt, &tempEndPts(0), GetSegEndPt( xx->segs+segInx, segEP1, FALSE, NULL ) ); if ( ep > 0 ) { epp = &tempEndPts(ep); if ( epp->track ) { ConnectTracks( stp1->trk, stp1->ep[segEP1], epp->track, epp->index ); DrawEndPt( &mainD, epp->track, epp->index, wDrawColorWhite ); epp->track = NULL; } } cp++; } cp++; } /* 10: cleanup: delete old track, draw new tracks */ UndoDelete( trk ); trackCount--; for ( segInx=0; segInx= 0 ) { mp = &mergePt( refCount(segInx) ); if ( mp->trk ) { DrawNewTrack( mp->trk ); mp->trk = NULL; } } else { DrawNewTrack( segTrack(segInx).trk ); } } wDrawDelayUpdate( mainD.d, FALSE ); } EXPORT void DoUngroup( void ) { track_p trk = NULL; int ungroupCnt; int oldTrackCount; TRKINX_T lastTrackIndex; if ( log_group < 0 ) log_group = LogFindIndex( "group" ); groupManuf[0] = 0; groupDesc[0] = 0; groupPartno[0] = 0; ungroupCnt = 0; oldTrackCount = trackCount; UndoStart( _("Ungroup Object"), "Ungroup Objects" ); lastTrackIndex = max_index; groupCompoundCount = 0; while ( TrackIterate( &trk ) ) { if ( GetTrkSelected( trk ) && GetTrkIndex(trk) <= lastTrackIndex ) { oldTrackCount = trackCount; UngroupTrack( trk ); if ( oldTrackCount != trackCount ) ungroupCnt++; } } if ( ungroupCnt ) InfoMessage( _("%d objects ungrouped"), ungroupCnt ); else InfoMessage( _("No objects ungrouped") ); } static drawCmd_t groupD = { NULL, &tempSegDrawFuncs, DC_GROUP, 1, 0.0, {0.0, 0.0}, {0.0, 0.0}, Pix2CoOrd, CoOrd2Pix }; static long groupSegCnt; static long groupReplace; char * groupReplaceLabels[] = { N_("Replace with new group?"), NULL }; static wWin_p groupW; static paramIntegerRange_t r0_999999 = { 0, 999999 }; static paramData_t groupPLs[] = { /*0*/ { PD_STRING, groupManuf, "manuf", PDO_NOPREF | PDO_STRINGLIMITLENGTH, (void*)350, N_("Manufacturer"), 0, (void *)sizeof(groupManuf) }, /*1*/ { PD_STRING, groupDesc, "desc", PDO_NOPREF | PDO_STRINGLIMITLENGTH, (void*)230, N_("Description"), 0, (void *)sizeof(groupDesc) }, /*2*/ { PD_STRING, groupPartno, "partno", PDO_NOPREF|PDO_DLGHORZ|PDO_DLGIGNORELABELWIDTH|PDO_STRINGLIMITLENGTH, (void*)100, N_("#"), 0, (void *)sizeof(groupPartno) }, /*3*/ { PD_LONG, &groupSegCnt, "segcnt", PDO_NOPREF, &r0_999999, N_("# Segments"), BO_READONLY }, /*4*/ { PD_TOGGLE, &groupReplace, "replace", 0, groupReplaceLabels, "", BC_HORZ|BC_NOBORDER } }; static paramGroup_t groupPG = { "group", 0, groupPLs, sizeof groupPLs/sizeof groupPLs[0] }; typedef struct { track_p trk; int segStart; int segEnd; } groupTrk_t, * groupTrk_p; static dynArr_t groupTrk_da; #define groupTrk(N) DYNARR_N( groupTrk_t, groupTrk_da, N ) typedef struct { int groupInx; EPINX_T ep1, ep2; PATHPTR_T path; BOOL_T flip; } pathElem_t, *pathElem_p; typedef struct { int pathElemStart; int pathElemEnd; EPINX_T ep1, ep2; int conflicts; BOOL_T inGroup; BOOL_T done; } path_t, *path_p; static dynArr_t path_da; #define path(N) DYNARR_N( path_t, path_da, N ) static dynArr_t pathElem_da; #define pathElem(N) DYNARR_N( pathElem_t, pathElem_da, N ) static int pathElemStart; static BOOL_T CheckTurnoutEndPoint( trkSeg_p segs, coOrd pos, int end ) { coOrd pos1; DIST_T d; pos1 = GetSegEndPt( segs, end, FALSE, NULL ); d = FindDistance( pos, pos1 ); return ( d < connectDistance ); } static char * FindPathBtwEP( track_p trk, EPINX_T ep1, EPINX_T ep2, BOOL_T * flip ) { struct extraData * xx = GetTrkExtraData( trk ); char * cp, *cp0; int epN; coOrd pos1, pos2; int segInx; EPINX_T segEP; if ( GetTrkType(trk) != T_TURNOUT ) { if ( ep1+ep2 != 1 ) AbortProg( "findPathBtwEP" ); *flip = ( ep1 == 1 ); if (GetTrkType(trk) == T_CORNU ) { // Cornu doesn't have a path but lots of segs! cp = CreateSegPathList(trk); // Make path LOG( log_group, 2, ( " Group: Cornu path:%s \n", cp ) ) } else cp = "\1\0\0"; //One segment (but could be a Bezier) return cp; } cp = (char *)xx->paths; pos1 = GetTrkEndPos(trk,ep1); Rotate( &pos1, xx->orig, -xx->angle ); pos1.x -= xx->orig.x; pos1.y -= xx->orig.y; pos2 = GetTrkEndPos(trk,ep2); Rotate( &pos2, xx->orig, -xx->angle ); pos2.x -= xx->orig.x; pos2.y -= xx->orig.y; while ( cp[0] ) { cp += strlen(cp)+1; //Ignore Path Name while ( cp[0] ) { cp0 = cp; epN = -1; GetSegInxEP( cp[0], &segInx, &segEP ); if ( CheckTurnoutEndPoint( &xx->segs[segInx], pos1, segEP ) ) epN = 1; else if ( CheckTurnoutEndPoint( &xx->segs[segInx], pos2, segEP ) ) epN = 0; cp += strlen(cp); if ( epN != -1 ) { GetSegInxEP( cp[-1], &segInx, &segEP ); if ( CheckTurnoutEndPoint( &xx->segs[segInx], epN==0?pos1:pos2, 1-segEP ) ) { *flip = epN==0; // If its reversed, set up to be flipped or noted return cp0; //Found path between EPs } } cp++; } cp++; } return NULL; } static int GroupShortestPathFunc( SPTF_CMD cmd, track_p trk, EPINX_T ep1, EPINX_T ep2, DIST_T dist, void * data ) { track_p trk1; path_t *pp; pathElem_t *ppp; BOOL_T flip; int inx; EPINX_T ep; coOrd pos1, pos2; ANGLE_T angle, ang1, ang2; switch ( cmd ) { case SPTC_MATCH: if ( !GetTrkSelected(trk) ) return 0; trk1 = GetTrkEndTrk(trk,ep1); if ( trk1 == NULL ) return 1; if ( !GetTrkSelected(trk1) ) return 1; return 0; case SPTC_MATCHANY: return -1; case SPTC_ADD_TRK: if (log_shortPath<=0||logTable(log_shortPath).level<4) LOG( log_group, 2, ( " T%d[%d]\n", GetTrkIndex(trk), ep2 ) ) DYNARR_APPEND( pathElem_t, pathElem_da, 10 ); ppp = &pathElem(pathElem_da.cnt-1); for ( inx=0; inxgroupInx = inx; ppp->ep1 = ep1; ppp->ep2 = ep2; ppp->path = (PATHPTR_T)FindPathBtwEP( trk, ep1, ep2, &ppp->flip ); return 0; } } AbortProg( "GroupShortestPathFunc(SPTC_ADD_TRK, T%d) - track not in group", GetTrkIndex(trk) ); case SPTC_TERMINATE: ppp = &pathElem(pathElemStart); trk = groupTrk(ppp->groupInx).trk; pos1 = GetTrkEndPos( trk, ppp->ep2 ); ang1 = GetTrkEndAngle( trk, ppp->ep2 ); ppp = &pathElem(pathElem_da.cnt-1); trk = groupTrk(ppp->groupInx).trk; pos2 = GetTrkEndPos( trk, ppp->ep1 ); ang2 = GetTrkEndAngle( trk, ppp->ep1 ); ep1 = ep2 = -1; for ( ep=0; epep1 == ep1 || pp->ep2 == ep1 ) ) && ( ep2 < 0 || ( pp->ep1 == ep2 || pp->ep2 == ep2 ) ) ) { LOG( log_group, 2, ( " Remove: duplicate path P%d\n", inx ) ) pathElem_da.cnt = pathElemStart; return 0; } } DYNARR_APPEND( path_t, path_da, 10 ); pp = &path(path_da.cnt-1); memset( pp, 0, sizeof *pp ); pp->pathElemStart = pathElemStart; pp->pathElemEnd = pathElem_da.cnt-1; pp->ep1 = ep1; pp->ep2 = ep2; pathElemStart = pathElem_da.cnt; LOG( log_group, 2, ( " Keep\n" ) ) return 0; case SPTC_IGNNXTTRK: if ( !GetTrkSelected(trk) ) return 1; if ( ep1 == ep2 ) return 1; if ( GetTrkEndPtCnt(trk) == 2 ) return 0; if ( GetTrkType(trk) != T_TURNOUT ) AbortProg( "GroupShortestPathFunc(IGNNXTTRK,T%d:%d,%d)", GetTrkIndex(trk), ep1, ep2 ); return FindPathBtwEP( trk, ep2, ep1, &flip ) == NULL; case SPTC_VALID: return 1; } return 0; } static int CmpGroupOrder( const void * ptr1, const void * ptr2 ) { int inx1 = *(int*)ptr1; int inx2 = *(int*)ptr2; return path(inx1).conflicts-path(inx2).conflicts; } static coOrd endPtOrig; static ANGLE_T endPtAngle; static int CmpEndPtAngle( const void * ptr1, const void * ptr2 ) { ANGLE_T angle; trkEndPt_p epp1 = (trkEndPt_p)ptr1; trkEndPt_p epp2 = (trkEndPt_p)ptr2; angle = NormalizeAngle(FindAngle(endPtOrig,epp1->pos)-endPtAngle) - NormalizeAngle(FindAngle(endPtOrig,epp2->pos)-endPtAngle); return (int)angle; } static int ConflictPaths( path_p path0, path_p path1 ) { /* do these paths share an EP? */ if ( path0->ep1 == path1->ep1 ) return TRUE; if ( path0->ep1 == path1->ep2 ) return TRUE; if ( path0->ep2 == path1->ep1 ) return TRUE; if ( path0->ep2 == path1->ep2 ) return TRUE; return FALSE; } static BOOL_T CheckPathEndPt( track_p trk, char cc, EPINX_T ep ) { struct extraData *xx = GetTrkExtraData(trk); wIndex_t segInx; EPINX_T segEP, epCnt; DIST_T d; coOrd pos; GetSegInxEP( cc, &segInx, &segEP ); if ( ep ) segEP = 1-segEP; pos = GetSegEndPt( &xx->segs[segInx], segEP, FALSE, NULL ); REORIGIN1( pos, xx->angle, xx->orig ); epCnt = GetTrkEndPtCnt(trk); for ( ep=0; eppaths; while ( cp[0] ) { cp += strlen(cp)+1; while ( cp[0] ) { if ( !CheckPathEndPt( trk, cp[0], 0 ) ) return FALSE; while ( cp[0] ) cp++; if ( !CheckPathEndPt( trk, cp[-1], 1 ) ) return FALSE; cp++; } cp++; } return TRUE; } static void GroupOk( void * junk ) { struct extraData *xx = NULL; turnoutInfo_t * to; int inx; EPINX_T ep, epCnt, epN; coOrd orig, size; long oldOptions; FILE * f = NULL; BOOL_T rc = TRUE; track_p trk, trk1; path_t * pp, *ppN; pathElem_p ppp; groupTrk_p groupP; BOOL_T flip, flip1, allDone; DIST_T dist; ANGLE_T angle, angleN; pathElem_t pathElemTemp; char * cp=NULL; #ifdef SEGMAP pathElem_p ppp1, ppp2; int segInx1, segInx2; coOrd pos1, pos2; static dynArr_t segMap_da; #define segMap(I,J) DYNARR_N( char, segMap_da, (2*(I)+0)*trackSegs_da.cnt+(J) ) #define segAcc(I,J) DYNARR_N( char, segMap_da, (2*(I)+1)*trackSegs_da.cnt+(J) ) #define segSum(I,J) DYNARR_N( char, segMap_da, (2*(groupTrk_da.cnt)+0)*trackSegs_da.cnt+(J) ) #endif static dynArr_t trackSegs_da; #define trackSegs(N) DYNARR_N( trkSeg_t, trackSegs_da, N ) trkSeg_p segPtr; int segCnt; static dynArr_t conflictMap_da; #define conflictMap( I, J ) DYNARR_N( int, conflictMap_da, (I)*(path_da.cnt)+(J) ) #define segFlip( N ) DYNARR_N( int, conflictMap_da, (N) ) static dynArr_t groupOrder_da; #define groupOrder( N ) DYNARR_N( int, groupOrder_da, N ) static dynArr_t groupMap_da; #define groupMap( I, J ) DYNARR_N( int, groupMap_da, (I)*(path_da.cnt+1)+(J) ) int groupCnt; int pinx, pinx2, ginx, ginx2, gpinx2; trkEndPt_p endPtP; PATHPTR_T path; int pathLen; signed char pathChar; char *oldLocale = NULL; #ifdef SEGMAP DYNARR_RESET( char, segMap_da ); #endif DYNARR_RESET( trkSeg_t, trackSegs_da ); DYNARR_RESET( trkSeg_t, tempSegs_da ); DYNARR_RESET( groupTrk_t, groupTrk_da ); DYNARR_RESET( path_t, path_da ); DYNARR_RESET( pathElem_t, pathElem_da ); DYNARR_RESET( trkEndPt_t, tempEndPts_da ); DYNARR_RESET( char, pathPtr_da ); ParamUpdate( &groupPG ); if ( groupManuf[0]==0 || groupDesc[0]==0 || groupPartno[0]==0 ) { NoticeMessage2( 0, MSG_GROUP_NONBLANK, _("Ok"), NULL ); return; } sprintf( message, "%s\t%s\t%s", groupManuf, groupDesc, groupPartno ); if ( strcmp( message, groupTitle ) != 0 ) { if ( FindCompound( FIND_TURNOUT|FIND_STRUCT, curScaleName, message ) ) if ( !NoticeMessage2( 1, MSG_TODSGN_REPLACE, _("Yes"), _("No") ) ) return; strcpy( groupTitle, message ); } wDrawDelayUpdate( mainD.d, TRUE ); /* * Collect tracks */ trk = NULL; while ( TrackIterate( &trk ) ) { if ( GetTrkSelected( trk ) ) { if ( IsTrack(trk) ) { DYNARR_APPEND( groupTrk_t, groupTrk_da, 10 ); groupP = &groupTrk(groupTrk_da.cnt-1); groupP->trk = trk; groupP->segStart = trackSegs_da.cnt; if ( GetTrkType(trk) == T_TURNOUT ) { xx = GetTrkExtraData(trk); for ( pinx=0; pinxsegCnt; pinx++ ) { segPtr = &xx->segs[pinx]; if ( IsSegTrack(segPtr) ) { DYNARR_APPEND( trkSeg_t, trackSegs_da, 10 ); trackSegs(trackSegs_da.cnt-1) = *segPtr; RotateSegs( 1, &trackSegs(trackSegs_da.cnt-1), zero, xx->angle ); MoveSegs( 1, &trackSegs(trackSegs_da.cnt-1), xx->orig ); } else { DrawSegs( &groupD, xx->orig, xx->angle, segPtr, 1, trackGauge, wDrawColorBlack ); } } } else if (GetTrkType(trk) == T_BEZIER || GetTrkType(trk) == T_BZRLIN ) { DYNARR_APPEND(trkSeg_t, trackSegs_da, 10); segPtr = &trackSegs(trackSegs_da.cnt-1); GetBezierSegmentFromTrack(trk,segPtr); } else if (GetTrkType(trk) == T_CORNU) { GetBezierSegmentsFromCornu(trk,&trackSegs_da); //Only give back Bezier - cant be undone } else { segCnt = tempSegs_da.cnt; oldOptions = groupD.options; groupD.options |= (DC_QUICK|DC_SIMPLE|DC_SEGTRACK); DrawTrack( trk, &groupD, wDrawColorBlack ); groupD.options = oldOptions; DYNARR_APPEND( trkSeg_t, trackSegs_da, 10 ); segPtr = &trackSegs(trackSegs_da.cnt-1); *segPtr = tempSegs( segCnt ); if ( tempSegs_da.cnt != segCnt+1 || !IsSegTrack(segPtr) ) { NoticeMessage2( 0, MSG_CANNOT_GROUP_TRACK, _("Ok"), NULL ); wHide( groupW ); return; } tempSegs_da.cnt = segCnt; } groupP->segEnd = trackSegs_da.cnt-1; } else { DrawTrack( trk, &groupD, wDrawColorBlack ); } } } if ( groupTrk_da.cnt>0 ) { if ( groupTrk_da.cnt > 128 ) { NoticeMessage( MSG_TOOMANYSEGSINGROUP, _("Ok"), NULL ); wDrawDelayUpdate( mainD.d, FALSE ); wHide( groupW ); return; } /* * Collect EndPts and find paths */ pathElemStart = 0; endPtOrig = zero; for ( inx=0; inx=tempEndPts_da.cnt ) { DYNARR_APPEND( trkEndPt_t, tempEndPts_da, 10 ); endPtP = &tempEndPts(tempEndPts_da.cnt-1); memset( endPtP, 0, sizeof *endPtP ); endPtP->pos = GetTrkEndPos(trk,ep); endPtP->angle = GetTrkEndAngle(trk,ep); endPtP->track = trk1; endPtP->index = (trk1?GetEndPtConnectedToMe(trk1,trk):-1); endPtOrig.x += endPtP->pos.x; endPtOrig.y += endPtP->pos.y; } } } } if ( tempEndPts_da.cnt <= 0 ) { NoticeMessage( _("No endpts"), _("Ok"), NULL ); wDrawDelayUpdate( mainD.d, FALSE ); wHide( groupW ); return; } if ( groupTrk_da.cnt == 1 && GetTrkType( groupTrk(0).trk ) == T_TURNOUT ) { path = xx->paths; pathLen = xx->pathLen; goto groupSimpleTurnout; } /* Make sure no turnouts in groupTrk list have a path end which is not an EndPt */ //TODO Add Trap Points (which are Turnouts with a bumper track) for ( inx=0; inx NormalizeAngle( tempEndPts(1).angle - tempEndPts(0).angle ) ) { #ifdef LATER if ( endPtAngle-FindAngle(endPtOrig,tempEndPts(tempEndPts_da.cnt-1).pos) > FindAngle(endPtOrig,tempEndPts(1).pos)-endPtAngle ) { #endif for ( ep=1; ep<(tempEndPts_da.cnt+1)/2; ep++ ) { trkEndPt_t tempEndPt; tempEndPt = tempEndPts(ep); tempEndPts(ep) = tempEndPts(tempEndPts_da.cnt-ep); tempEndPts(tempEndPts_da.cnt-ep) = tempEndPt; } } /* * Find shortest Paths */ for ( inx=0; inxdone ) continue; for ( pinx2=0; pinx2ep1 == ppN->ep1 || pp->ep2 == ppN->ep2 ) { pp->done = TRUE; allDone = FALSE; LOG( log_group, 1, ( "P%d aligns with P%d\n", pinx, pinx2 ) ); break; } if ( pp->ep1 == ppN->ep2 || pp->ep2 == ppN->ep1 ) { pp->done = TRUE; allDone = FALSE; LOG( log_group, 1, ( "P%d aligns flipped with P%d\n", pinx, pinx2 ) ); inx = (pp->pathElemStart+pp->pathElemEnd-1)/2; for ( ginx=pp->pathElemStart,ginx2=pp->pathElemEnd; ginx<=inx; ginx++,ginx2-- ) { pathElemTemp = pathElem(ginx); pathElem(ginx) = pathElem(ginx2); pathElem(ginx2) = pathElemTemp; } for ( ginx=pp->pathElemStart; ginx<=pp->pathElemEnd; ginx++ ) { ppp = &pathElem(ginx); ep = ppp->ep1; ppp->ep1 = ppp->ep2; ppp->ep2 = ep; ppp->flip = !ppp->flip; } ep = pp->ep1; pp->ep1 = pp->ep2; pp->ep2 = ep; break; } } if ( inx<0 && !pp->done ) inx = pinx; } if ( allDone && inx>=0 ) { allDone = FALSE; path(inx).done = TRUE; } } if ( log_group >= 1 && logTable(log_group).level > log_group ) { LogPrintf( "Group Paths\n" ); for ( pinx=0; pinxep1, pp->ep2 ); for ( pinx2=pp->pathElemEnd; pinx2>=pp->pathElemStart; pinx2-- ) { ppp = &pathElem(pinx2); LogPrintf( " %sT%d:%d.%d", ppp->flip?"-":"", GetTrkIndex(groupTrk(ppp->groupInx).trk), ppp->ep1, ppp->ep2 ); } LogPrintf( "\n" ); } } #ifdef SEGMAP DYNARR_SET( char, segMap_da, 2 * trackSegs_da.cnt * path_da.cnt + 2 ); memset( segMap_da.ptr, 0, segMap_da.max * sizeof segMap(0,0) ); for ( inx=0; inxpathElem_da.cnt-1; inx2>=0; inx2-- ) { ppp = &pathElem(pp->pathElemStart+inx2); groupP = &groupTrk(ppp->groupInx); if ( GetTrkEndPtCnt(groupP->trk) == 2 ) { segMap(inx,groupP->segStart) = 1; continue; } cp = ppp->path; if ( cp == NULL ) continue; segInx1 = cp[0]-1; for ( ; *cp; cp++ ) segMap(inx,groupP->segInx+cp[0]-1) = 1; segInx2 = cp[-1]-1; pos1 = GetSegEndPt( &trackSegs(groupP->segInx+segInx1), ppp->flip?1:0, FALSE, NULL ); pos2 = GetSegEndPt( &trackSegs(groupP->segInx+segInx2), ppp->flip?0:1, FALSE, NULL ); for ( inx3=0; inx3segCnt; inx3++ ) { if ( inx3 == segInx1 || inx3 == segInx2 ) continue; if ( segMap(inx,groupP->segInx+inx3) != 0 ) continue; if ( CheckTurnoutEndPoint( &trackSegs(groupP->segInx+inx3), pos1, 0 ) ) segMap(inx,inx3) = 2; else if ( CheckTurnoutEndPoint( &trackSegs(groupP->segInx+inx3), pos2, 0 ) ) segMap(inx,groupP->segInx+inx3) = 2; } } } if ( log_group >= 1 && logTable(log_group).level > log_group ) { LogPrintf( "Path to Segment Map\n "); for ( inx=0; inxtrk) ); for ( inx2=1; inx2segCnt; inx2++ ) LogPrintf( "--" ); } LogPrintf( "\n " ); for ( inx=0; inxsegCnt; inx2++ ) LogPrintf( "%2d", inx2+1 ); } LogPrintf( "\n" ); for ( inx=0; inxinGroup ) continue; pp->inGroup = TRUE; groupCnt++; groupMap( groupCnt-1, 0 ) = groupOrder(pinx); ginx = 1; for ( pinx2=pinx+1; pinx2=0; ginx++ ); for ( pinx2=0; pinx2= 1 && logTable(log_group).level > log_group ) { LogPrintf( "Group Map\n"); for ( pinx=0; pinx= 0; ginx++ ) LogPrintf( " %d", groupMap(pinx,ginx) ); LogPrintf( "\n" ); } } #ifdef SEGMAP for ( inx=0; inxgroupInx < 0 ) continue; for ( inx2=0; inx2path; *path; path++ ) { inx = *path; if ( inx<0 ) inx = - inx; if ( inx > trackSegs_da.cnt ) AbortProg( "inx > trackSegs_da.cnt" ); flip = *path<0; if ( ppp->flip ) flip = !flip; inx += groupTrk(ppp->groupInx).segStart - 1; if ( !flip ) segFlip(inx)++; else segFlip(inx)--; } } /* * Flip each segment that is used as flipped more than not */ for ( pinx=0; pinx= 0; ginx++ ) { pp = &path(groupMap(pinx,ginx)); for ( pinx2=pp->pathElemEnd; pinx2>=pp->pathElemStart; pinx2-- ) { ppp = &pathElem( pinx2 ); groupP = &groupTrk( ppp->groupInx ); path = ppp->path; flip = ppp->flip; if ( path == NULL ) AbortProg( "Missing Path T%d:%d.%d", GetTrkIndex(groupP->trk), ppp->ep2, ppp->ep1 ); if ( flip ) path += strlen((char *)path)-1; while ( *path && (path >= ppp->path) ) { //Add Guard for flip backwards DYNARR_APPEND( char, pathPtr_da, 10 ); pathChar = *path; flip1 = flip; if ( pathChar < 0 ) { flip1 = !flip; pathChar = - pathChar; } pathChar = groupP->segStart+pathChar; if ( segFlip(pathChar-1)<0 ) flip1 = ! flip1; if ( flip1 ) pathChar = - pathChar; pathPtr(pathPtr_da.cnt-1) = pathChar; path += (flip?-1:1); } } DYNARR_APPEND( char, pathPtr_da, 10 ); pathPtr(pathPtr_da.cnt-1) = 0; } DYNARR_APPEND( char, pathPtr_da, 10 ); pathPtr(pathPtr_da.cnt-1) = 0; } DYNARR_APPEND( char, pathPtr_da, 10 ); pathPtr(pathPtr_da.cnt-1) = 0; path = (PATHPTR_T)&pathPtr(0); pathLen = pathPtr_da.cnt; groupSimpleTurnout: /* * Copy and Reorigin Segments */ if ( tempSegs_da.cnt > 0 ) { inx = trackSegs_da.cnt; DYNARR_SET( trkSeg_t, trackSegs_da, trackSegs_da.cnt+tempSegs_da.cnt ); memcpy( &trackSegs(inx), tempSegs_da.ptr, tempSegs_da.cnt*sizeof trackSegs(0) ); CloneFilledDraw( tempSegs_da.cnt, &trackSegs(inx), TRUE ); } GetSegBounds( zero, 0, trackSegs_da.cnt, &trackSegs(0), &orig, &size ); orig.x = - tempEndPts(0).pos.x; orig.y = - tempEndPts(0).pos.y; MoveSegs( trackSegs_da.cnt, &trackSegs(0), orig ); for ( ep=0; epcustomInfo = xx->customInfo; #endif f = OpenCustom("a"); if (f && to) { oldLocale = SaveLocale("C"); rc &= fprintf( f, "TURNOUT %s \"%s\"\n", curScaleName, PutTitle(to->title) )>0; #ifdef LATER if ( to->customInfo ) rc &= fprintf( f, "\tU %s\n", to->customInfo )>0; #endif rc &= WriteCompoundPathsEndPtsSegs( f, path, trackSegs_da.cnt, &trackSegs(0), tempEndPts_da.cnt, &tempEndPts(0) ); } if ( groupReplace ) { UndoStart( _("Group Tracks"), "group" ); orig.x = - orig.x; orig.y = - orig.y; for ( ep=0; eptrack ) { trk = GetTrkEndTrk( endPtP->track, endPtP->index ); epN = GetEndPtConnectedToMe( trk, endPtP->track ); DrawEndPt( &mainD, endPtP->track, endPtP->index, wDrawColorWhite ); DrawEndPt( &mainD, trk, epN, wDrawColorWhite ); DisconnectTracks( trk, epN, endPtP->track, endPtP->index ); } endPtP->pos.x += orig.x; endPtP->pos.y += orig.y; } trk = NULL; while ( TrackIterate( &trk ) ) { if ( GetTrkSelected( trk ) ) { DrawTrack( trk, &mainD, wDrawColorWhite ); UndoDelete( trk ); trackCount--; } } trk = NewCompound( T_TURNOUT, 0, orig, 0.0, to->title, tempEndPts_da.cnt, &tempEndPts(0), pathLen, (char *)path, trackSegs_da.cnt, &trackSegs(0) ); SetTrkVisible( trk, TRUE ); SetTrkVisible( trk, TRUE ); for ( ep=0; ep0; #ifdef LATER if ( to->customInfo ) rc &= fprintf( f, "\tU %s\n", to->customInfo )>0; #endif rc &= WriteSegs( f, tempSegs_da.cnt, &tempSegs(0) ); } if ( groupReplace ) { UndoStart( _("Group Tracks"), "group" ); trk = NULL; while ( TrackIterate( &trk ) ) { if ( GetTrkSelected( trk ) ) { DrawTrack( trk, &mainD, wDrawColorWhite ); UndoDelete( trk ); trackCount--; } } orig.x = - orig.x; orig.y = - orig.y; trk = NewCompound( T_STRUCTURE, 0, orig, 0.0, groupTitle, 0, NULL, 0, "", tempSegs_da.cnt, &tempSegs(0) ); SetTrkVisible( trk, TRUE ); DrawNewTrack( trk ); EnableCommands(); } } if (f) fclose(f); RestoreLocale(oldLocale); DoChangeNotification( CHANGE_PARAMS ); wHide( groupW ); wDrawDelayUpdate( mainD.d, FALSE ); groupDesc[0] = '\0'; groupPartno[0] = '\0'; } EXPORT void DoGroup( void ) { track_p trk = NULL; struct extraData *xx; TRKTYP_T trkType; xx = NULL; groupSegCnt = 0; groupCompoundCount = 0; while ( TrackIterate( &trk ) ) { if ( GetTrkSelected( trk ) ) { trkType = GetTrkType(trk); if ( trkType == T_TURNOUT || trkType == T_STRUCTURE ) { xx = GetTrkExtraData(trk); groupSegCnt += xx->segCnt; GroupCopyTitle( xtitle(xx) ); } else groupSegCnt += 1; } } if ( groupSegCnt <= 0 ) { ErrorMessage( MSG_NO_SELECTED_TRK ); return; } sprintf( groupTitle, "%s\t%s\t%s", groupManuf, groupDesc, groupPartno ); if ( log_group < 0 ) log_group = LogFindIndex( "group" ); if ( !groupW ) { ParamRegister( &groupPG ); groupW = ParamCreateDialog( &groupPG, MakeWindowTitle(_("Group Objects")), _("Ok"), GroupOk, wHide, TRUE, NULL, F_BLOCK, NULL ); groupD.dpi = mainD.dpi; } ParamLoadControls( &groupPG ); wShow( groupW ); }