Пример #1
0
def test_viewer_uninitialized(assert_image_similarity):
    """An uninitialized engine should not crash"""
    engine = Engine()

    engine.scale = 3.0
    engine.origin = (10.0, 20.0)
    engine.view_mode = ViewMode.OUTLINE_COLORFUL
    engine.show_pen_up = True
    engine.show_points = True
    engine.pen_width = 0.5
    engine.pen_opacity = 0.5
    engine.debug = True
    engine.show_rulers = True
    engine.pixel_factor = 2.0
    engine.unit_type = UnitType.IMPERIAL

    engine.zoom(2, 10, 10)
    engine.pan(50, 40)
    engine.render()
    engine.viewport_to_model(30, 30)
    engine.toggle_layer_visibility(10)
    engine.fit_to_viewport()

    # ensure 100% coverage
    doc = vp.Document()
    engine.document = doc
    engine.fit_to_viewport()
Пример #2
0
def test_viewer_engine_properties(assert_image_similarity):
    renderer = ImageRenderer((640, 480))

    doc = vp.Document()
    renderer.engine.document = doc
    assert renderer.engine.document is doc

    renderer.engine.scale = 3.0
    assert renderer.engine.scale == 3.0

    renderer.engine.origin = (10.0, 20.0)
    assert renderer.engine.origin == (10.0, 20.0)

    renderer.engine.view_mode = ViewMode.OUTLINE_COLORFUL
    assert renderer.engine.view_mode == ViewMode.OUTLINE_COLORFUL

    renderer.engine.show_pen_up = True
    assert renderer.engine.show_pen_up

    renderer.engine.show_points = True
    assert renderer.engine.show_points

    renderer.engine.pen_width = 0.5
    assert renderer.engine.pen_width == 0.5

    renderer.engine.pen_opacity = 0.5
    assert renderer.engine.pen_opacity == 0.5

    renderer.engine.debug = True
    assert renderer.engine.debug

    renderer.engine.toggle_layer_visibility(10)
    assert not renderer.engine.layer_visible(10)
def run(args):
    saver = ReproSaver()
    saver.seed()
    R = 150
    phi = 1.6180339
    smaller = R / phi  # golden ratio
    pts = []
    pts.extend(circle_connections(1200, R, np.pi, -np.pi))
    rays = draw_rays(0, 7 * R * phi, 0, np.pi, 30)
    rays.extend(draw_rays(2.5 * R, 7 * R * phi,
                          np.pi / 60,
                          np.pi - np.pi / 60,
                          29))
    rays = shift(rays, np.array([1.7 * R, 0]))
    rays = mask_drawing(rays, circle((0, 0), 1.05 * R, N=50), invert=True)
    pts.extend(rays)
    # pts = mask_drawing(pts, circle((0, 0), R, N=50))
    # pts.extend(circle_connections(230, R, np.pi * 0.3, 0))
    # pts.extend(circle_connections(530, R, np.pi * 0.5, 0))
    # pts.extend(circle_connections(650, R, np.pi * 0.65, np.pi * 0.50))
    # pts.extend(circle_connections(700, R, np.pi * 0.95, np.pi * 0.72))
    # for i in range(1, 18):
    #     smooth_circle = circle_connections(360, R * 1.02**i, np.pi / 180, np.pi / 180)
    #     pts.extend(drop_parts(smooth_circle, space=1, passes=4))
    lines = to_vpype(pts)
    lines.crop(-2.3 * R - smaller, 2.3 * R + smaller, R + smaller, -R - smaller)  # left, bottom, right, top
    pts = from_vpype_lines(lines)
    if not args.nosave:
        saver.add_svg(pts)
    document = vpype.Document(lines)
    # document.extend_page_size(None)
    with open('/tmp/signal_planet.svg', 'w') as fout:
        vpype.write_svg(fout, document)
    vpype_viewer.show(document)
    return 0
Пример #4
0
def run(args):
    saver = ReproSaver('star_results')
    saver.seed()

    if args.rnd:
        description = random_star()
        print(f"description: {description}")
        paths = construct_star(description)
    else:
        N_star = 12
        R = 600
        paths = []
        start_fractions = np.linspace(0.4, 1, 25)
        end_fractions = reversed(start_fractions)
        for start_fraction, end_fraction in zip(start_fractions,
                                                end_fractions):
            start_r = R * start_fraction
            end_r = R * end_fraction
            paths.extend(star_line(N_star, start_r, end_r, 1))

        # vlastovka
        start_fractions = np.geomspace(0.4, 0.65, 20)
        end_fractions = reversed(np.geomspace(0.3, 0.9, 20))
        for start_fraction, end_fraction in zip(start_fractions,
                                                end_fractions):
            start_r = R * start_fraction
            end_r = R * end_fraction
            paths.extend(star_line(N_star, start_r, end_r, 1))

        # stred
        start_fractions = rev_geomspace(0.1, 1, 30)
        end_fractions = start_fractions
        # end_fractions = np.linspace(0.1, 1, 30)
        for start_fraction, end_fraction in zip(start_fractions,
                                                end_fractions):
            start_r = R * start_fraction
            end_r = R * end_fraction
            paths.extend(star_line(N_star, start_r, end_r, 5, mirror=False))

        # stred 2
        start_fractions = rev_geomspace(0.13, 0.7, 20)
        end_fractions = start_fractions
        # end_fractions = np.linspace(0.1, 1, 30)
        for start_fraction, end_fraction in zip(start_fractions,
                                                end_fractions):
            start_r = R * start_fraction
            end_r = R * end_fraction
            paths.extend(star_line(N_star, start_r, end_r, 5, mirror=False))

        paths.extend(star_line(N_star, R, R, 6, mirror=False))

    lines = to_vpype(paths)
    if not args.nosave:
        saver.add_svg(paths)
    document = vpype.Document(lines)
    with open('/tmp/stars.svg', 'w') as fout:
        vpype.write_svg(fout, document)
    if not args.novis:
        vpype_viewer.show(document)
    return 0
Пример #5
0
def big_doc():
    random.seed(0)
    doc = vp.Document()
    doc.add(
        vp.LineCollection([(
            random.uniform(0, 100) + random.uniform(0, 100) * 1j,
            random.uniform(0, 100) + random.uniform(0, 100) * 1j,
        ) for _ in range(1000)]))
    return doc
Пример #6
0
def execute_single_line(pipeline: str, line: vp.LineLike) -> vp.LineCollection:
    """Execute a pipeline on a single line. The pipeline is expected to remain single layer.

    Returns:
        the layer 1's LineCollection
    """
    doc = execute(pipeline, vp.Document(vp.LineCollection([line])))
    assert len(doc.layers) == 1
    return doc.layers[1]
Пример #7
0
def execute(pipeline: str,
            document: Optional[vp.Document] = None) -> vp.Document:
    """Execute a vpype pipeline.

    This function serves as a Python API to vpype's pipeline. It can be used from a regular
    Python script (as opposed to the ``vpype`` CLI which must be used from a console or via
    :func:`os.system`.

    If a :class:`vpype.Document` instance is provided, it will be preloaded in the pipeline
    before the first command executes. The pipeline's content after the last command is
    returned as a :class:`vpype.Document` instance.

    Examples:

        Read a SVG file, optimize it and return the result as a :class:`vpype.Document`
        instance::

            >>> doc = execute("read input.svg linemerge linesimplify linesort")

        Optimize and save a :class:`vpype.Document` instance::

            >>> doc = vp.Document()
            >>> # populate `doc` with some graphics
            >>> execute("linemerge linesimplify linesort write output.svg", doc)

    Args:
        pipeline: vpype pipeline as would be used with ``vpype`` CLI
        document: if provided, is perloaded in the pipeline before the first command executes

    Returns:
        pipeline's content after the last command executes
    """

    if document:

        @cli.command()
        @vp.global_processor
        def vsketchinput(doc):
            doc.extend(document)
            return doc

    out_doc = vp.Document()

    @cli.command()
    @vp.global_processor
    def vsketchoutput(doc):
        out_doc.extend(doc)
        return doc

    args = ("vsketchinput " if document else "") + pipeline + " vsketchoutput"
    cli.main(prog_name="vpype", args=shlex.split(args), standalone_mode=False)
    return out_doc
Пример #8
0
def test_hpgl_flex_no_pagesize(simple_printer_config):
    doc = vp.Document()
    doc.add(vp.LineCollection([(1 + 1j, 2 + 4j)]))
    vp.config_manager.load_config_file(simple_printer_config)
    with pytest.raises(ValueError):
        vp.write_hpgl(
            output=sys.stdout,
            document=doc,
            landscape=False,
            center=False,
            device="simple",
            page_size="simple_flex_portrait",
            velocity=None,
        )
Пример #9
0
def test_layout(runner, args, expected_bounds):
    document = vp.Document()

    @cli.command()
    @vp.global_processor
    def sample(doc: vp.Document):
        nonlocal document
        document = doc

    res = runner.invoke(cli, f"random -n 100 rect 0 0 1cm 1cm layout {args} sample")
    assert res.exit_code == 0
    bounds = document.bounds()
    assert bounds is not None
    for act, exp in zip(bounds, expected_bounds):
        assert act == pytest.approx(exp * CM)
Пример #10
0
def test_hpgl_wrong_ref(simple_printer_config):
    doc = vp.Document()
    doc.add(vp.LineCollection([(1 + 1j, 2 + 4j)]))
    doc.page_size = 10, 15
    vp.config_manager.load_config_file(simple_printer_config)
    with pytest.raises(ValueError):
        vp.write_hpgl(
            output=sys.stdout,
            document=doc,
            landscape=False,
            center=False,
            device="defective",
            page_size="wrong_ref",
            velocity=None,
        )
Пример #11
0
def test_viewer_engine_properties(assert_image_similarity):
    renderer = ImageRenderer((640, 480))

    doc = vp.Document()
    renderer.engine.document = doc
    assert renderer.engine.document is doc

    renderer.engine.scale = 3.0
    assert renderer.engine.scale == 3.0

    renderer.engine.origin = (10.0, 20.0)
    assert renderer.engine.origin == (10.0, 20.0)

    renderer.engine.view_mode = ViewMode.OUTLINE_COLORFUL
    assert renderer.engine.view_mode == ViewMode.OUTLINE_COLORFUL

    renderer.engine.show_pen_up = True
    assert renderer.engine.show_pen_up

    renderer.engine.show_points = True
    assert renderer.engine.show_points

    renderer.engine.pen_width = 0.5
    assert renderer.engine.pen_width == 0.5

    renderer.engine.pen_opacity = 0.5
    assert renderer.engine.pen_opacity == 0.5

    renderer.engine.debug = True
    assert renderer.engine.debug

    renderer.engine.unit_type = UnitType.IMPERIAL
    assert renderer.engine.unit_type == UnitType.IMPERIAL

    renderer.engine.pixel_factor = 2.0
    assert renderer.engine.pixel_factor == 2.0

    renderer.engine.show_rulers = False
    assert not renderer.engine.show_rulers

    assert renderer.engine.scale_spec == DEFAULT_SCALE_SPEC

    renderer.engine.toggle_layer_visibility(10)
    assert not renderer.engine.layer_visible(10)
Пример #12
0
def run(args):
    R = 150
    smaller = R / 1.6180339  # golden ratio
    pts = []
    pts.extend(circle_connections(230, R, np.pi * 0.3, 0))
    pts.extend(circle_connections(530, R, np.pi * 0.5, 0))
    pts.extend(circle_connections(650, R, np.pi * 0.65, np.pi * 0.50))
    pts.extend(circle_connections(700, R, np.pi * 0.95, np.pi * 0.72))
    for i in range(1, 18):
        smooth_circle = circle_connections(360, R * 1.02**i, np.pi / 180,
                                           np.pi / 180)
        pts.extend(drop_parts(smooth_circle, space=1, passes=4))
    lines = to_vpype(pts)
    lines.crop(R - smaller, 0, R, -R)
    document = vpype.Document(lines)
    # document.extend_page_size(None)
    with open('/tmp/circles.svg', 'w') as fout:
        vpype.write_svg(fout, document)
    vpype_viewer.show(document)
    return 0
Пример #13
0
def test_text_block_render(assert_image_similarity, font_name, align,
                           line_spacing, justify):
    doc = vp.Document()
    doc.add(
        vp.text_block(
            LOREM,
            500,
            font_name,
            size=18,
            align=align,
            line_spacing=line_spacing,
            justify=justify,
        ))
    doc[1].append(vp.line(500, -20, 500, 500))
    renderer = ImageRenderer((1024, 1024))
    renderer.engine.document = doc
    renderer.engine.show_rulers = True
    renderer.engine.unit_type = UnitType.PIXELS
    renderer.engine.origin = (-20, -20)
    renderer.engine.scale = 1.8
    assert_image_similarity(renderer.render())
Пример #14
0
def run(args):
    saver = ReproSaver()
    saver.seed()
    if args.cfg is not None:
        saver.load_seeds(args.cfg)

    H, W = 210, 148
    angle_range = 3

    horizontal_lines = []
    for i in range(1000):
        horizontal_lines.append(
            random_line(H,
                        W,
                        min_angle=np.radians(0 - angle_range / 2),
                        max_angle=np.radians(0 + angle_range / 2)))

    vertical_lines = []
    for i in range(1000):
        vertical_lines.append(
            random_line(H,
                        W,
                        min_angle=np.radians(90 - angle_range / 2),
                        max_angle=np.radians(90 + angle_range / 2)))

    cross = draw_cross(H, W)
    horizontal, vertical = draw_cross_parts(H, W)
    margin = 10
    (horizontal, vertical,
     cross) = resize_and_center([horizontal, vertical, cross], H, W, margin,
                                margin, margin, margin)
    horizontal_lines = mask_drawing(horizontal_lines, horizontal)
    vertical_lines = mask_drawing(vertical_lines, vertical)

    horizontal_lines = jitter_length(horizontal_lines, 0, 5)
    vertical_lines = jitter_length(vertical_lines, 0, 5)

    vertical_lines = mask_drawing(vertical_lines, horizontal, invert=True)

    cross_lines = horizontal_lines
    cross_lines.extend(vertical_lines)
    cross_lines.extend(vertical_lines)
    # cross_lines.append(cross)

    bg_lines = []
    for i in range(300):
        bg_lines.append(random_line(0.2 * H, W))

    bg_lines = shift(bg_lines, (0, 0.8 * H))

    bg_lines = mask_drawing(bg_lines, cross, invert=True)

    pts = []
    pts.extend(cross_lines)
    pts = resize_and_center(pts, H, W, margin, margin, margin, margin)
    # pts.extend(bg_lines)

    lines = to_vpype(pts)
    document = vpype.Document(lines)
    vpype_viewer.show(document)

    if not args.nosave:
        saver.add_svg(pts)
    return 0
Пример #15
0
    def effect(self):
        lc = vpype.LineCollection() # create a new array of LineStrings consisting of Points. We convert selected paths to polylines and grab their points
        elementsToWork = [] # we make an array of all collected nodes to get the boundingbox of that array. We need it to place the vpype converted stuff to the correct XY coordinates
          
        applyTransformAvailable = False
        
        # at first we apply external extension
        try:
            sys.path.append("..") # add parent directory to path to allow importing applytransform (vpype extension is encapsulated in sub directory)
            import applytransform
            applyTransformAvailable = True
        except Exception as e:
            # inkex.utils.debug(e)
            inkex.utils.debug("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping this step")

        def flatten(node):
            path = node.path.to_superpath()
            bezier.cspsubdiv(path, self.options.flatness)
            newpath = []
            for subpath in path:
                first = True
                for csp in subpath:
                    cmd = 'L'
                    if first:
                        cmd = 'M'
                    first = False
                    newpath.append([cmd, [csp[1][0], csp[1][1]]])
            node.path = newpath

        # flatten the node's path to linearize, split up the path to it's subpaths (break apart) and add all points to the vpype lines collection
        def convertPath(node, nodes = None):
            if nodes is None:
                nodes = []
            if node.tag == inkex.addNS('path','svg'):
                nodes.append(node)
                if self.options.flattenbezier is True:
                    flatten(node)

                raw = node.path.to_arrays()
                subPaths, prev = [], 0
                for i in range(len(raw)): # Breaks compound paths into simple paths
                    if raw[i][0] == 'M' and i != 0:
                        subPaths.append(raw[prev:i])
                        prev = i
                subPaths.append(raw[prev:])
                for subPath in subPaths:
                    points = []
                    for csp in subPath:
                        if len(csp[1]) > 0: #we need exactly two points per straight line segment
                            points.append(Point(round(csp[1][0], self.options.decimals), round(csp[1][1], self.options.decimals)))
                    if  subPath[-1][0] == 'Z' or subPath[0][1] == subPath[-1][1]:  #check if path is closed by Z or first pont == last point
                        points.append(Point(round(subPath[0][1][0], self.options.decimals), round(subPath[0][1][1], self.options.decimals))) #if closed, we add the first point again
                    lc.append(LineString(points))
                      
            children = node.getchildren()
            if children is not None: 
                for child in children:
                    convertPath(child, nodes)
            return nodes

        doc = None #create a vpype document
        
        '''
        if 'paths' we process paths only. Objects like rectangles or strokes like polygon have to be converted before accessing them
        if 'layers' we can process all layers in the complete document
        '''
        if self.options.input_handling == "paths":
            # getting the bounding box of the current selection. We use to calculate the offset XY from top-left corner of the canvas. This helps us placing back the elements
            input_bbox = None
            if self.options.apply_transformations is True and applyTransformAvailable is True:
                '''
                we need to apply transfoms to the complete document even if there are only some single paths selected. 
                If we apply it to selected nodes only the parent groups still might contain transforms. 
                This messes with the coordinates and creates hardly controllable behaviour
                '''
                applytransform.ApplyTransform().recursiveFuseTransform(self.document.getroot())
            if len(self.svg.selected) == 0:
                elementsToWork = convertPath(self.document.getroot())
                for element in elementsToWork:
                    input_bbox += element.bounding_box()      
            else:
                elementsToWork = None
                for element in self.svg.selected.values():
                    elementsToWork = convertPath(element, elementsToWork)
                #input_bbox = inkex.elements._selected.ElementList.bounding_box(self.svg.selected) # get BoundingBox for selection
                input_bbox = self.svg.selection.bounding_box() # get BoundingBox for selection
            if len(lc) == 0:
                inkex.errormsg('Selection appears to be empty or does not contain any valid svg:path nodes. Try to cast your objects to paths using CTRL + SHIFT + C or strokes to paths using CTRL + ALT+ C')
                return  
            # find the first object in selection which has a style attribute (skips groups and other things which have no style)
            firstElementStyle = None
            for element in elementsToWork:
                if element.attrib.has_key('style'):
                    firstElementStyle = element.get('style')
            doc = vpype.Document(page_size=(input_bbox.width + input_bbox.left, input_bbox.height + input_bbox.top)) #create new vpype document     
            doc.add(lc, layer_id=None) # we add the lineCollection (converted selection) to the vpype document
            
        elif self.options.input_handling == "layers":
            doc = vpype.read_multilayer_svg(self.options.input_file, quantization = self.options.flatness, crop = False, simplify = self.options.simplify, parallel = self.options.parallel, default_width = self.document.getroot().get('width'), default_height = self.document.getroot().get('height'))

            for element in self.document.getroot().xpath("//svg:g", namespaces=inkex.NSS): #all groups/layers
                elementsToWork.append(element)

        tooling_length_before = doc.length()
        traveling_length_before = doc.pen_up_length()
        
        # build and execute the conversion command
        # the following code block is not intended to sum up the commands to build a series (pipe) of commands!
        ##########################################
        
        # Line Sorting
        if self.options.linesort is True:
            command = "linesort "
            if self.options.linesort_no_flip is True:
                command += " --no-flip"

        # Line Merging
        if self.options.linemerge is True:     
            command = "linemerge --tolerance " + str(self.options.linemerge_tolerance)
            if self.options.linemerge_no_flip is True:
                command += " --no-flip"
 
        # Trimming
        if self.options.trim is True:     
            command = "trim " + str(self.options.trim_x_margin) + " " + str(self.options.trim_y_margin)
 
        # Relooping
        if self.options.reloop is True:     
            command = "reloop --tolerance " + str(self.options.reloop_tolerance)
 
        # Multipass
        if self.options.multipass is True:     
            command = "multipass --count " + str(self.options.multipass_count)
 
        # Filter
        if self.options.filter is True:     
            command = "filter --tolerance " + str(self.options.filter_tolerance)
            if self.options.filter_min_length_enabled is True:
                command += " --min-length " + str(self.options.filter_min_length)
            if self.options.filter_max_length_enabled is True:
                command += " --max-length " + str(self.options.filter_max_length)
            if self.options.filter_closed is True and self.options.filter_not_closed is False:
                command += " --closed"
            if self.options.filter_not_closed is True and self.options.filter_closed is False:
                command += " --not-closed"
            if self.options.filter_closed is False and \
                self.options.filter_not_closed is False and \
                self.options.filter_min_length_enabled is False and \
                self.options.filter_max_length_enabled is False:
                inkex.errormsg('No filters to apply. Please select at least one filter.')
                return

        # Plugin Occult
        if self.options.plugin_occult is True:     
            command = "occult --tolerance " + str(self.options.plugin_occult_tolerance)
            if self.options.plugin_occult_keepseparatelayer is True:
                command += " --keep-occulted"

        # Split All
        if self.options.splitall is True:     
            command = " splitall"

        # Free Mode
        if self.options.freemode is True:
            command = ""
            if self.options.freemode_cmd1_enabled is True:
                command += " " + self.options.freemode_cmd1.strip()
            if self.options.freemode_cmd2_enabled is True:
                command += " " + self.options.freemode_cmd2.strip()
            if self.options.freemode_cmd3_enabled is True:
                command += " " + self.options.freemode_cmd3.strip()
            if self.options.freemode_cmd4_enabled is True:
                command += " " + self.options.freemode_cmd4.strip()
            if self.options.freemode_cmd5_enabled is True:
                command += " " + self.options.freemode_cmd5.strip()
            if self.options.freemode_cmd1_enabled is False and \
                self.options.freemode_cmd2_enabled is False and \
                self.options.freemode_cmd3_enabled is False and \
                self.options.freemode_cmd4_enabled is False and \
                self.options.freemode_cmd5_enabled is False:
                inkex.utils.debug("Warning: empty vpype pipeline. With this you are just getting read-write layerset/lineset.")
            else:
                if self.options.freemode_show_cmd is True:
                    inkex.utils.debug("Your command pipe will be the following:")
                    inkex.utils.debug(command)

        # inkex.utils.debug(command)
        try:
            doc = execute(command, doc)
        except Exception as e:
            inkex.utils.debug("Error in vpype:" + str(e))
            return

        ##########################################
        
        tooling_length_after = doc.length()
        traveling_length_after = doc.pen_up_length()        
        if tooling_length_before > 0:
            tooling_length_saving = (1.0 - tooling_length_after / tooling_length_before) * 100.0
        else:
            tooling_length_saving = 0.0            
        if traveling_length_before > 0:
            traveling_length_saving = (1.0 - traveling_length_after / traveling_length_before) * 100.0
        else:
            traveling_length_saving = 0.0  
        if self.options.output_stats is True:
            inkex.utils.debug('Total tooling length before vpype conversion: '   + str('{:0.2f}'.format(tooling_length_before))   + ' mm')
            inkex.utils.debug('Total traveling length before vpype conversion: ' + str('{:0.2f}'.format(traveling_length_before)) + ' mm')
            inkex.utils.debug('Total tooling length after vpype conversion: '    + str('{:0.2f}'.format(tooling_length_after))    + ' mm')
            inkex.utils.debug('Total traveling length after vpype conversion: '  + str('{:0.2f}'.format(traveling_length_after))  + ' mm')
            inkex.utils.debug('Total tooling length optimized: '   + str('{:0.2f}'.format(tooling_length_saving))   + ' %')
            inkex.utils.debug('Total traveling length optimized: ' + str('{:0.2f}'.format(traveling_length_saving)) + ' %')
         
        if tooling_length_after == 0:
            inkex.errormsg('No lines left after vpype conversion. Conversion result is empty. Cannot continue')
            return
         
        # show the vpype document visually
        if self.options.output_show:
            warnings.filterwarnings("ignore") # workaround to suppress annoying DeprecationWarning
            # vpype_viewer.show(doc, view_mode=ViewMode.PREVIEW, show_pen_up=self.options.output_trajectories, show_points=self.options.output_show_points, pen_width=0.1, pen_opacity=1.0, argv=None)
            vpype_viewer.show(doc, view_mode=ViewMode.PREVIEW, show_pen_up=self.options.output_trajectories, show_points=self.options.output_show_points, argv=None) # https://vpype.readthedocs.io/en/stable/api/vpype_viewer.ViewMode.html
            warnings.filterwarnings("default") # reset warning filter
            exit(0) #we leave the code loop because we only want to preview. We don't want to import the geometry
          
        # save the vpype document to new svg file and close it afterwards
        output_file = self.options.input_file + ".vpype.svg"
        output_fileIO = open(output_file, "w", encoding="utf-8")
        #vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer', single_path = True)       
        vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer')       
        #vpype.write_svg(output_fileIO, doc, page_size=(self.svg.unittouu(self.document.getroot().get('width')), self.svg.unittouu(self.document.getroot().get('height'))), center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer')       
        output_fileIO.close()
        
        # convert vpype polylines/lines/polygons to regular paths again. We need to use "--with-gui" to respond to "WARNING: ignoring verb FileSave - GUI required for this verb."
        if self.options.strokes_to_paths is True:
            cli_output = inkscape(output_file, "--with-gui", actions="EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit")
            if len(cli_output) > 0:
                self.debug(_("Inkscape returned the following output when trying to run the vpype object to path back-conversion:"))
                self.debug(cli_output)
        
        # this does not work because line, polyline and polygon have no base class to execute replace_with
        #if self.options.strokes_to_paths is True:
        #    for lineLayer in lineLayers:
        #        for element in lineLayer:
        #                element.replace_with(element.to_path_element())
               
        # parse the SVG file
        try:
            stream = open(output_file, 'r')
        except FileNotFoundError as e:
            inkex.utils.debug("There was no SVG output generated by vpype. Cannot continue")
            exit(1)
        p = etree.XMLParser(huge_tree=True)
        import_doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True))
        stream.close()
          
        # handle pen_up trajectories (travel lines)
        trajectoriesLayer = import_doc.getroot().xpath("//svg:g[@id='pen_up_trajectories']", namespaces=inkex.NSS)
        if self.options.output_trajectories is True:
            if len(trajectoriesLayer) > 0:
                trajectoriesLayer[0].set('style', 'stroke:#0000ff;stroke-width:{:0.2f}px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none'.format(self.options.trajectories_stroke_width))
                trajectoriesLayer[0].attrib.pop('stroke') # remove unneccesary stroke attribute
                trajectoriesLayer[0].attrib.pop('fill') # remove unneccesary fill attribute
        else:
            if len(trajectoriesLayer) > 0:
                trajectoriesLayer[0].delete()
   
        lineLayers = import_doc.getroot().xpath("//svg:g[not(@id='pen_up_trajectories')]", namespaces=inkex.NSS) #all layer except the pen_up trajectories layer
        if self.options.use_style_of_first_element is True and self.options.input_handling == "paths" and firstElementStyle is not None:
            
            # if we remove the fill property and use "Use style of first element in layer" the conversion will just crash with an unknown reason
            #declarations = firstElementStyle.split(';')
            #for i, decl in enumerate(declarations):
            #    parts = decl.split(':', 2)
            #    if len(parts) == 2:
            #        (prop, val) = parts
            #        prop = prop.strip().lower()
            #        #if prop == 'fill':
            #        #   declarations[i] = prop + ':none'   
            for lineLayer in lineLayers:
                #lineLayer.set('style', ';'.join(declarations))
                lineLayer.set('style', firstElementStyle)
                lineLayer.attrib.pop('stroke') # remove unneccesary stroke attribute
                lineLayer.attrib.pop('fill') # remove unneccesary fill attribute

        else:
            for lineLayer in lineLayers:          
                if lineLayer.attrib.has_key('stroke'):
                    color = lineLayer.get('stroke')
                    lineLayer.set('style', 'stroke:' + color + ';stroke-width:{:0.2f}px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none'.format(self.options.lines_stroke_width))
                    lineLayer.attrib.pop('stroke') # remove unneccesary stroke attribute
                    lineLayer.attrib.pop('fill') # remove unneccesary fill attribute

        import_viewBox = import_doc.getroot().get('viewBox').split(" ")
        self_viewBox = self.document.getroot().get('viewBox').split(" ")
        scaleX = self.svg.unittouu(self_viewBox[2]) / self.svg.unittouu(import_viewBox[2])
        scaleY = self.svg.unittouu(self_viewBox[3]) / self.svg.unittouu(import_viewBox[3])

        for element in import_doc.getroot().iter("{http://www.w3.org/2000/svg}g"):
            self.document.getroot().append(element)
            if self.options.input_handling == "layers":
                element.set('transform', 'scale(' + str(scaleX) + ',' + str(scaleY) + ')') #imported groups need to be transformed. Or they have wrong size. Reason: different viewBox sizes/units in namedview definitions
                if self.options.apply_transformations is True and applyTransformAvailable is True: #we apply the transforms directly after adding them
                    applytransform.ApplyTransform().recursiveFuseTransform(element) 

        # Delete the temporary file again because we do not need it anymore
        if os.path.exists(output_file):
            os.remove(output_file)
            
        # Remove selection objects to do a real replace with new objects from vpype document
        if self.options.keep_objects is False:
            for element in elementsToWork:
                element.delete()
def to_vpype_document(layers):
    document = vpype.Document()
    for layer_name, layer in layers.items():
        lines = to_vpype_lines(layer)
        document.add(lines, layer_name_to_id(layer_name))
    return document
Пример #17
0
    def effect(self):
        #get input file and copy it to some new temporary directory
        inputfile = self.options.inputfile
        if not os.path.exists(inputfile):
            inkex.utils.debug(
                "The input file does not exist. Please select a *.dxf or *.dwg file and try again."
            )
            exit(1)
        temp_input_dir = os.path.join(tempfile.gettempdir(), "dxfdwg_input")
        shutil.rmtree(temp_input_dir, ignore_errors=True
                      )  #remove the input directory before doing new job
        if not os.path.exists(temp_input_dir):
            os.mkdir(temp_input_dir)  #recreate blank dir
        shutil.copy2(
            inputfile, os.path.join(
                temp_input_dir,
                Path(inputfile).name))  # complete target filename given

        #Prepapre output conversion
        outputfilebase = os.path.splitext(os.path.basename(inputfile))[0]
        inputfile_ending = os.path.splitext(os.path.basename(inputfile))[1]
        temp_output_dir = os.path.join(tempfile.gettempdir(), "dxfdwg_output")
        shutil.rmtree(temp_output_dir, ignore_errors=True
                      )  #remove the output directory before doing new job
        if not os.path.exists(temp_output_dir):
            os.mkdir(temp_output_dir)

        #Prepare some more options for proceeding
        autocad_version = self.options.oda_outputformat.split("_")[0]
        autocad_format = self.options.oda_outputformat.split("_")[1]
        self.options.oda_audit_repair = "1" if self.options.oda_audit_repair else "0"  #overwrite string bool with int value
        entityspace = []
        if self.options.allentities or self.options.THREE_DFACE:
            entityspace.append("3DFACE")
        if self.options.allentities or self.options.ARC:
            entityspace.append("ARC")
        if self.options.allentities or self.options.BLOCK:
            entityspace.append("BLOCK")
        if self.options.allentities or self.options.CIRCLE:
            entityspace.append("CIRCLE")
        if self.options.allentities or self.options.ELLIPSE:
            entityspace.append("ELLIPSE")
        if self.options.allentities or self.options.LINE:
            entityspace.append("LINE")
        if self.options.allentities or self.options.LWPOLYLINE:
            entityspace.append("LWPOLYLINE")
        if self.options.allentities or self.options.POINT:
            entityspace.append("POINT")
        if self.options.allentities or self.options.POLYLINE:
            entityspace.append("POLYLINE")
        if self.options.allentities or self.options.POP_TRAFO:
            entityspace.append("POP_TRAFO")
        if self.options.allentities or self.options.SEQEND:
            entityspace.append("SEQEND")
        if self.options.allentities or self.options.SOLID:
            entityspace.append("SOLID")
        if self.options.allentities or self.options.SPLINE:
            entityspace.append("SPLINE")
        if self.options.allentities or self.options.TABLE:
            entityspace.append("TABLE")
        if self.options.allentities or self.options.VERTEX:
            entityspace.append("VERTEX")
        if self.options.allentities or self.options.VIEWPORT:
            entityspace.append("VIEWPORT")

        if self.options.allentities or self.options.THREE_DSOLID:
            entityspace.append("3DSOLID")
        if self.options.allentities or self.options.ATTRIB:
            entityspace.append("ATTRIB")
        if self.options.allentities or self.options.BODY:
            entityspace.append("BODY")
        if self.options.allentities or self.options.ARC_DIMENSION:
            entityspace.append("ARC_DIMENSION")
        if self.options.allentities or self.options.HATCH:
            entityspace.append("HATCH")
        if self.options.allentities or self.options.IMAGE:
            entityspace.append("IMAGE")
        if self.options.allentities or self.options.INSERT:
            entityspace.append("INSERT")
        if self.options.allentities or self.options.MESH:
            entityspace.append("MESH")
        if self.options.allentities or self.options.MTEXT:
            entityspace.append("MTEXT")
        if self.options.allentities or self.options.RAY:
            entityspace.append("RAY")
        if self.options.allentities or self.options.REGION:
            entityspace.append("REGION")
        if self.options.allentities or self.options.SHAPE:
            entityspace.append("SHAPE")
        if self.options.allentities or self.options.SURFACE:
            entityspace.append("SURFACE")
        if self.options.allentities or self.options.TRACE:
            entityspace.append("TRACE")
        if self.options.allentities or self.options.UNDERLAY:
            entityspace.append("UNDERLAY")
        if self.options.allentities or self.options.XLINE:
            entityspace.append("XLINE")

        #ODA to ezdxf mapping
        oda_ezdxf_mapping = []
        oda_ezdxf_mapping.append(
            ["ACAD9", "R12", "AC1004"]
        )  #this mapping is not supported directly. so we use the lowest possible which is R12
        oda_ezdxf_mapping.append(
            ["ACAD10", "R12", "AC1006"]
        )  #this mapping is not supported directly. so we use the lowest possible which is R12
        oda_ezdxf_mapping.append(["ACAD12", "R12", "AC1009"])
        oda_ezdxf_mapping.append(
            ["ACAD13", "R2000", "AC1012"]
        )  #R13 was overwritten by R2000 which points to AC1015 instead of AC1014 (see documentation)
        oda_ezdxf_mapping.append(
            ["ACAD14", "R2000", "AC1014"]
        )  #R14 was overwritten by R2000 which points to AC1015 instead of AC1014 (see documentation)
        oda_ezdxf_mapping.append(["ACAD2000", "R2000", "AC1015"])
        oda_ezdxf_mapping.append(["ACAD2004", "R2004", "AC1018"])
        oda_ezdxf_mapping.append(["ACAD2007", "R2007", "AC1021"])
        oda_ezdxf_mapping.append(["ACAD2010", "R2010", "AC1024"])
        oda_ezdxf_mapping.append(["ACAD2013", "R2013", "AC1027"])
        oda_ezdxf_mapping.append(["ACAD2018", "R2018", "AC1032"])

        ezdxf_autocad_format = None
        for oe in oda_ezdxf_mapping:
            if oe[0] == autocad_version:
                ezdxf_autocad_format = oe[1]
                break
        if ezdxf_autocad_format is None:
            inkex.errormsg("ezdxf conversion format version unknown")

        #Prepare DXF and SVG paths
        dxf_file = os.path.join(temp_output_dir, outputfilebase + ".dxf")
        svg_file = os.path.join(temp_output_dir, outputfilebase + ".svg")

        # Run ODA File Converter
        if self.options.oda_skip_dxf_to_dxf == False or inputfile_ending == ".dwg":
            # Executable test (check for proper configuration by checking mime type. Should return octet stream for a binary executable)
            if os.name == "nt" and "application/octet-stream" not in str(
                    MimeTypes().guess_type(
                        urllib.pathname2url(self.options.oda_fileconverter))):
                inkex.utils.debug(
                    "You selected to use ODA File Converter but it is not configured properly. Check for installation and path location or select 'Skip conversion from DXF to DXF'. You can download ODA Converter from 'https://www.opendesign.com/guestfiles/oda_file_converter'. You need to install it in order to use it."
                )
                exit(1)
            elif os.path.isfile(self.options.oda_fileconverter) == False:
                inkex.utils.debug(
                    "You selected to use ODA File Converter but it is not configured properly. Check for installation and path location or select 'Skip conversion from DXF to DXF'. You can download ODA Converter from 'https://www.opendesign.com/guestfiles/oda_file_converter'. You need to install it in order to use it."
                )
                exit(1)
            else:
                # Build and run ODA File Converter command
                oda_cmd = [
                    self.options.oda_fileconverter, temp_input_dir,
                    temp_output_dir, autocad_version, autocad_format, "0",
                    self.options.oda_audit_repair
                ]
                if os.name == 'nt' and self.options.oda_hidewindow:
                    info = subprocess.STARTUPINFO(
                    )  #hide the ODA File Converter window because it is annoying (does not work for Linux :-()
                    info.dwFlags = 1
                    info.wShowWindow = 0
                    proc = subprocess.Popen(oda_cmd,
                                            startupinfo=info,
                                            shell=False,
                                            stdout=PIPE,
                                            stderr=PIPE)
                else:
                    proc = subprocess.Popen(oda_cmd,
                                            shell=False,
                                            stdout=PIPE,
                                            stderr=PIPE)
                stdout, stderr = proc.communicate()
                if proc.returncode != 0 or (len(stderr) > 0 and
                                            stderr != b"Quit (core dumped)\n"):
                    inkex.utils.debug("ODAFileConverter failed: %d %s %s" %
                                      (proc.returncode, stdout, stderr))
                    if os.name != 'nt':
                        inkex.utils.debug(
                            "If the error message above contains a warning about wrong/missing Qt version please install the required version. You can get the installer from 'https://download.qt.io/archive/qt/'. Sadly you will need to create a free account to install. After installation please configure the shell script '/usr/bin/ODAFileConverter' to add a preceding line with content similar to 'LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/Qt5.14.2/5.14.2/gcc_64/lib/'."
                        )
                    exit(1)
            # check if ODA converted successfully. This is the case if no error file was created
            oda_errorfile = os.path.join(temp_output_dir,
                                         Path(inputfile).name + ".err")
            if os.path.exists(oda_errorfile):
                inkex.utils.debug(
                    "ODA File Converter failed to process the file. Cannot continue DXF/DWG import. The error message is:"
                )
                errormessage = open(oda_errorfile, 'r')
                errorlines = errormessage.readlines()
                for errorline in errorlines:
                    inkex.utils.debug(errorline.strip())
                errormessage.close()
                exit(1)

        # Do some movings/copies of skipped or processed DXF
        if self.options.oda_skip_dxf_to_dxf:  #if true we need to move the file to simulate "processed"
            shutil.move(os.path.join(temp_input_dir,
                                     Path(inputfile).name),
                        os.path.join(temp_output_dir,
                                     Path(inputfile).name))

        if self.options.oda_keepconverted_dxf:
            shutil.copy2(
                dxf_file,
                os.path.join(os.path.dirname(inputfile), outputfilebase +
                             "_oda.dxf"))  # complete target filename given

        # Preprocessing DXF to DXF (entity filter) by using ezdxf the first time
        if self.options.ezdxf_preprocessing:
            # uniconverter does not handle all entities. we parse the file to exlude stuff which lets uniconverter fail
            dxf = ezdxf.readfile(dxf_file)
            modelspace = dxf.modelspace()
            allowed_entities = []
            # supported entities by UniConverter- impossible: MTEXT TEXT INSERT and a lot of others
            query_string = str(entityspace)[1:-1].replace("'",
                                                          "").replace(",", "")
            if query_string != "":
                for e in modelspace.query(query_string):
                    allowed_entities.append(e)
            #inkex.utils.debug(ezdxf_autocad_format)
            #inkex.utils.debug(self.options.ezdxf_output_version)
            if self.options.ezdxf_output_version == "SAME":
                doc = ezdxf.new(ezdxf_autocad_format)
            else:
                doc = ezdxf.new(
                    self.options.ezdxf_output_version
                )  #use the string values from inx file. Required to match the values from ezdxf library. See Python reference
            msp = doc.modelspace()
            for e in allowed_entities:
                msp.add_foreign_entity(e)
            doc.saveas(dxf_file)
            if self.options.ezdfx_keep_preprocessed:
                shutil.copy2(
                    dxf_file,
                    os.path.join(
                        os.path.dirname(inputfile), outputfilebase +
                        "_ezdxf.dxf"))  # complete target filename given

        # Make SVG from DXF
        if self.options.dxf_to_svg_parser == "sk1":
            if os.name != "nt":
                inkex.utils.debug(
                    "You selected sk1 UniConvertor but you are not running on a Windows platform. On Linux uniconverter 1.1.X can be installed using the now obsolete Python 2.7, but it will not run correctly because you finally will fail at installing liblcms1-dev library on newer systems. That leads to uncompilable sk1libs package. Unfortunately sk1 UniConvertor 2.X does not support dxf format. So please use another DXF to SVG converter."
                )
                exit(1)
            sk1_command_ending = os.path.splitext(
                os.path.splitext(
                    os.path.basename(self.options.sk1_uniconverter))[1])[0]
            if sk1_command_ending != ".cmd":
                inkex.utils.debug(
                    "You selected sk1 UniConverter but it was not configured properly. Check the path to the executable."
                )
                exit(1)
            uniconverter_cmd = [
                self.options.sk1_uniconverter, dxf_file, svg_file
            ]
            #inkex.utils.debug(uniconverter_cmd)
            proc = subprocess.Popen(uniconverter_cmd,
                                    shell=False,
                                    stdout=PIPE,
                                    stderr=PIPE)
            stdout, stderr = proc.communicate()
            if proc.returncode != 0:
                inkex.errormsg("UniConverter failed: %d %s %s" %
                               (proc.returncode, stdout, stderr))
                if self.options.opendironerror:
                    self.openExplorer(temp_output_dir)

        elif self.options.dxf_to_svg_parser == "bjnortier":
            if which("node") is None:
                inkex.utils.debug(
                    "NodeJS executable not found on path. Please check your installation."
                )
                exit(1)
            else:
                bjnortier_cmd = [
                    "node",
                    os.path.join("node_modules", "dxf", "lib", "cli.js"),
                    dxf_file, svg_file
                ]
                #inkex.utils.debug(bjnortier_cmd)
                proc = subprocess.Popen(bjnortier_cmd,
                                        shell=False,
                                        stdout=PIPE,
                                        stderr=PIPE)
                stdout, stderr = proc.communicate()
                if proc.returncode != 0:
                    inkex.errormsg(
                        "node.js DXF to SVG conversion failed: %d %s %s" %
                        (proc.returncode, stdout, stderr))
                    if self.options.opendironerror:
                        self.openExplorer(temp_output_dir)

        elif self.options.dxf_to_svg_parser == "kabeja":
            wd = os.path.join(os.getcwd(), "kabeja")
            #inkex.utils.debug(wd)
            proc = subprocess.Popen(
                "java -jar launcher.jar -nogui -pipeline svg " + dxf_file +
                " " + svg_file,
                cwd=wd,
                shell=True,
                stdout=PIPE,
                stderr=PIPE)
            stdout, stderr = proc.communicate()
            if proc.returncode != 0:
                inkex.errormsg("kabeja failed: %d %s %s" %
                               (proc.returncode, stdout, stderr))
                if self.options.opendironerror:
                    self.openExplorer(temp_output_dir)

        elif self.options.dxf_to_svg_parser == "vpype_dxf":
            try:
                from inkex.command import inkscape
                import vpype
                from vpype_cli import execute
            except Exception as e:
                inkex.errormsg(
                    "Error importing vpype. Did you properly install the vpype and vpype-dxf python modules?"
                )
                exit(1)
            doc = vpype.Document()  #create new vpype document
            command = "dread  --quantization " + str(
                self.options.vpype_quantization)
            if self.options.vpype_simplify is True:
                command += " --simplify"
            if self.options.vpype_parallel is True:
                command += " --parallel"
            #command += " '" + inputfile + "'"
            command += " '" + dxf_file + "'"

            #inkex.errormsg(command)
            doc = execute(command, doc)
            if doc.length() == 0:
                inkex.errormsg(
                    'No lines left after vpype conversion. Conversion result is empty. Cannot continue'
                )
                exit(1)
            # save the vpype document to new svg file and close it afterwards
            output_fileIO = open(svg_file, "w", encoding="utf-8")
            vpype.write_svg(output_fileIO,
                            doc,
                            page_size=None,
                            center=False,
                            source_string='',
                            layer_label_format='%d',
                            show_pen_up=False,
                            color_mode='layer')
            output_fileIO.close()
            # convert vpype polylines/lines/polygons to regular paths again. We need to use "--with-gui" to respond to "WARNING: ignoring verb FileSave - GUI required for this verb."
            cli_output = inkscape(
                svg_file,
                "--with-gui",
                actions=
                "EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit"
            )
            if len(cli_output) > 0:
                self.debug(
                    _("Inkscape returned the following output when trying to run the vpype object to path back-conversion:"
                      ))
                self.debug(cli_output)

        elif self.options.dxf_to_svg_parser == "ezdxf":
            try:
                doc = ezdxf.readfile(dxf_file)
                msp = doc.modelspace()
                #for e in msp: #loop through entities
                #    inkex.errormsg(e)
                #doc.header['$DIMSCALE'] = 0.2 does not apply to the plot :-(
                #inkex.utils.debug(doc.header['$DIMSCALE'])
                #inkex.utils.debug(doc.header['$MEASUREMENT'])
                auditor = doc.audit(
                )  #audit & repair DXF document before rendering
                # The auditor.errors attribute stores severe errors, which *may* raise exceptions when rendering.
                if len(auditor.errors) == 0:
                    fig = plt.figure()
                    ax = plt.axes([0., 0., 1., 1.], xticks=[], yticks=[])
                    #ax = plt.axes([0., 0., 1., 1.], frameon=False, xticks=[], yticks=[])
                    ax.patches = []
                    #plt.axis('off')
                    plt.margins(0, 0)
                    plt.gca().xaxis.set_major_locator(plt.NullLocator())
                    plt.gca().yaxis.set_major_locator(plt.NullLocator())
                    plt.subplots_adjust(top=1,
                                        bottom=0,
                                        right=1,
                                        left=0,
                                        hspace=0,
                                        wspace=0)
                    out = MatplotlibBackend(fig.add_axes(ax))
                    Frontend(RenderContext(doc),
                             out).draw_layout(msp, finalize=True)
                    #plt.show()
                    #fig.savefig(os.path.join(temp_output_dir, outputfilebase + ".png"), dpi=300)
                    fig.savefig(
                        svg_file
                    )  #see https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.savefig.html
            except IOError:
                inkex.errormsg("Not a DXF file or a generic I/O error.")
                exit(1)
            except ezdxf.DXFStructureError:
                inkex.errormsg("Invalid or corrupted DXF file.")
                exit(1)

        elif self.options.dxf_to_svg_parser == "legacy":
            inkex.utils.debug(
                "The selected legacy DXF to SVG parser is not supported by this extension yet. Use File > Import > *.dxf. This calls the \"dxf_input.inx\" extension."
            )
            exit(1)
        else:
            inkex.utils.debug("undefined parser")
            exit(1)

        # Write the generated SVG into InkScape's canvas
        try:
            stream = open(svg_file, 'r')
        except FileNotFoundError as e:
            inkex.utils.debug(
                "There was no SVG output generated. Cannot continue")
            exit(1)
        p = etree.XMLParser(huge_tree=True)
        doc = etree.parse(stream,
                          parser=etree.XMLParser(huge_tree=True)).getroot()
        stream.close()
        doc.set(
            'id',
            self.svg.get_unique_id("dxf_dwg_import-" +
                                   self.options.dxf_to_svg_parser + "-"))
        self.document.getroot().append(doc)

        #get children of the doc and move them one group above - we don't do this for bjnortier tool because this has different structure which we don't want to disturb
        if self.options.dxf_to_svg_parser == "sk1":
            elements = []
            emptyGroup = None
            for firstGroup in doc.getchildren():
                emptyGroup = firstGroup
                for element in firstGroup.getchildren():
                    elements.append(element)
                #break #only one cycle - could be bad idea or not
            for element in elements:
                doc.set('id', self.svg.get_unique_id('dxf_dwg_import'))
                doc.insert(doc.index(firstGroup), element)

            if emptyGroup is not None:
                emptyGroup.getparent().remove(emptyGroup)

        #empty the following vals because they destroy the size aspects of the import / make viewbox looking wrong
        if self.options.dxf_to_svg_parser == "bjnortier" or self.options.dxf_to_svg_parser == "kabeja":
            doc.set('width', '')
            doc.set('height', '')
            doc.set('viewBox', '')
            doc.getchildren()[0].set('transform', '')

        #adjust viewport and width/height to have the import at the center of the canvas
        if self.options.resizetoimport:
            bbox = inkex.elements._selected.ElementList.bounding_box(
                doc.getchildren()[0])
            if bbox is not None:
                root = self.svg.getElement('//svg:svg')
                offset = self.svg.unittouu(
                    str(self.options.extraborder) +
                    self.options.extraborder_units)
                root.set(
                    'viewBox', '%f %f %f %f' %
                    (bbox.left - offset, bbox.top - offset,
                     bbox.width + 2 * offset, bbox.height + 2 * offset))
                root.set('width', bbox.width + 2 * offset)
                root.set('height', bbox.height + 2 * offset)