여러 개의 Node로 연결된 곡선이나 도형을 분할하는 것은 모든 꼭지점을 잘라내고 다시 Bezier 곡선을 생성하는 방식으로 쉽게 분할이 됩니다. 이는 이미 결합 순서, 위치 정보를 모두 가지고 있기 때문에 단순히 잘라내는 작업일 뿐이라 특별한 순서가 필요없습니다만, 여러 개의 직선이나 곡선을 1개 도형으로 연결하는 것은 일정한 순서로 작업이 필요합니다.
만약, 도형을 선택한 순서가 정확하게 연결하려는 순서와 같고, 1번 도형의 시작점→끝점 → 2번 도형의 시작점→끝점 → 3번 도형의 시작점→끝점 ... 순서로 연결하는 것이라면 그냥 Node 배열을 단순히 결합해주면 됩니다만, 슬라이드에 임의로 만들어놓은 도형을 선택할 때, 각 도형의 시작점이 어디인지, 마우스 드래그로 도형을 여러개 선택했을 때 도형 선택 순서는 어떻게 되는지 눈에 보이지 않기 때문에 작업자 입장에서는 도형을 연결하기 위한 수작업이 더 많이 들어가게 됩니다.
따라서, 작업자가 연결하고자 하는 여러 개의 도형을 연결하고자 하는 모양대로 대략적으로 배치해둔 상태에서 자동으로 가장 인접한 도형을 찾아 연결해주는 매크로를 작성하는 것이 작업 편의성을 높일 수 있습니다. 이러한 작업을 위해 아래와 같은 순서로 매크로를 작성합니다.
1개 도형의 Node 정보는 1~N번까지 각 점들의 위치 정보로 구성되어있으며, 1번부터 N번의 점을 연결하는 방식입니다. 그러나, 도형 사이를 연결하기 위해 거리를 계산하는 데에는 시작점과 끝점의 정보만 필요할 뿐 중간점들의 정보는 필요하지 않습니다. Node 배열에서 시작점을 S, 끝점을 F라고 했을 때, (S1, F1), (S2, F2), ... 좌표만 먼저 추출해준 후, 이들의 거리를 구해줍니다.
위의 그림을 기준으로 설명드리면, 우선 1번 도형의 (S1, F1)를 기준으로, 각 도형의 시작점과 끝점의 거리를 계산해줍니다. 가장 가까운 점은 S1-S4입니다. 따라서, 1번 도형은 F1에서 시작해서 S1으로 끝나는 곡선으로 바꿔주어야 합니다. (나중에 전체 node를 결합할 때, 1번 도형의 node는 역순으로 결합해주어야 합니다.)
1번과 가장 가까운 도형은 선택순으로는 4번 도형(Sh4)이지만, 연결 순서는 2번째로 설정해주어야 합니다. 4번 도형은 시작점 S4가 1번 도형과 연결되도록 했으므로, Node 연결 순서는 정방향으로 결합해주도록 설정해주고, 끝점인 F4와 다른 도형의 거리를 다시 계산해줍니다.
4번 도형의 끝점(F4)과 가장 가까운 2번 도형의 끝점(F2)을 찾아서, 2번 도형을 3번째 연결 순서로 설정하고, Node 연결방향은 역순이며, 다시 S2와 가까운 도형을 찾아주도록 합니다.
이러한 방법으로 각 도형의 연결 순서와 연결 방향이 지정되고 나면, 각 도형의 Node 좌표를 1개 배열로 만든 후 곡선을 생성해주고, 원본 도형은 삭제합니다.
Public Type typeSortNode
Nodes() As Single
PS() As Single
PF() As Single
LinkStart As Boolean
End Type
'-----------------------------
Function Sh_UnGroup(iSh() As Shape, oSh() As Shape, Optional iNum As Long = 0)
Dim i As Long, n As Long, tSL As ShapeRange, tSh() As Shape
'도형이 그룹화된 경우, 개개의 도형으로 분리해주기 위한 함수입니다. 재귀함수이며, 도형이 그룹화되어있으면 자기함수를 다시 호출해서 계속 Ungroup으로 그룹해제하여 단일 도형으로 분리해줍니다.
If iSh(i).Type = msoGroup Then
Set tSL = iSh(i).Ungroup
ReDim tSh(1 To tSL.Count)
For j = 1 To tSL.Count
Set tSh(j) = tSL(j)
Next
Sh_UnGroup tSh, oSh, iNum
Else
iNum = iNum + 1
ReDim Preserve oSh(1 To iNum)
Set oSh(iNum) = iSh(i)
End If
Next
End Function
'-----------------------------
Function Sh_SortNode(iSh() As Shape) As typeSortNode()
Dim tSN() As typeSortNode, n1 As Long, n2 As Long
Dim tSNTemp As typeSortNode, tP() As Single, tV(1 To 2) As Single, i As Long, j As Long, n As Long
Dim tMin As Single, tDir As Boolean, tDir2 As Boolean, tVal As Single
n1 = LBound(iSh)
n2 = UBound(iSh)
ReDim tSN(n1 To n2)
For i = n1 To n2
With tSN(i)
Set .Sh = iSh(i)
.PS = .Sh.Nodes(1).Points
.PF = .Sh.Nodes(.Sh.Nodes.Count).Points
.LinkStart = True
End With
Next
tV(1) = tSN(n1).PF(1, 1) - tSN(n1 + 1).PS(1, 1)
tV(2) = tSN(n1).PF(1, 2) - tSN(n1 + 1).PS(1, 2)
tMin = tV(1) ^ 2 + tV(2) ^ 2
For j = n1 + 1 To n2
tV(1) = tSN(n1).PS(1, 1) - tSN(j).PS(1, 1): tV(2) = tSN(n1).PS(1, 2) - tSN(j).PS(1, 2): tVal = tV(1) ^ 2 + tV(2) ^ 2
If tMin > tVal Then tMin = tVal: n = j: tDir = False: tDir2 = True
tV(1) = tSN(n1).PS(1, 1) - tSN(j).PF(1, 1): tV(2) = tSN(n1).PS(1, 2) - tSN(j).PF(1, 2): tVal = tV(1) ^ 2 + tV(2) ^ 2
If tMin > tVal Then tMin = tVal: n = j: tDir = False: tDir2 = False
tV(1) = tSN(n1).PF(1, 1) - tSN(j).PS(1, 1): tV(2) = tSN(n1).PF(1, 2) - tSN(j).PS(1, 2): tVal = tV(1) ^ 2 + tV(2) ^ 2
If tMin > tVal Then tMin = tVal: n = j: tDir = True: tDir2 = True
tV(1) = tSN(n1).PF(1, 1) - tSN(j).PF(1, 1): tV(2) = tSN(n1).PF(1, 2) - tSN(j).PF(1, 2): tVal = tV(1) ^ 2 + tV(2) ^ 2
If tMin > tVal Then tMin = tVal: n = j: tDir = True: tDir2 = False
Next
tSNTemp = tSN(2): tSN(2) = tSN(n): tSN(n) = tSNTemp
tSN(2).LinkStart = tDir2
If tSN(i - 1).LinkStart Then tPEnd = tSN(i - 1).PF Else tPEnd = tSN(i - 1).PS
tV(1) = tSN(i).PS(1, 1) - tPEnd(1, 1): tV(2) = tSN(i).PS(1, 2) - tPEnd(1, 2): tMin = tV(1) ^ 2 + tV(2) ^ 2
n = i: tDir = True
tV(1) = tSN(i).PF(1, 1) - tPEnd(1, 1): tV(2) = tSN(i).PF(1, 2) - tPEnd(1, 2): tVal = tV(1) ^ 2 + tV(2) ^ 2
If tMin > tVal Then tMin = tVal: tDir = False
For j = i + 1 To n
tV(1) = tSN(j).PS(1, 1) - tPEnd(1, 1): tV(2) = tSN(j).PS(1, 2) - tPEnd(1, 2): tMin = tV(1) ^ 2 + tV(2) ^ 2
If tMin > tVal Then tMin = tVal: n = j: tDir = True
tV(1) = tSN(j).PF(1, 1) - tPEnd(1, 1): tV(2) = tSN(j).PF(1, 2) - tPEnd(1, 2): tMin = tV(1) ^ 2 + tV(2) ^ 2
If tMin > tVal Then tMin = tVal: n = j: tDir = False
Next
tSNTemp = tSN(i): tSN(i) = tSN(n): tSN(n) = tSNTemp
tSN(i).LinkStart = tDir
Next
Sh_SortNode = tSN
End Function
'-----------------------------
Function Sh_MergeLines(iSh() As Shape, Optional iAddLines As Boolean = False) As Shape
Dim tSN() As typeSortNode, tUG() As Shape, tNode() As Single, tP() As Single, tP1() As Single, tP2() As Single, tPX() As Single, tPY() As Single, i As Long, j As Long, n As Long, tV() As Single
Sh_UnGroup iSh, tUG, 0
For i = LBound(tUG) To UBound(tUG)
tNode = GetVertices(tUG(i), False)
tUG(i).Delete
Set tUG(i) = ActiveSlide.Shapes.AddCurve(tNode)
Next
tSN = Sh_SortNode(tUG)
n = 0
For i = LBound(tSN) To UBound(tSN)
If i > LBound(tSN) Then
If tSN(i - 1).LinkStart Then tP1 = tSN(i - 1).PF Else tP1 = tSN(i - 1).PS
If tSN(i).LinkStart Then tP2 = tSN(i).PS Else tP2 = tSN(i).PF
If iAddLines Then
n = n - 1: ReDim Preserve tPX(1 To n): ReDim Preserve tPY(1 To n)
Else
n = n + 2: ReDim Preserve tPX(1 To n): ReDim Preserve tPY(1 To n)
tPX(n - 1) = (tP1(1, 1) + 2 * tP2(1, 1)) / 3: tPY(n - 1) = (tP1(1, 2) + 2 * tP2(1, 2)) / 3
tPX(n) = (2 * tP1(1, 1) + tP2(1, 1)) / 3: tPY(n) = (2 * tP1(1, 2) + tP2(1, 2)) / 3
End If
Else
With tSN(i)
.Sh.Left = .Sh.Left + tP1(1, 1) - tP2(1, 1): .Sh.Top = .Sh.Top + tP1(1, 2) - tP2(1, 2)
tV(1) = tP1(1, 1) - tP2(1, 1): tV(2) = tP1(1, 2) - tP2(1, 2)
.PS(1, 1) = .PS(1, 1) + tV(1): .PS(1, 2) = .PS(1, 2) + tV(2)
.PF(1, 1) = .PF(1, 1) + tV(1): .PF(1, 2) = .PF(1, 2) + tV(2)
End With
n = n - 1: ReDim Preserve tPX(1 To n): ReDim Preserve tPY(1 To n)
End If
End If
'연결할 도형의 좌표를 읽어와서, X, Y 좌표를 1차원 배열로 만들어 계속 확장해서 결합해줍니다.
If tSN(i).LinkStart Then
For j = 1 To UBound(tP, 1)
n = n + 1: ReDim Preserve tPX(1 To n): ReDim Preserve tPY(1 To n)
tPX(n) = tP(j, 1): tPY(n) = tP(j, 2)
Next
Else
For j = UBound(tP, 1) To 1 Step -1
n = n + 1: ReDim Preserve tPX(1 To n): ReDim Preserve tPY(1 To n)
tPX(n) = tP(j, 1): tPY(n) = tP(j, 2)
Next
End If
Next
ReDim tNode(1 To n, 1 To 2)
For i = 1 To n: tNode(i, 1) = tPX(i): tNode(i, 2) = tPY(i): Next
Set Sh_MergeLines = ActiveSlide.Shapes.AddCurve(tNode)
For i = LBound(tUG) To UBound(tUG): tUG(i).Delete: Next
End Function
'-----------------------------
Sub 도형생성_선병합()
On Error GoTo ErrorHandler
Dim tSR As ShapeRange, tSh() As Shape, i As Long, tMSG
'Powerpoint에서 실행하기 위한 Sub 프로시저입니다.
tMSG = MsgBox("각 라인의 시작과 끝점을 연결하시겠습니까?" & vbCrLf & _
" -Yes : 각 도형의 끝점 맞춤 (도형 위치 이동)" & vbCrLf & _
" -No : 라인 사이를 선으로 연결 (각 도형 위치 유지)" & vbCrLf & _
" -Cancel : 작업 취소", vbYesNoCancel, "작업 확인")
If tMSG = vbCancel Then Exit Sub
Set tSR = SelectedShapeRange(False)
ReDim tSh(1 To tSR.Count)
For i = 1 To tSR.Count: Set tSh(i) = tSR(i): Next
Sh_MergeLines(tSh, tMSG = vbNo).Select msoTrue
ErrorHandler:
End Sub