pythonlispwin32comautocadautocad-plugin

How to add hatches in AutoCAD so that they overlap each other?


I am using the Python language and the win32com.client library to add hatches to a drawing.

Here is the code that implements the creation of hatchings:

import win32com.client
import pythoncom

acad = win32com.client.Dispatch("AutoCAD.Application")
acadModel = acad.ActiveDocument.ModelSpace 

def APoint(x, y, z = 0):
    return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (x, y, z))

def ADouble(xyz):
    return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (xyz))

def variants(object):
    return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_DISPATCH, (object))

out_loop = [acadModel.AddPolyline(ADouble([0,0,0,1000,0,0,1000,1000,0,0,1000,0,0,0,0]))]

hatch = acadModel.AddHatch(0, "HEX", True)
hatch.PatternScale = 20
hatch.AppendOuterLoop(variants(out_loop))
hatch.Evaluate()

hatch = acadModel.AddHatch(0, "ANSI31", True)
hatch.PatternScale = 10
hatch.AppendOuterLoop(variants(out_loop))
hatch.Evaluate()

The task is to find a solution to overlapping hatches on each other, which can be implemented using python. The desired result is shown on the right side of the picture.

pic

I can do this task manually in autocad using the Explode command to the first hatch and then add a second hatch. But I need to implement this using Python.

I was also advised a method where WipeOut and SuperHatch are used, but I still haven't figured out how to implement this in code. (https://stackoverflow.com/a/77905739/23389658)

Maybe it need to modify the autocad scripts, with lisp or Autolisp. I don't know.


Solution

  • Unfortunately I haven't found a solution using ActiveX (COM), mainly because Hatch doesn't have an explode method. I suggest using PyRx - Python wrapper over Object ARX.

    In a nutshell:

    import traceback
    from itertools import pairwise
    from typing import Iterable, Tuple
    
    import numpy as np
    import shapely
    from pyrx_imp import Db, Ge
    from shapely.ops import linemerge
    
    
    def PyRxCmd_doit():
        try:
            
            # HEX hatch
            hex_hatch = Db.Hatch()
            hex_hatch.setDatabaseDefaults()
            hex_hatch.setPattern(Db.HatchPatternType.kPreDefined, "HEX")
    
            outer_edges = (
                ((0.0, 0.0), (100.0, 0.0)),
                ((100.0, 0.0), (100.0, 100.0)),
                ((100.0, 100.0), (0.0, 100.0)),
                ((0.0, 100.0), (0.0, 0.0)),
            )
    
            outer_segments = [
                Ge.LineSeg2d(Ge.Point2d(p1), Ge.Point2d(p2)) for p1, p2 in outer_edges
            ]
    
            hex_hatch.appendLoopEdges(
                Db.HatchLoopType.kExternal,
                outer_segments,
                [Db.HatchEdgeType.kLine for _ in outer_segments],
            )
            hex_hatch.evaluateHatch()
    
            # explode hatch and get single lines
            hex_lines_ents = hex_hatch.explode()
            hex_lines = [Db.Line.cast(ent) for ent in hex_lines_ents]
    
            # connect individual lines in a "polyline" so that you can 
            # create loops for cutting the target hatch; round the coordinates 
            # so that the lines connect properly
            hex_lines_points = [
                (
                    np.round(i.startPoint().toTuple()[:2], 6),
                    np.round(i.endPoint().toTuple()[:2], 6),
                )
                for i in hex_lines
            ]
    
            plines = get_plines(hex_lines_points)  # get_plines below
    
            # Target hatch
            hatch = Db.Hatch()
            hatch.setPattern(Db.HatchPatternType.kPreDefined, "ANSI31")
    
            hatch.appendLoopEdges(
                Db.HatchLoopType.kExternal,
                outer_segments,
                [Db.HatchEdgeType.kLine for _ in outer_edges],
            )
    
            for pline in plines:  # hex plines
                
                # create a list of segments that make up the polylines of one hexagon
                line_segments = [
                    Ge.LineSeg2d(Ge.Point2d(p1), Ge.Point2d(p2))
                    for p1, p2 in pairwise(pline)
                ]
                hatch.appendLoopEdges(
                    Db.HatchLoopType.kDefault,
                    line_segments,
                    [Db.HatchEdgeType.kLine for _ in line_segments],
                )
    
            hatch.evaluateHatch()
    
            db = Db.HostApplicationServices().workingDatabase()
            model = Db.BlockTableRecord(db.modelSpaceId(), Db.OpenMode.kForWrite)
            model.appendAcDbEntity(hatch)  # add hatch to the model
            model.appendAcDbEntities(hex_lines)  # add hexagon lines if needed too
            model.close()
            
        except Exception:
            traceback.print_exc()
    
    
    Point_T = Tuple[float, float]
    
    def get_plines(
        line_points: Iterable[Tuple[Point_T, Point_T]]
    ) -> Iterable[Iterable[Point_T]]:
        line_strings = linemerge(line_points)
        if isinstance(line_strings, shapely.LineString):
            line_strings = (line_strings,)
        elif isinstance(line_strings, shapely.MultiLineString):
            pass
        else:
            raise TypeError
        for line_string in line_strings.geoms:
            ch = line_string.convex_hull
            if isinstance(ch, shapely.Polygon):
                yield ch.exterior.coords
    

    Problems to solve

    The algorithm requires refinement at the hatch outer edge; the beginning and end of a polyline that is part of a hexagon are connected straight ahead instead of along the outer hatch outline; I suggest playing with shapely - create a HEX hatch whose outline is larger than the target hatch, and cut the polylines (get_plines() above) of this hatch from the polygon (shapely.Polygon) of the target hatch.


    PyRx

    PyRx allows user interaction, for the user to specify the external hatch outline, use the Ed module (from pyrx_imp import Ed)

    You can also use ActiveX (Autocad Object Model) with PyRx, check it out:

    from pyrx_impx import Ax
    
    doc = Ax.getDbx()  # same as acad.ActiveDocument