SmartBreakApart - макрос для раскомбинирования очень сложных объектов.

  • Автор темы Автор темы _MBK_
  • Дата начала Дата начала
Статус
Закрыто для дальнейших ответов.

_MBK_

Пикирующий бомбардировщик
Топикстартер
15 лет на форуме
Сообщения
33 765
Реакции
11 041
Недавно тут обсуждалась проблема превышения допустимого количества узлов на кривых с последующим слетом объектов при печати в PS. Такое очень часто бывает при переводе больших порций текста в кривые, импорта созданных в других программах EPS и PDF, а так же трассированных изображений. Сейчас наткнулся на замечательный макрос, который помогает разобраться с этой проблемой - раскомбинирует сложные кривые на набор из простых объектов, который при простой доводке напильником (глобальной замене цвета), выглядит как исходное изображение. Автор утверждает, что работает в 99% случаев.
Код:
Option Explicit

Sub smartBreakApart()

Dim s As Shape, sr As ShapeRange, shs As Shape
Dim sr2 As New ShapeRange, sr3 As ShapeRange
Dim x As Double, y As Double
Dim nodecount As Long, tempDis As Double

   On Error GoTo smartBreakApart_Error

If ActiveSelection.Shapes.count = 0 Then Exit Sub

Optimization = True
EventsEnabled = False
ActiveDocument.BeginCommandGroup "smart break"

ActiveSelection.UngroupAll
Set sr2 = ActiveSelection.BreakApartEx
Set sr = OrderBySize(sr2)

tempDis = 0.005

For Each s In sr
    s.OrderToFront
    s.Fill.ApplyUniformFill CreateRGBColor(64, 32, 32)
    nodecount = 1
    s.Curve.Nodes(nodecount).GetPosition x, y
    
If 1 = 2 Then
1001:
    If nodecount <= s.Curve.Nodes.count Then
        s.Curve.Nodes(nodecount).GetPosition x, y
    Else
        GoTo 1002:
    End If
End If
    
    If s.IsOnShape(x + tempDis, y) = cdrInsideShape And s.IsOnShape(x + tempDis, y) <> cdrOnMarginOfShape Then
        x = x + tempDis
        
    ElseIf s.IsOnShape(x - tempDis, y) = cdrInsideShape And s.IsOnShape(x - tempDis, y) <> cdrOnMarginOfShape Then
        x = x - tempDis
        
    ElseIf s.IsOnShape(x, y + tempDis) = cdrInsideShape And s.IsOnShape(x, y + tempDis) <> cdrOnMarginOfShape Then
        y = y + tempDis
        
    ElseIf s.IsOnShape(x, y - tempDis) = cdrInsideShape And s.IsOnShape(x, y - tempDis) <> cdrOnMarginOfShape Then
        y = y - tempDis
        
    ElseIf s.IsOnShape(x - tempDis, y - tempDis) = cdrInsideShape And s.IsOnShape(x - tempDis, y - tempDis) <> cdrOnMarginOfShape Then
        y = y - tempDis: x = x - tempDis

    ElseIf s.IsOnShape(x + tempDis, y + tempDis) = cdrInsideShape And s.IsOnShape(x + tempDis, y + tempDis) <> cdrOnMarginOfShape Then
        y = y + tempDis: x = x + tempDis
        
    ElseIf s.IsOnShape(x - tempDis, y + tempDis) = cdrInsideShape And s.IsOnShape(x - tempDis, y + tempDis) <> cdrInsideShape Then
        y = y + tempDis: x = x - tempDis
        
    ElseIf s.IsOnShape(x + tempDis, y - tempDis) = cdrInsideShape And s.IsOnShape(x + tempDis, y - tempDis) <> cdrOnMarginOfShape Then
        y = y - tempDis:  x = x + tempDis
        
    Else
        nodecount = nodecount + 1
        's.Fill.ApplyUniformFill CreateRGBColor(255, 0, 0) 'RED - testing
        GoTo 1001:
    End If
1002:
    Set shs = ActivePage.SelectShapesAtPoint(x, y, False, tempDis / 2) 'notice!!! tempdis /2
    If Not IsOdd(shs.Shapes.count) Then s.Fill.ApplyUniformFill CreateRGBColor(255, 255, 121)
    sr2.Add s
Next s

ActiveDocument.EndCommandGroup
Optimization = False
EventsEnabled = True
ActiveWindow.Refresh

   On Error GoTo 0
   Exit Sub

smartBreakApart_Error:
    ActiveDocument.EndCommandGroup
    Optimization = False
    EventsEnabled = True
    ActiveWindow.Refresh
    MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure smartBreakApart of Module newSmartBreakApart"
    
End Sub

Private Function IsOdd(i As Long) As Boolean
    IsOdd = (i Mod 2) <> 0
End Function

Private Function OrderBySize(sr As ShapeRange) As ShapeRange
    Dim srSorted As New ShapeRange
    Dim s As Shape, i As Integer
    Dim t As Variant, j As Integer, y As Integer
    Dim iUpper As Integer, Condition1 As Boolean
    ReDim ShapesAndSizes(sr.count - 1, 1) As Double 'Create an Array to hold area and staticID
    
    'Add shape data to array
    For i = 1 To sr.count
        ShapesAndSizes(i - 1, 0) = Round(sr(i).SizeWidth * sr(i).SizeHeight, 3) 'Area of the shape
        ShapesAndSizes(i - 1, 1) = sr(i).StaticID 'Static ID of current shape
    Next i
    
    'A very simple sort
    For i = LBound(ShapesAndSizes, 1) To UBound(ShapesAndSizes, 1) - 1
        For j = LBound(ShapesAndSizes, 1) To UBound(ShapesAndSizes, 1) - 1
            Condition1 = ShapesAndSizes(j, 0) <= ShapesAndSizes(j + 1, 0)
            If Condition1 Then
                For y = LBound(ShapesAndSizes, 2) To UBound(ShapesAndSizes, 2)
                    t = ShapesAndSizes(j, y)
                    ShapesAndSizes(j, y) = ShapesAndSizes(j + 1, y)
                    ShapesAndSizes(j + 1, y) = t
                Next y
            End If
        Next
    Next
    
    'Create a ShapeRange from the sorted array
    For i = 0 To sr.count - 1
        srSorted.Add ActivePage.FindShape(StaticID:=ShapesAndSizes(i, 1))
    Next i

    Set OrderBySize = srSorted 'Return the new sorted shaperange
End Function
Источник: http://www.gdgmacros.com/helpful_vba_code_details.php?codeID=11
 
Ответ: SmartBreakApart - макрос для раскомбинирования очень сложных объектов.

Читаю, не могу въехать в алгоритм, вроде бы проверяется расположение объектов друг относительно друга и т.п. А как разбивка-то происходит?

Наверное, я бы проще сделала - все сложные объекты по несколько раз дублировала, у дубликатов отсекала лишние узлы (последовательно: например, в первом дубликате остаются узлы номер 1-10, во втором 11-20 и т.д.), исходный объект удаляла б. Вроде так короче, чем тут?

И это все если самый простой код не работал бы -



PHP:
iE = ActiveLayer.Shapes.Count
ReDim arr(iE) 'tak kak nomera objektov pri razjedinenii sdvigayutsya, nado ih zapomnit'
For i = 1 To iE
    Set arr(i) = ActiveLayer.Shapes(i)
Next i
For i = 1 To iE
        Set brk1 = arr(i).BreakApartEx
Next i
 
Ответ: SmartBreakApart - макрос для раскомбинирования очень сложных объектов.

Уважаемая ekali, вы не совсем правильно поняли назначение данного макроса. Попробую объяснить, зачем вообще нужно разбиение комбинированных объектов.
Возьмем, для примера, объект, представляющий собой переведенную в кривые букву "О"
Не по теме:
(вообще, честно говоря, изначально я хотел, для наглядности, взять слово, содержащее эту букву, но потом подумал, что девушке приводить такой пример будет не очень удобно :)

Этот объект представляет собой два комбинированных замкнутых контура один в другом: 2 subpath 20 nodes. При обычном раскомбинировании получаем 2 объекта примерно по 10 узлов в каждом, но дырка в букве оказалось залитой. Чтобы исправить данную проблему, достаточно просто перетащить объект, представляющий собой дырку, на передний план и залить цветом фона. Этим, по сути и занимается данный макрос - раскомбинирует объект на множество отдельных контуров при сохранении общего внешнего вида. На достаточно сложных объектах (скажем, оттрассированный битмап или переведенный в кривые абзац текста) это сильно помогает.
 
Ответ: SmartBreakApart - макрос для раскомбинирования очень сложных объектов.

Да, я нашла эту тему на англоязычном форуме, они там решают проблему дырок (ну или формы-матрешки, Russian dolls). Попробовала написать свой вариант, без сортировки по размерам (она по-любому кажется тут лишней). Может, какие-то нюансы не учла, не знаю. У меня на шрифтах с кружочками внутри (матрешки глубиной до 3) вроде срабатывает, но поскольку растров нет, то всерьез обпробовать не на чем. Алгоритм ручной работы представляю себе довольно схематично, в основном по чужим обсуждениям.

Там просто для каждой формы запоминаем, внутри каких форм она находится, а в следующем блоке по списку вычитаем ее из них. При этом заливка сохраняется для четных матрешек и убирается для нечетных. Больше ничего. Они же идут от точек на листе, все формы в точке сравнивают... это выглядит намного сложнее, в чем резон, я не уловила. У них их еще припуски учтены...

Код:
Option Base 1
Option Explicit
Sub Holess()
Dim s As Shape, s1 As Shape, minus As Shape
Dim n As NodeDim i As Long, iE As Long, i1 As Long, i2 As Long, iNode As Long, iType As Integer, iIsIn As Integer, iID As Long, iEnd As Long
Dim IsInShapes(), IsIn(), Arr(), IsInShapesID(), ArrShapes()
Dim Farbe As String, Stri As String, Mess As String
Dim ifIsIn As Boolean
Dim brk As ShapeRange
Dim x As Double, y As Double



iE = ActiveLayer.Shapes.Count
ReDim ArrShapes(iE) 'tak kak nomera objektov pri razjedinenii sdvigayutsya, nado ih zapomnit'
For i = 1 To iE
    Set ArrShapes(i) = ActiveLayer.Shapes(i)
Next i

For i = 1 To iE
    Set s = ArrShapes(i)
    iType = s.Type
    If iType <> 3 Then s.ConvertToCurves
    Set brk = s.BreakApartEx
Next i
iE = ActiveLayer.Shapes.Count
ReDim IsIn(iE)
ReDim IsInShapes(iE)
ReDim IsInShapesID(iE)
ReDim ArrShapes(iE)
For i = 1 To iE
    IsInShapes(i) = Array()
    IsInShapesID(i) = Array()
    ActiveLayer.Shapes(i).Name = i
Next i

For i = 1 To iE
    Set s = ActiveLayer.Shapes(i)
    For i1 = i + 1 To iE
        Set s1 = ActiveLayer.Shapes(i1)
        ifIsIn = True
        For Each n In s1.Curve.Nodes
            n.GetPosition x, y
            If s.IsOnShape(x, y) < 1 Then ifIsIn = False
        Next n
        If ifIsIn = True Then
            Stri = IsIn(i1)
            If Len(Stri) = 0 Then Stri = "0"
            iIsIn = CInt(Stri) + 1
            IsIn(i1) = iIsIn
           
           Arr = IsInShapes(i1)
            ReDim Preserve Arr(iIsIn)
            Set Arr(iIsIn) = s
            IsInShapes(i1) = Arr
                        
           Arr = IsInShapesID(i1)
            ReDim Preserve Arr(iIsIn)
            Arr(iIsIn) = i 
           IsInShapesID(i1) = Arr
        End If
    Next i1Next i
For i = 1 To iE
    iIsIn = IsIn(i)
        If iIsIn > 0 Then
          Set s = ActiveLayer.Shapes.FindShapes(i)(1) 
           If CInt(iIsIn / 2) <> iIsIn / 2 Then
               s.Fill.ApplyNoFill
            End If
            For i1 = 1 To iIsIn
            '-------------------------------------
              iID = IsInShapesID(i)(i1)
              Set s1 = ActiveLayer.Shapes.FindShapes(iID)(1)

              If s1 Is Nothing Then
              Else
                 Set minus = s.Trim(s1, True, True)
                 s1.Delete
                 iEnd = ActiveLayer.Shapes.Count
                 For i2 = 1 To iEnd
                     If Len(ActiveLayer.Shapes(i2).Name) = 0 Then
                         ActiveLayer.Shapes(i2).Name = i
                     End If
                 Next i2
             End If 
       Next i1
     End If
Next i

End Sub
 
Ответ: SmartBreakApart - макрос для раскомбинирования очень сложных объектов.

Вроде, выглядит неплохо, надо погонять.
Хотя...
А зачем вычитать-то? Весь смысл ведь именно в закрашивании раскомбинированных объектов?
 
Ответ: SmartBreakApart - макрос для раскомбинирования очень сложных объектов.

А зачем вычитать-то?
Так там тема изначально называется "How to detect holes in combined curve objects?". У ТС проблема была именно в том, что и Вы написали: при разъединении формы-контейнеры заливались сплошь, и формы-содержимое-контейнеров исчезало под заливкой. Он именно и пишет, что при наложении нескольких фигур не может задать, какой объект пустой, а какой залитый.

Как можно это решить? Если мы вырезаем все внутренности, то это выглядит так - контейнер залит цветом, внутри пустая площадь (пробел), дальше внутри - залитая фигура, потом опять пробел и т.д. То есть каждая форма вырезается из всех контейнеров, в которых она лежит. Ну сейчас понимаю, там получаются лишние операции (например, дырка в контейнере уже вырезана, для предыдущей формы, а макрос опять пытается вырезать, для более маленькой, уже в пустоте)

Это, например, если я режу на фрезере и мне не надо трогать фон. А другой вариант - там лазерщик пишет, ему надо, чтобы каждая фигура была залита, даже если это цвет фона. Но там принцип не меняется, просто нужно вместо ApplyNoFill определиться с цветом заливки.

Ну это, конечно, общие рассуждения, а чтобы корректно написать под конкретные задачи, надо их понимать. А то я вот не понимаю, зачем там целый блок кода под учет припусков. И почему припуск именно 0.005. Единицы не указаны. Может, ему файлы такие заказчик носит, неаккуратные, шаляй-валяй, и он боится формы потерять при обработке. Или это Корел сдвигает на такую погрешность в работе с растром. Без комментариев трудно.
 
Ответ: SmartBreakApart - макрос для раскомбинирования очень сложных объектов.

Как я понял, данный макрос использует несколько другой подход - вместо перекрашивания лежащих друг внутри друга объектов, вырезает под них дырки? В принципе, можно и так, хотя, количество узлов на один объект чуть больше получается, чем при первом подходе, к тому же, у них зачем-то лишние объекты остаются. Такой подход, кстати, можно применять для частично пересекающихся контуров (случай которых не обрабатывается ни в этом ни в исходном макросах) Такие контура можно отслеживать и разбивать по правилу:
A xor B = (A-B) + (B-A) , но данный случай не особо актуален для курвленых текстов и трассированных объектов.
А то я вот не понимаю, зачем там целый блок кода под учет припусков. И почему припуск именно 0.005.
Это баг в самом кореловском скриптинге, некорректно обрабатывается равенство чисел с плавающей точкой, в частности, координат. Мне самому частенько приходилось заменять выражения типа X1=X2 на Abs(X1-X2)<0.001 :-(
 
Статус
Закрыто для дальнейших ответов.