added clip-out extension
This commit is contained in:
parent
f635098935
commit
da1c1322c4
49
extensions/fablabchemnitz/clip_out/clip_out.inx
Normal file
49
extensions/fablabchemnitz/clip_out/clip_out.inx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Clip Out</name>
|
||||||
|
<id>fablabchemnitz.de.clip_out</id>
|
||||||
|
<param name="notebook_main" type="notebook">
|
||||||
|
<page name="settings_page" gui-text="Settings">
|
||||||
|
<param name="clip_type_inverse" type="bool" gui-text="Inverse" gui-description="Make holes :)">false</param>
|
||||||
|
<param name="shape_unit_fix" type="bool" gui-text="Shape Unit Fix" gui-description="Unit correction for rect / ellipse / circle">true</param>
|
||||||
|
<separator/>
|
||||||
|
<param name="output_set" type="optiongroup" appearance="radio" gui-text="Output">
|
||||||
|
<option value="master_only">Master Only</option>
|
||||||
|
<option value="separate">Separate</option>
|
||||||
|
<option value="master_and_separate">Master & Separate</option>
|
||||||
|
</param>
|
||||||
|
<param name="canvas_to_selection" type="optiongroup" appearance="radio" gui-text="Resize to Selection" gui-description="Crop Canvas To Resulting Clip">
|
||||||
|
<option value="true">Yes</option>
|
||||||
|
<option value="false">No</option>
|
||||||
|
</param>
|
||||||
|
<param name="png_dpi" type="int" min="10" max="99999999" gui-text="PNG dpi">96</param>
|
||||||
|
<separator/>
|
||||||
|
<param type="path" name="save_path" gui-text="File Save Path" mode="folder">None Selected</param>
|
||||||
|
</page>
|
||||||
|
<page name="about_page" gui-text="About">
|
||||||
|
<label xml:space="preserve">
|
||||||
|
Clip Out - Export multiple clipped png images using paths / shapes and a background image
|
||||||
|
</label>
|
||||||
|
<label appearance="url">https://gitlab.com/inklinea/quick-export</label>
|
||||||
|
<label appearance="url">https://inkscape.org/~inklinea/</label>
|
||||||
|
<label xml:space="preserve">
|
||||||
|
Requires Inkscape 1.1+ -->
|
||||||
|
The image to be clipped must be the last selected.
|
||||||
|
An easy way to do this, is select all then shift & left click the image twice to make it the last selected.
|
||||||
|
It does require that you have saved our svg file
|
||||||
|
at least once before using ( will not work on an unsaved svg )
|
||||||
|
</label>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect needs-live-preview="false">
|
||||||
|
<object-type>path</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Import/Export/Transfer"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">clip_out.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
318
extensions/fablabchemnitz/clip_out/clip_out.py
Normal file
318
extensions/fablabchemnitz/clip_out/clip_out.py
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright (C) [2021] [Matt Cottam], [mpcottam@raincloud.co.uk]
|
||||||
|
#
|
||||||
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# #############################################################################
|
||||||
|
# Clip Out - Export multiple clipped images using paths / shapes and a background image
|
||||||
|
# After setting the options in the main dialogue
|
||||||
|
# Assign a shortcut in Inkscape Edit>Preferences>Interface>Keyboard to org.inkscape.inklinea.clip_out.noprefs
|
||||||
|
# For shortcut triggered quick export
|
||||||
|
# It does require that you have saved
|
||||||
|
# Your svg file at least once before using ( will not work on an unsaved svg )
|
||||||
|
# Requires Inkscape 1.1+ -->
|
||||||
|
# #############################################################################
|
||||||
|
import random
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from inkex import command
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
import tempfile, shutil, os
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
conversions = {
|
||||||
|
'in': 96.0,
|
||||||
|
'pt': 1.3333333333333333,
|
||||||
|
'px': 1.0,
|
||||||
|
'mm': 3.779527559055118,
|
||||||
|
'cm': 37.79527559055118,
|
||||||
|
'm': 3779.527559055118,
|
||||||
|
'km': 3779527.559055118,
|
||||||
|
'Q': 0.94488188976378,
|
||||||
|
'pc': 16.0,
|
||||||
|
'yd': 3456.0,
|
||||||
|
'ft': 1152.0,
|
||||||
|
'': 1.0, # Default px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def make_temp_svg(self):
|
||||||
|
temp_svg_file = tempfile.NamedTemporaryFile(mode='r+', delete='false', suffix='.svg')
|
||||||
|
# Write the contents of the updated svg to a tempfile to use with command line
|
||||||
|
my_svg_string = self.svg.root.tostring().decode("utf-8")
|
||||||
|
temp_svg_file.write(my_svg_string)
|
||||||
|
return temp_svg_file
|
||||||
|
|
||||||
|
|
||||||
|
def inkscape_command_line_export(self, my_temp_svg_filename_path, my_export_path, my_options, export_png_actions):
|
||||||
|
if Path(my_export_path).is_dir():
|
||||||
|
inkex.command.inkscape(my_temp_svg_filename_path, my_options, export_png_actions)
|
||||||
|
else:
|
||||||
|
inkex.errormsg('Please Select An Export Folder')
|
||||||
|
|
||||||
|
|
||||||
|
def make_image_frame(self, background_image):
|
||||||
|
found_units = self.svg.unit
|
||||||
|
|
||||||
|
unit_conversion_factor = conversions[found_units]
|
||||||
|
|
||||||
|
bbox_x = background_image.bounding_box().left / unit_conversion_factor
|
||||||
|
bbox_y = background_image.bounding_box().top / unit_conversion_factor
|
||||||
|
bbox_width = background_image.bounding_box().width / unit_conversion_factor
|
||||||
|
bbox_height = background_image.bounding_box().height / unit_conversion_factor
|
||||||
|
|
||||||
|
top_left = str(f'{bbox_x} {bbox_y}')
|
||||||
|
top_right_x = str(bbox_x + bbox_width)
|
||||||
|
top_right_y = str(bbox_y)
|
||||||
|
bottom_right_x = str(bbox_x + bbox_width)
|
||||||
|
bottom_right_y = str(bbox_y + bbox_height)
|
||||||
|
bottom_left_x = str(bbox_x)
|
||||||
|
bottom_left_y = str(bbox_y + bbox_height)
|
||||||
|
|
||||||
|
rect_path = f'M {top_left} L {top_right_x} {top_right_y} L {bottom_right_x} {bottom_right_y} L {bottom_left_x} {bottom_left_y} Z'
|
||||||
|
|
||||||
|
path_id = 'inverse_clip_frame' + str(random.randrange(10000, 99999))
|
||||||
|
|
||||||
|
parent = self.svg.get_current_layer()
|
||||||
|
|
||||||
|
rect_path_object = etree.SubElement(parent, inkex.addNS('path', 'svg'))
|
||||||
|
|
||||||
|
rect_path_object.attrib['id'] = path_id
|
||||||
|
|
||||||
|
rect_path_object.attrib['d'] = rect_path
|
||||||
|
|
||||||
|
rect_path_object.style['stroke'] = 'black'
|
||||||
|
rect_path_object.style['stroke-width'] = '1px'
|
||||||
|
|
||||||
|
return rect_path_object.get_id()
|
||||||
|
|
||||||
|
|
||||||
|
def command_line_call(self):
|
||||||
|
# current date and time to time stamp
|
||||||
|
timestamp = datetime.today().replace(microsecond=0)
|
||||||
|
timestamp_suffix = str(timestamp.strftime('%Y-%m-%d-%H-%M-%S'))
|
||||||
|
|
||||||
|
# Get export path
|
||||||
|
my_export_path = self.options.save_path
|
||||||
|
|
||||||
|
# Get name of currently open Inkscape file
|
||||||
|
my_filename = self.svg.name
|
||||||
|
|
||||||
|
# Check to see if user has saved file at least once
|
||||||
|
if len(my_filename) < 2:
|
||||||
|
inkex.errormsg('Please Save Your File First')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get png dpi setting
|
||||||
|
png_dpi = self.options.png_dpi
|
||||||
|
|
||||||
|
# Get crop settings
|
||||||
|
if self.options.canvas_to_selection == 'true':
|
||||||
|
canvas_to_selection = 'FitCanvasToSelection;'
|
||||||
|
is_cropped = 'cropped_'
|
||||||
|
else:
|
||||||
|
canvas_to_selection = ''
|
||||||
|
is_cropped = ''
|
||||||
|
|
||||||
|
# Look at selection list 1st item must be background image
|
||||||
|
my_objects = self.svg.selected
|
||||||
|
# Exit if less than 2 objects are selected
|
||||||
|
if len(my_objects) < 2:
|
||||||
|
return
|
||||||
|
if my_objects[-1].TAG != 'image':
|
||||||
|
inkex.errormsg('Last Selected Object Must Be An Image')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Clip background image by each object and export to png
|
||||||
|
|
||||||
|
my_background = my_objects[-1]
|
||||||
|
my_background_id = my_background.get_id()
|
||||||
|
|
||||||
|
# Create a rectangular path same size as image to be clipped used for inverse only
|
||||||
|
image_frame_id = make_image_frame(self, my_background)
|
||||||
|
|
||||||
|
# my_temp_filename_path = make_temp_svg(my_file_path, my_filename)
|
||||||
|
my_temp_svg_file = make_temp_svg(self)
|
||||||
|
my_temp_svg_filename_path = my_temp_svg_file.name
|
||||||
|
|
||||||
|
for my_object in my_objects:
|
||||||
|
# This loop looks at each clipping object, ignores any image objects
|
||||||
|
if my_object.TAG != 'image':
|
||||||
|
my_object_id = my_object.get_id()
|
||||||
|
|
||||||
|
# Build a formatted string for command line actions
|
||||||
|
|
||||||
|
# --batch-process ( or --with-gui ) is required if verbs are used in addition to actions
|
||||||
|
my_options = '--batch-process'
|
||||||
|
|
||||||
|
my_actions = '--actions='
|
||||||
|
|
||||||
|
export_png_actions = ''
|
||||||
|
|
||||||
|
# For Positive Clip
|
||||||
|
if self.options.clip_type_inverse == 'false':
|
||||||
|
|
||||||
|
# Creates individual object clipped files
|
||||||
|
if self.options.output_set == 'separate' or self.options.output_set == 'master_and_separate':
|
||||||
|
my_png_export_filename_path = my_export_path + '/' + my_filename.replace('.svg',
|
||||||
|
'_' + my_object_id + '_' + is_cropped + timestamp_suffix + '.png')
|
||||||
|
|
||||||
|
export_png_actions = my_actions + f'select-by-id:{my_background_id}; \
|
||||||
|
SelectionToBack; \
|
||||||
|
select-by-id:{my_background_id},{my_object_id}; \
|
||||||
|
select-invert; \
|
||||||
|
EditDelete; \
|
||||||
|
select-all; \
|
||||||
|
ObjectSetClipPath; \
|
||||||
|
select-all; \
|
||||||
|
{canvas_to_selection} \
|
||||||
|
export-filename:{my_png_export_filename_path}; \
|
||||||
|
export-dpi:{png_dpi}; \
|
||||||
|
export-do'
|
||||||
|
|
||||||
|
export_png_actions = export_png_actions.replace(' ', '')
|
||||||
|
|
||||||
|
inkscape_command_line_export(self, my_temp_svg_filename_path, my_export_path, my_options,
|
||||||
|
export_png_actions)
|
||||||
|
|
||||||
|
# For Inverse Clip
|
||||||
|
else:
|
||||||
|
if self.options.output_set == 'separate' or self.options.output_set == 'master_and_separate':
|
||||||
|
my_png_export_filename_path = my_export_path + '/' + my_filename.replace('.svg',
|
||||||
|
'_' + my_object_id + '_''inverse_' + is_cropped + timestamp_suffix + '.png')
|
||||||
|
|
||||||
|
export_png_actions = my_actions + f'select-by-id:{image_frame_id},{my_object_id},{my_background_id}; \
|
||||||
|
select-invert; \
|
||||||
|
EditDelete; \
|
||||||
|
select-by-id:{image_frame_id}; \
|
||||||
|
SelectionToBack; \
|
||||||
|
EditDeselect; \
|
||||||
|
select-by-id:{image_frame_id},{my_object_id}; \
|
||||||
|
SelectionSymDiff; \
|
||||||
|
EditDeselect; \
|
||||||
|
select-by-id:{my_background_id}; \
|
||||||
|
SelectionToBack; \
|
||||||
|
select-all; \
|
||||||
|
ObjectSetClipPath; \
|
||||||
|
select-all; \
|
||||||
|
{canvas_to_selection} \
|
||||||
|
export-filename:{my_png_export_filename_path}; \
|
||||||
|
export-dpi:{png_dpi}; \
|
||||||
|
export-do'
|
||||||
|
|
||||||
|
export_png_actions = export_png_actions.replace(' ', '')
|
||||||
|
|
||||||
|
inkscape_command_line_export(self, my_temp_svg_filename_path, my_export_path, my_options,
|
||||||
|
export_png_actions)
|
||||||
|
|
||||||
|
if self.options.output_set == 'master_only' or self.options.output_set == 'master_and_separate':
|
||||||
|
|
||||||
|
# Creates a master image with all clipped objects
|
||||||
|
my_actions = '--actions='
|
||||||
|
my_object_id_list = ''
|
||||||
|
|
||||||
|
for my_object in my_objects:
|
||||||
|
if my_object.TAG != 'image':
|
||||||
|
# Build select ID string length - Windows has a max cmdline string length of 8192
|
||||||
|
my_object_id = my_object.get_id()
|
||||||
|
my_object_id_list += f'{my_object_id},'
|
||||||
|
# Remove last comma from id list
|
||||||
|
my_object_id_list = my_object_id_list.rstrip(',')
|
||||||
|
|
||||||
|
if self.options.clip_type_inverse == 'false':
|
||||||
|
|
||||||
|
my_png_export_filename_path = my_export_path + '/' + my_filename.replace('.svg',
|
||||||
|
'_' + 'master_' + is_cropped + timestamp_suffix + '.png')
|
||||||
|
|
||||||
|
export_png_actions = my_actions + f' \
|
||||||
|
select-by-id:{my_object_id_list},{my_background_id}; \
|
||||||
|
select-invert; \
|
||||||
|
EditDelete; \
|
||||||
|
select-clear; \
|
||||||
|
select-by-id:{my_object_id_list}; \
|
||||||
|
SelectionCombine; \
|
||||||
|
select-clear; \
|
||||||
|
select-by-id:{my_background_id}; \
|
||||||
|
SelectionToBack; \
|
||||||
|
select-clear; \
|
||||||
|
select-all; \
|
||||||
|
ObjectSetClipPath; \
|
||||||
|
select-all; \
|
||||||
|
{canvas_to_selection} \
|
||||||
|
export-filename:{my_png_export_filename_path}; \
|
||||||
|
export-dpi:{png_dpi}; \
|
||||||
|
export-do;'
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
my_png_export_filename_path = my_export_path + '/' + my_filename.replace('.svg',
|
||||||
|
'_' + 'inverse_master_' + is_cropped + timestamp_suffix + '.png')
|
||||||
|
|
||||||
|
export_png_actions = my_actions + f' \
|
||||||
|
select-by-id:{my_object_id_list},{my_background_id},{image_frame_id}; \
|
||||||
|
select-invert; \
|
||||||
|
EditDelete; \
|
||||||
|
select-by-id:{my_object_id_list}; \
|
||||||
|
SelectionCombine; \
|
||||||
|
select-clear; \
|
||||||
|
select-by-id:{image_frame_id}; \
|
||||||
|
SelectionToBack; \
|
||||||
|
select-all; \
|
||||||
|
unselect-by-id:{my_background_id}; \
|
||||||
|
SelectionSymDiff; \
|
||||||
|
select-clear; \
|
||||||
|
select-by-id:{my_background_id}; \
|
||||||
|
SelectionToBack; \
|
||||||
|
select-all; \
|
||||||
|
ObjectSetClipPath; \
|
||||||
|
select-all; \
|
||||||
|
{canvas_to_selection} \
|
||||||
|
export-filename:{my_png_export_filename_path}; \
|
||||||
|
export-dpi:{png_dpi}; \
|
||||||
|
export-do;'
|
||||||
|
|
||||||
|
export_png_actions = export_png_actions.replace(' ', '')
|
||||||
|
|
||||||
|
inkscape_command_line_export(self, my_temp_svg_filename_path, my_export_path, my_options, export_png_actions)
|
||||||
|
|
||||||
|
# Remove rectangular path
|
||||||
|
image_frame = self.svg.getElementById(image_frame_id)
|
||||||
|
image_frame.delete()
|
||||||
|
|
||||||
|
# Close temp file
|
||||||
|
my_temp_svg_file.close()
|
||||||
|
|
||||||
|
class QuickExport(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--clip_type_inverse", default='false')
|
||||||
|
pars.add_argument("--shape_unit_fix", default='false')
|
||||||
|
pars.add_argument("--notebook_main", default=0)
|
||||||
|
pars.add_argument("--output_set", default=0)
|
||||||
|
pars.add_argument("--canvas_to_selection", default=0)
|
||||||
|
pars.add_argument("--save_path", default=str(Path.home()))
|
||||||
|
pars.add_argument("--png_dpi", type=int, default=96)
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
command_line_call(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
QuickExport().run()
|
Reference in New Issue
Block a user