Merge branch 'master' of https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X
This commit is contained in:
commit
fa165a5717
@ -34,32 +34,14 @@ class AboutUpgradeMightyScape(inkex.EffectExtension):
|
|||||||
localCommit = local_repo.head.commit
|
localCommit = local_repo.head.commit
|
||||||
remote_repo = git.remote.Remote(local_repo, 'origin')
|
remote_repo = git.remote.Remote(local_repo, 'origin')
|
||||||
remoteCommit = remote_repo.fetch()[0].commit
|
remoteCommit = remote_repo.fetch()[0].commit
|
||||||
|
self.msg("Latest remote commit is: " + str(remoteCommit)[:7])
|
||||||
|
|
||||||
authors = []
|
authors = []
|
||||||
# for every remote commit
|
# for every remote commit
|
||||||
while remoteCommit.hexsha != localCommit.hexsha:
|
while remoteCommit.hexsha != localCommit.hexsha:
|
||||||
authors.append(remoteCommit.author.email)
|
authors.append(remoteCommit.author.email)
|
||||||
remoteCommit = remoteCommit.parents[0]
|
remoteCommit = remoteCommit.parents[0]
|
||||||
|
|
||||||
#local_commits = list(local_repo.iter_commits("master", max_count=5))
|
|
||||||
local_commits = list(local_repo.iter_commits("master"))
|
|
||||||
self.msg("Local commit id is: " + str(localCommit)[:7])
|
|
||||||
self.msg("Latest remote commit is: " + str(remoteCommit)[:7])
|
|
||||||
self.msg("There are {} local commits at the moment.".format(len(local_commits)))
|
|
||||||
localCommitList = []
|
|
||||||
for local_commit in local_commits:
|
|
||||||
localCommitList.append(local_commit)
|
|
||||||
#localCommitList.reverse()
|
|
||||||
self.msg("*"*40)
|
|
||||||
self.msg("Latest 10 local commits are:")
|
|
||||||
for i in range(0, 10):
|
|
||||||
self.msg("{} | {} : {}".format(
|
|
||||||
datetime.utcfromtimestamp(localCommitList[i].committed_date).strftime('%Y-%m-%d %H:%M:%S'),
|
|
||||||
localCommitList[i].name_rev[:7],
|
|
||||||
localCommitList[i].message.strip())
|
|
||||||
)
|
|
||||||
#self.msg(" - {}: {}".format(localCommitList[i].newhexsha[:7], localCommitList[i].message))
|
|
||||||
self.msg("*"*40)
|
|
||||||
|
|
||||||
if localCommit.hexsha != remoteCommit.hexsha:
|
if localCommit.hexsha != remoteCommit.hexsha:
|
||||||
ssh_executable = 'git'
|
ssh_executable = 'git'
|
||||||
with local_repo.git.custom_environment(GIT_SSH=ssh_executable):
|
with local_repo.git.custom_environment(GIT_SSH=ssh_executable):
|
||||||
@ -126,6 +108,26 @@ class AboutUpgradeMightyScape(inkex.EffectExtension):
|
|||||||
remotes.append("https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X.git") #main
|
remotes.append("https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X.git") #main
|
||||||
remotes.append("https://github.com/vmario89/mightyscape-1.X.git") #copy/second remote
|
remotes.append("https://github.com/vmario89/mightyscape-1.X.git") #copy/second remote
|
||||||
|
|
||||||
|
localCommit = local_repo.head.commit
|
||||||
|
#local_commits = list(local_repo.iter_commits("master", max_count=5))
|
||||||
|
localCommits = list(local_repo.iter_commits("master"))
|
||||||
|
self.msg("Local commit id is: " + str(localCommit)[:7])
|
||||||
|
self.msg("There are {} local commits at the moment.".format(len(localCommits)))
|
||||||
|
localCommitList = []
|
||||||
|
for local_commit in localCommits:
|
||||||
|
localCommitList.append(localCommit)
|
||||||
|
#localCommitList.reverse()
|
||||||
|
self.msg("*"*40)
|
||||||
|
self.msg("Latest 10 local commits are:")
|
||||||
|
for i in range(0, 10):
|
||||||
|
self.msg("{} | {} : {}".format(
|
||||||
|
datetime.utcfromtimestamp(localCommitList[i].committed_date).strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
localCommitList[i].name_rev[:7],
|
||||||
|
localCommitList[i].message.strip())
|
||||||
|
)
|
||||||
|
#self.msg(" - {}: {}".format(localCommitList[i].newhexsha[:7], localCommitList[i].message))
|
||||||
|
self.msg("*"*40)
|
||||||
|
|
||||||
#finally run the update
|
#finally run the update
|
||||||
success = self.update(local_repo, remotes[0])
|
success = self.update(local_repo, remotes[0])
|
||||||
if success is False: #try the second remote if first failed
|
if success is False: #try the second remote if first failed
|
||||||
|
@ -59,10 +59,6 @@ class AnimateOrder(inkex.EffectExtension):
|
|||||||
|
|
||||||
target_html = os.path.join(extension_dir, "animate_order.html")
|
target_html = os.path.join(extension_dir, "animate_order.html")
|
||||||
|
|
||||||
if os.path.exists(target_html) is False:
|
|
||||||
inkex.utils.debug("Error. Target file does not exist!")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
docTitle = self.document.getroot().get("{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}docname")
|
docTitle = self.document.getroot().get("{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}docname")
|
||||||
if docTitle is None:
|
if docTitle is None:
|
||||||
title = "Animate Order - Vivus JS"
|
title = "Animate Order - Vivus JS"
|
||||||
@ -96,6 +92,10 @@ class AnimateOrder(inkex.EffectExtension):
|
|||||||
print( ' </body>' , file=text_file)
|
print( ' </body>' , file=text_file)
|
||||||
print( '</html>' , file=text_file)
|
print( '</html>' , file=text_file)
|
||||||
|
|
||||||
|
if os.path.exists(target_html) is False:
|
||||||
|
inkex.utils.debug("Error. Target file does not exist!")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
#now open firefox
|
#now open firefox
|
||||||
args = [self.options.browser, target_html]
|
args = [self.options.browser, target_html]
|
||||||
try:
|
try:
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
<name>Contour Scanner And Trimmer</name>
|
<name>Contour Scanner And Trimmer</name>
|
||||||
<id>fablabchemnitz.de.contour_scanner_and_trimmer</id>
|
<id>fablabchemnitz.de.contour_scanner_and_trimmer</id>
|
||||||
<param name="tab" type="notebook">
|
<param name="nb_main" type="notebook">
|
||||||
|
<page name="tab_settings_and_actions" gui-text="Settings and Actions">
|
||||||
|
<param name="nb_settings_and_actions" type="notebook">
|
||||||
<page name="tab_settings" gui-text="Settings">
|
<page name="tab_settings" gui-text="Settings">
|
||||||
<label appearance="header">General</label>
|
<label appearance="header">General input/output</label>
|
||||||
<param name="show_debug" type="bool" gui-text="Show debug infos">false</param>
|
<param name="show_debug" type="bool" gui-text="Show debug infos">false</param>
|
||||||
<param name="break_apart" type="bool" gui-text="Break apart input" gui-description="Break apart input paths into sub paths. Modifies original paths: converts to absolute paths and might create additional new path elements.">false</param>
|
<param name="break_apart" type="bool" gui-text="Break apart input" gui-description="Break apart input paths into sub paths. Modifies original paths: converts to absolute paths and might create additional new path elements.">false</param>
|
||||||
<param name="handle_groups" type="bool" gui-text="Handle groups" gui-description="Also looks for paths in groups which are in the current selection. Note: The generated results have a different structure (less granularity due to grouping and conversion of absolute paths to relative paths) than directly selected paths. The colorization for non-intersected paths will be different too.">false</param>
|
<param name="handle_groups" type="bool" gui-text="Handle groups" gui-description="Also looks for paths in groups which are in the current selection. Note: The generated results have a different structure (less granularity due to grouping and conversion of absolute paths to relative paths) than directly selected paths. The colorization for non-intersected paths will be different too.">false</param>
|
||||||
@ -12,13 +14,22 @@
|
|||||||
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get (quantization). Large values might destroy the line continuity.">0.100</param>
|
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get (quantization). Large values might destroy the line continuity.">0.100</param>
|
||||||
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for sub split lines / lines trimmed by shapely (default: 3)">3</param>
|
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for sub split lines / lines trimmed by shapely (default: 3)">3</param>
|
||||||
<param name="snap_tolerance" type="float" min="0.01" max="10.0" gui-text="Snap tolerance" gui-description="Snap tolerance for intersection points on paths (default: 0.1)">0.1</param>
|
<param name="snap_tolerance" type="float" min="0.01" max="10.0" gui-text="Snap tolerance" gui-description="Snap tolerance for intersection points on paths (default: 0.1)">0.1</param>
|
||||||
<param name="draw_subsplit" type="bool" gui-text="Draw sub split lines (for debugging purposes)" gui-description="Draws polylines. Will be automatically enabled if any highlighting is activated.">false</param>
|
<label appearance="header">General style</label>
|
||||||
<param name="remove_subsplit_collinear" type="bool" gui-text="Remove collinear overlapping lines (experimental)" gui-description="Removes any duplicates by merging (multiple) overlapping line segments into longer lines. Not possible to apply for original paths because this routine does not support bezier type paths.">true</param>
|
<param name="strokewidth" min="0.0" max="10000.0" precision="3" gui-text="Stroke width (px)" gui-description="Applies For sub split lines and trimmed lines" type="float">1.0</param>
|
||||||
|
<param name="dotsize_intersections" type="int" min="0" max="10000" gui-text="Intersection dot size (px)" gui-description="For self-intersecting and global intersection points">30</param>
|
||||||
|
<param name="removefillsetstroke" type="bool" gui-text="Remove fill and define stroke" gui-description="Modifies original path style">false</param>
|
||||||
|
<param name="subsplit_style" type="optiongroup" appearance="combo" gui-text="Sub split line style">
|
||||||
|
<option value="default">Use default sub split style</option>
|
||||||
|
<option value="apply_from_highlightings">Apply highlighting styles</option>
|
||||||
|
<option value="apply_from_original">Apply original path styles</option>
|
||||||
|
</param>
|
||||||
|
<param name="trimmed_style" type="optiongroup" appearance="combo" gui-text="Trimmed line style">
|
||||||
|
<option value="apply_from_trimmed">Apply default trimming styles</option>
|
||||||
|
<option value="apply_from_original">Apply original path styles</option>
|
||||||
|
</param>
|
||||||
</page>
|
</page>
|
||||||
<page name="tab_scanning" gui-text="Scanning and Trimming">
|
<page name="tab_removing" gui-text="Removing">
|
||||||
<hbox>
|
<label appearance="header">Applying to original paths and sub split lines</label>
|
||||||
<vbox>
|
|
||||||
<label appearance="header">Removing Original Paths</label>
|
|
||||||
<param name="remove_relative" type="bool" gui-text="relative cmd">false</param>
|
<param name="remove_relative" type="bool" gui-text="relative cmd">false</param>
|
||||||
<param name="remove_absolute" type="bool" gui-text="absolute cmd">false</param>
|
<param name="remove_absolute" type="bool" gui-text="absolute cmd">false</param>
|
||||||
<param name="remove_mixed" type="bool" gui-text="mixed cmd" gui-description="combined relative and absolute">false</param>
|
<param name="remove_mixed" type="bool" gui-text="mixed cmd" gui-description="combined relative and absolute">false</param>
|
||||||
@ -27,22 +38,35 @@
|
|||||||
<param name="remove_opened" type="bool" gui-text="opened">false</param>
|
<param name="remove_opened" type="bool" gui-text="opened">false</param>
|
||||||
<param name="remove_closed" type="bool" gui-text="closed">false</param>
|
<param name="remove_closed" type="bool" gui-text="closed">false</param>
|
||||||
<param name="remove_self_intersecting" type="bool" gui-text="self-intersecting">false</param>
|
<param name="remove_self_intersecting" type="bool" gui-text="self-intersecting">false</param>
|
||||||
<separator/>
|
<label appearance="header">Applying to sub split lines only</label>
|
||||||
<label appearance="header">Highlighting</label>
|
<param name="filter_subsplit_collinear" type="bool" gui-text="Filter collinear overlapping lines" gui-description="Removes any duplicates by merging (multiple) overlapping line segments into longer lines. Not possible to apply for original paths because this routine does not support bezier type paths.">true</param>
|
||||||
<param name="highlight_relative" type="bool" gui-text="relative cmd paths">false</param>
|
<param name="filter_subsplit_collinear_action" type="optiongroup" appearance="combo" gui-text="What to do with collinear overlapping lines?">
|
||||||
<param name="highlight_absolute" type="bool" gui-text="absolute cmd paths">false</param>
|
<option value="remove">remove</option>
|
||||||
<param name="highlight_mixed" type="bool" gui-text="mixed cmd paths" gui-description="combined relative and absolute">false</param>
|
<option value="separate_group">put to separate group</option>
|
||||||
<param name="highlight_polylines" type="bool" gui-text="polyline paths">false</param>
|
</param>
|
||||||
<param name="highlight_beziers" type="bool" gui-text="bezier paths">false</param>
|
<label appearance="header">Applying to original paths only</label>
|
||||||
<param name="highlight_opened" type="bool" gui-text="opened paths">false</param>
|
<param name="keep_original_after_split_trim" type="bool" gui-text="Keep original paths after sub splitting / trimming">false</param>
|
||||||
<param name="highlight_closed" type="bool" gui-text="closed paths">false</param>
|
</page>
|
||||||
<param name="highlight_self_intersecting" type="bool" gui-text="self-intersecting paths" gui-description="Requires to draw sub split lines. Will override highlighting colors for open and closed paths (if those options are enabled)">false</param>
|
<page name="tab_highlighting" gui-text="Highlighting">
|
||||||
<param name="visualize_self_intersections" type="bool" gui-text="self-intersecting path points">false</param>
|
<label appearance="header">Applying to original paths and sub split lines</label>
|
||||||
|
<param name="highlight_relative" type="bool" gui-text="relative cmd">false</param>
|
||||||
|
<param name="highlight_absolute" type="bool" gui-text="absolute cmd">false</param>
|
||||||
|
<param name="highlight_mixed" type="bool" gui-text="mixed cmd" gui-description="combined relative and absolute">false</param>
|
||||||
|
<param name="highlight_polylines" type="bool" gui-text="polylines">false</param>
|
||||||
|
<param name="highlight_beziers" type="bool" gui-text="beziers">false</param>
|
||||||
|
<param name="highlight_opened" type="bool" gui-text="opened">false</param>
|
||||||
|
<param name="highlight_closed" type="bool" gui-text="closed">false</param>
|
||||||
|
<param name="highlight_self_intersecting" type="bool" gui-text="self-intersecting" gui-description="Requires enabled 'Draw sub split lines' option (will auto-enable). Will override other highlighting colors (if those options are enabled)">false</param>
|
||||||
|
<label appearance="header">Applying to sub split lines only</label>
|
||||||
|
<param name="draw_subsplit" type="bool" gui-text="Draw sub split lines" gui-description="Draws polylines which are generated from all input paths">false</param>
|
||||||
|
<param name="highlight_duplicates" type="bool" gui-text="duplicates">false</param>
|
||||||
|
<param name="highlight_merges" type="bool" gui-text="merges" gui-description="Requires enabled 'Remove collinear overlapping lines' option (will auto-enable)">false</param>
|
||||||
|
<label appearance="header">Intersection points</label>
|
||||||
|
<param name="visualize_self_intersections" type="bool" gui-text="self-intersecting path points" gui-description="Will put into background (z-Index) by global intersection points and trimmed lines (if enabled)">false</param>
|
||||||
<param name="visualize_global_intersections" type="bool" gui-text="global intersection points" gui-description="Will also contain self-intersecting points! Global intersections will only show if 'Draw trimmed lines' is enabled!">false</param>
|
<param name="visualize_global_intersections" type="bool" gui-text="global intersection points" gui-description="Will also contain self-intersecting points! Global intersections will only show if 'Draw trimmed lines' is enabled!">false</param>
|
||||||
</vbox>
|
</page>
|
||||||
<separator/>
|
<page name="tab_trimming" gui-text="Trimming">
|
||||||
<vbox>
|
<label appearance="header">General trimming settings</label>
|
||||||
<label appearance="header">Trimming</label>
|
|
||||||
<param name="trimming_path_types" type="optiongroup" appearance="combo" gui-text="Trimming selection" gui-description="Process open paths by other open paths, closed paths by other closed paths, or all paths by all other paths. This selection does not apply for paths which intersect themselves!">
|
<param name="trimming_path_types" type="optiongroup" appearance="combo" gui-text="Trimming selection" gui-description="Process open paths by other open paths, closed paths by other closed paths, or all paths by all other paths. This selection does not apply for paths which intersect themselves!">
|
||||||
<option value="both">all:all paths</option>
|
<option value="both">all:all paths</option>
|
||||||
<option value="open_paths">open:open paths</option>
|
<option value="open_paths">open:open paths</option>
|
||||||
@ -50,11 +74,11 @@
|
|||||||
</param>
|
</param>
|
||||||
<param name="draw_trimmed" type="bool" gui-text="Draw trimmed lines">false</param>
|
<param name="draw_trimmed" type="bool" gui-text="Draw trimmed lines">false</param>
|
||||||
<param name="combine_nonintersects" type="bool" gui-text="Chain + combine non-intersected lines" gui-description="This will colorize all paths segments which were not intersected ('non-intersected lines'). If the whole path was not intersected at all, it will get another color ('non-intersected paths').">true</param>
|
<param name="combine_nonintersects" type="bool" gui-text="Chain + combine non-intersected lines" gui-description="This will colorize all paths segments which were not intersected ('non-intersected lines'). If the whole path was not intersected at all, it will get another color ('non-intersected paths').">true</param>
|
||||||
<param name="remove_duplicates" type="bool" gui-text="Remove duplicate trim lines">true</param>
|
<param name="remove_trim_duplicates" type="bool" gui-text="Remove duplicate trim lines" gui-description="Has no effect if option 'Filter collinear overlapping lines' is enabled because duplicates get pre-filtered.">true</param>
|
||||||
<param name="reverse_removal_order" type="bool" gui-text="Reverse removal order" gui-description="Reverses the order of removal. Relevant for keeping certain styles of elements">false</param>
|
<param name="reverse_trim_removal_order" type="bool" gui-text="Reverse trim line removal order" gui-description="Reverses the order of removal. Relevant for keeping certain styles of elements">false</param>
|
||||||
|
<param name="remove_subsplit_after_trimming" type="bool" gui-text="Remove sub split lines after trimming" gui-description="Recommended if option 'Filter collinear overlapping lines' is enabled">true</param>
|
||||||
<param name="bezier_trimming" type="bool" gui-text="Trim original beziers (not working yet)" gui-description="If enabled we try to split the original bezier paths at the intersections points by finding the correct bezier segments and calculating t parameters from trimmed sub split lines. Not working yet. Will just print debug info if debug is enabled.">true</param>
|
<param name="bezier_trimming" type="bool" gui-text="Trim original beziers (not working yet)" gui-description="If enabled we try to split the original bezier paths at the intersections points by finding the correct bezier segments and calculating t parameters from trimmed sub split lines. Not working yet. Will just print debug info if debug is enabled.">true</param>
|
||||||
<param name="keep_original_after_trim" type="bool" gui-text="Keep original paths after trimming">false</param>
|
<label appearance="header">Bentley-Ottmann sweep line settings</label>
|
||||||
<label appearance="header">Bentley Ottmann Sweep Line Settings</label>
|
|
||||||
<param name="bent_ott_use_ignore_segment_endings" type="bool" gui-text="Ignore segment endings" gui-description="Whether to ignore intersections of line segments when both their end points form the intersection point">true</param>
|
<param name="bent_ott_use_ignore_segment_endings" type="bool" gui-text="Ignore segment endings" gui-description="Whether to ignore intersections of line segments when both their end points form the intersection point">true</param>
|
||||||
<param name="bent_ott_use_debug" type="bool" gui-text="Debug">false</param>
|
<param name="bent_ott_use_debug" type="bool" gui-text="Debug">false</param>
|
||||||
<param name="bent_ott_use_verbose" type="bool" gui-text="Verbose">false</param>
|
<param name="bent_ott_use_verbose" type="bool" gui-text="Verbose">false</param>
|
||||||
@ -64,14 +88,13 @@
|
|||||||
<option value="native">native (default)</option>
|
<option value="native">native (default)</option>
|
||||||
<option value="numpy">numpy</option>
|
<option value="numpy">numpy</option>
|
||||||
</param>
|
</param>
|
||||||
</vbox>
|
|
||||||
</hbox>
|
|
||||||
</page>
|
</page>
|
||||||
<page name="tab_style" gui-text="Style">
|
<page name="tab_colors" gui-text="Colors">
|
||||||
<hbox>
|
<hbox>
|
||||||
<vbox>
|
<vbox>
|
||||||
<label appearance="header">Scanning Colors</label>
|
<label appearance="header">Sub split lines</label>
|
||||||
<param name="color_subsplit" type="color" appearance="colorbutton" gui-text="sub split lines">1630897151</param>
|
<param name="color_subsplit" type="color" appearance="colorbutton" gui-text="sub split lines">1630897151</param>
|
||||||
|
<label appearance="header">Path structure</label>
|
||||||
<param name="color_relative" type="color" appearance="colorbutton" gui-text="relative cmd paths">3419879935</param>
|
<param name="color_relative" type="color" appearance="colorbutton" gui-text="relative cmd paths">3419879935</param>
|
||||||
<param name="color_absolute" type="color" appearance="colorbutton" gui-text="absolute cmd paths">1592519679</param>
|
<param name="color_absolute" type="color" appearance="colorbutton" gui-text="absolute cmd paths">1592519679</param>
|
||||||
<param name="color_mixed" type="color" appearance="colorbutton" gui-text="mixed cmd paths" gui-description="combined relative and absolute">3351636735</param>
|
<param name="color_mixed" type="color" appearance="colorbutton" gui-text="mixed cmd paths" gui-description="combined relative and absolute">3351636735</param>
|
||||||
@ -79,33 +102,30 @@
|
|||||||
<param name="color_bezier" type="color" appearance="colorbutton" gui-text="bezier paths">258744063</param>
|
<param name="color_bezier" type="color" appearance="colorbutton" gui-text="bezier paths">258744063</param>
|
||||||
<param name="color_opened" type="color" appearance="colorbutton" gui-text="opened paths">4012452351</param>
|
<param name="color_opened" type="color" appearance="colorbutton" gui-text="opened paths">4012452351</param>
|
||||||
<param name="color_closed" type="color" appearance="colorbutton" gui-text="closed paths">2330080511</param>
|
<param name="color_closed" type="color" appearance="colorbutton" gui-text="closed paths">2330080511</param>
|
||||||
<param name="color_self_intersecting_paths" type="color" appearance="colorbutton" gui-text="self-intersecting paths">2593756927</param>
|
|
||||||
<param name="color_self_intersections" type="color" appearance="colorbutton" gui-text="self-intersecting paths points">6320383</param>
|
|
||||||
<param name="color_global_intersections" type="color" appearance="colorbutton" gui-text="global intersection points">4239343359</param>
|
|
||||||
</vbox>
|
</vbox>
|
||||||
<separator/>
|
<separator/>
|
||||||
<vbox>
|
<vbox>
|
||||||
<label appearance="header">Trimming Colors</label>
|
<label appearance="header">Duplicates and merges</label>
|
||||||
|
<param name="color_duplicates" type="color" appearance="colorbutton" gui-text="duplicates" gui-description="Color for overlapping line segments / duplicated lines)">897901823</param>
|
||||||
|
<param name="color_merges" type="color" appearance="colorbutton" gui-text="merges" gui-description="Color for replaced merges">869366527</param>
|
||||||
|
<label appearance="header">Intersections</label>
|
||||||
|
<param name="color_self_intersecting_paths" type="color" appearance="colorbutton" gui-text="self-intersecting paths">2593756927</param>
|
||||||
|
<param name="color_self_intersections" type="color" appearance="colorbutton" gui-text="self-intersecting paths points">6320383</param>
|
||||||
|
<param name="color_global_intersections" type="color" appearance="colorbutton" gui-text="global intersection points">4239343359</param>
|
||||||
|
<label appearance="header">Trimming</label>
|
||||||
<param name="color_trimmed" type="color" appearance="colorbutton" gui-text="trimmed lines">3227634687</param>
|
<param name="color_trimmed" type="color" appearance="colorbutton" gui-text="trimmed lines">3227634687</param>
|
||||||
<param name="color_combined" type="color" appearance="colorbutton" gui-text="non-intersected lines" gui-description="Colorize non-trimmed lines differently than the trimmed ones. Does not apply if 'Original style for trimmed lines' is enabled">1923076095</param>
|
<param name="color_combined" type="color" appearance="colorbutton" gui-text="non-intersected lines" gui-description="Colorize non-trimmed lines differently than the trimmed ones. Does not apply if 'Original style for trimmed lines' is enabled">1923076095</param>
|
||||||
<param name="color_nonintersected" type="color" appearance="colorbutton" gui-text="non-intersected paths" gui-description="Colorize the complete path in case it does not contain any trim. Does not apply if 'Original style for trimmed lines' is enabled">3045284607</param>
|
<param name="color_nonintersected" type="color" appearance="colorbutton" gui-text="non-intersected paths" gui-description="Colorize the complete path in case it does not contain any trim. Does not apply if 'Original style for trimmed lines' is enabled">3045284607</param>
|
||||||
<label appearance="header">General Style</label>
|
|
||||||
<param name="strokewidth" min="0.0" max="10000.0" precision="3" gui-text="Stroke width (px)" gui-description="Applies For sub split lines and trimmed lines" type="float">1.0</param>
|
|
||||||
<param name="dotsize_intersections" type="int" min="0" max="10000" gui-text="Dot size (px)" gui-description="For self-intersecting and global intersection points">30</param>
|
|
||||||
<param name="removefillsetstroke" type="bool" gui-text="Remove fill and define stroke" gui-description="Modifies original path style">false</param>
|
|
||||||
<param name="apply_style_to_subsplits" type="bool" gui-text="Highlighting styles for sub split lines" gui-description="Apply highlighting styles to sub split lines.">true</param>
|
|
||||||
<param name="apply_style_to_trimmed" type="bool" gui-text="Original style for trimmed lines" gui-description="Apply original path style to trimmed lines.">true</param>
|
|
||||||
|
|
||||||
</vbox>
|
</vbox>
|
||||||
</hbox>
|
</hbox>
|
||||||
</page>
|
</page>
|
||||||
|
</param>
|
||||||
|
</page>
|
||||||
<page name="tab_tips" gui-text="Tips">
|
<page name="tab_tips" gui-text="Tips">
|
||||||
<label xml:space="preserve"> - Helps to find duplicate lines and to visualize intersections
|
<label xml:space="preserve"> - Allows to separate different contour types by colors
|
||||||
- Works for self-intersecting paths too
|
- Finds overlapping / collinear / (self-)intersecting
|
||||||
- Uses Bentley-Ottmann algorithm to detect intersections
|
(using Bentley-Ottmann algorithm) lines
|
||||||
- Allows to separate different contour types by colors
|
|
||||||
- Works with paths which have Live Path Effects (LPE)
|
- Works with paths which have Live Path Effects (LPE)
|
||||||
- Does not find overlapping colinear lines (sweep line algorithm does not intersect them)
|
|
||||||
|
|
||||||
Tips:
|
Tips:
|
||||||
- If nothings is selected, the whole document will be processed, regardless of groups. In contrast, if you made a custom selection, check to handle or not to handle groups.
|
- If nothings is selected, the whole document will be processed, regardless of groups. In contrast, if you made a custom selection, check to handle or not to handle groups.
|
||||||
|
@ -3,6 +3,11 @@
|
|||||||
'''
|
'''
|
||||||
Extension for InkScape 1.0+
|
Extension for InkScape 1.0+
|
||||||
- WARNING: HORRIBLY SLOW CODE. PLEASE HELP TO MAKE IT USEFUL FOR LARGE AMOUNT OF PATHS
|
- WARNING: HORRIBLY SLOW CODE. PLEASE HELP TO MAKE IT USEFUL FOR LARGE AMOUNT OF PATHS
|
||||||
|
- ToDo:
|
||||||
|
- add more comments
|
||||||
|
- add more debug output
|
||||||
|
- add documentation at online page
|
||||||
|
- add statistics about type counts and path lengths (before/after sub splitting/trimming)
|
||||||
- add options:
|
- add options:
|
||||||
- replace trimmed paths by bezier paths (calculating lengths and required t parameter)
|
- replace trimmed paths by bezier paths (calculating lengths and required t parameter)
|
||||||
- filter/remove overlapping/duplicates in
|
- filter/remove overlapping/duplicates in
|
||||||
@ -13,6 +18,7 @@ Extension for InkScape 1.0+
|
|||||||
- maybe option: convert rel path to abs path
|
- maybe option: convert rel path to abs path
|
||||||
replacedelement.path = replacedelement.path.to_absolute().to_superpath().to_path()
|
replacedelement.path = replacedelement.path.to_absolute().to_superpath().to_path()
|
||||||
- maybe option: break apart while keeping relative/absolute commands (more complex and not sure if we have a great advantage having this)
|
- maybe option: break apart while keeping relative/absolute commands (more complex and not sure if we have a great advantage having this)
|
||||||
|
- note: running this extension might leave some empty parent groups in some circumstances. run the clean groups extension separately to fix that
|
||||||
|
|
||||||
- important to notice
|
- important to notice
|
||||||
- this algorithm might be really slow. Reduce flattening quality to speed up
|
- this algorithm might be really slow. Reduce flattening quality to speed up
|
||||||
@ -47,7 +53,7 @@ Extension for InkScape 1.0+
|
|||||||
Author: Mario Voigt / FabLab Chemnitz
|
Author: Mario Voigt / FabLab Chemnitz
|
||||||
Mail: mario.voigt@stadtfabrikanten.org
|
Mail: mario.voigt@stadtfabrikanten.org
|
||||||
Date: 09.08.2020 (extension originally called "Contour Scanner")
|
Date: 09.08.2020 (extension originally called "Contour Scanner")
|
||||||
Last patch: 22.06.2021
|
Last patch: 24.06.2021
|
||||||
License: GNU GPL v3
|
License: GNU GPL v3
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -68,10 +74,11 @@ from shapely import speedups
|
|||||||
if speedups.available:
|
if speedups.available:
|
||||||
speedups.enable()
|
speedups.enable()
|
||||||
|
|
||||||
|
|
||||||
idPrefixSubSplit = "subsplit"
|
idPrefixSubSplit = "subsplit"
|
||||||
idPrefixTrimming = "shapely"
|
idPrefixTrimming = "trimmed"
|
||||||
intersectedVerb = "intersected"
|
intersectedVerb = "intersected"
|
||||||
|
collinearVerb = "collinear"
|
||||||
|
|
||||||
EPS_M = 0.01
|
EPS_M = 0.01
|
||||||
|
|
||||||
class ContourScannerAndTrimmer(inkex.EffectExtension):
|
class ContourScannerAndTrimmer(inkex.EffectExtension):
|
||||||
@ -216,6 +223,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
selfIntersectionPointCircle.set('id', self.svg.get_unique_id('selfIntersectionPoint-'))
|
selfIntersectionPointCircle.set('id', self.svg.get_unique_id('selfIntersectionPoint-'))
|
||||||
selfIntersectionPointCircle.style = selfIntersectionPointStyle
|
selfIntersectionPointCircle.style = selfIntersectionPointStyle
|
||||||
selfIntersectionGroup.add(selfIntersectionPointCircle)
|
selfIntersectionGroup.add(selfIntersectionPointCircle)
|
||||||
|
return selfIntersectionGroup
|
||||||
|
|
||||||
|
|
||||||
def visualize_global_intersections(self, globalIntersectionPoints):
|
def visualize_global_intersections(self, globalIntersectionPoints):
|
||||||
@ -296,9 +304,9 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
|
|
||||||
#if trimGroupParentTransform is not None:
|
#if trimGroupParentTransform is not None:
|
||||||
# trimLine.path = trimLine.path.transform(-trimGroupParentTransform)
|
# trimLine.path = trimLine.path.transform(-trimGroupParentTransform)
|
||||||
if self.options.apply_style_to_trimmed is False:
|
if self.options.trimmed_style == "apply_from_trimmed":
|
||||||
trimLine.style = trimLineStyle
|
trimLine.style = trimLineStyle
|
||||||
else:
|
elif self.options.trimmed_style == "apply_from_original":
|
||||||
trimLine.style = subSplitLineArray[subSplitIndex].attrib['originalPathStyle']
|
trimLine.style = subSplitLineArray[subSplitIndex].attrib['originalPathStyle']
|
||||||
trimGroup.add(trimLine)
|
trimGroup.add(trimLine)
|
||||||
return trimGroup
|
return trimGroup
|
||||||
@ -368,7 +376,10 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
def filter_collinear(self, lineArray):
|
def filter_collinear(self, lineArray):
|
||||||
'''
|
'''
|
||||||
Loop through a set of lines and find + fiter all overlapping segments / duplicate segments
|
Loop through a set of lines and find + fiter all overlapping segments / duplicate segments
|
||||||
finally returns a set of merged-like lines and a set of original items which should be dropped
|
finally returns a set of merged-like lines and a set of original items which should be dropped.
|
||||||
|
Based on the style of the algorithm we have no good influence on the z-index of the items because
|
||||||
|
it is scanned by slope and point coordinates. That's why we have a more special
|
||||||
|
'remove_trim_duplicates()' function for trimmed duplicates!
|
||||||
'''
|
'''
|
||||||
input_set = []
|
input_set = []
|
||||||
input_ids = []
|
input_ids = []
|
||||||
@ -376,7 +387,11 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
# collect segments, calculate their slopes, order their points left-to-right
|
# collect segments, calculate their slopes, order their points left-to-right
|
||||||
for line in lineArray:
|
for line in lineArray:
|
||||||
#csp = line.path.to_arrays()
|
#csp = line.path.to_arrays()
|
||||||
csp = Path(line.path.transform(line.getparent().composed_transform())).to_arrays()
|
parent = line.getparent()
|
||||||
|
if parent is not None:
|
||||||
|
csp = Path(line.path.transform(parent.composed_transform())).to_arrays()
|
||||||
|
else:
|
||||||
|
csp = line.path.to_arrays()
|
||||||
#self.msg("csp = {}".format(csp))
|
#self.msg("csp = {}".format(csp))
|
||||||
x1, y1, x2, y2 = csp[0][1][0], csp[0][1][1], csp[1][1][0], csp[1][1][1]
|
x1, y1, x2, y2 = csp[0][1][0], csp[0][1][1], csp[1][1][0], csp[1][1][1]
|
||||||
# ensure p0 is left of p1
|
# ensure p0 is left of p1
|
||||||
@ -431,7 +446,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
|
|
||||||
#we finally build a list which contains all overlapping elements we want to drop
|
#we finally build a list which contains all overlapping elements we want to drop
|
||||||
dropped_ids = []
|
dropped_ids = []
|
||||||
for input_id in input_ids: #if the working id is not in the output id we are going to drop it
|
for input_id in input_ids: #if the input_id id is not in the output ids we are going to drop it
|
||||||
if input_id not in output_ids:
|
if input_id not in output_ids:
|
||||||
dropped_ids.append(input_id)
|
dropped_ids.append(input_id)
|
||||||
|
|
||||||
@ -459,10 +474,14 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
return output_set, dropped_ids
|
return output_set, dropped_ids
|
||||||
|
|
||||||
|
|
||||||
def remove_duplicates(self, allTrimGroups):
|
def remove_trim_duplicates(self, allTrimGroups):
|
||||||
''' find duplicate lines in a given array [] of groups '''
|
'''
|
||||||
|
find duplicate lines in a given array [] of groups
|
||||||
|
note: this function is similar to filter_collinear but we keep it because we have a 'reverse_trim_removal_order' option.
|
||||||
|
We can use this option in some special situations where we work without the function 'filter_collinear()'.
|
||||||
|
'''
|
||||||
totalTrimPaths = []
|
totalTrimPaths = []
|
||||||
if self.options.reverse_removal_order is True:
|
if self.options.reverse_trim_removal_order is True:
|
||||||
allTrimGroups = allTrimGroups[::-1]
|
allTrimGroups = allTrimGroups[::-1]
|
||||||
for trimGroup in allTrimGroups:
|
for trimGroup in allTrimGroups:
|
||||||
for element in trimGroup:
|
for element in trimGroup:
|
||||||
@ -522,7 +541,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
self.msg("trim group {} has {} combinable segments:".format(trimGroup.get('id'), len(newPathData)))
|
self.msg("trim group {} has {} combinable segments:".format(trimGroup.get('id'), len(newPathData)))
|
||||||
self.msg("{}".format(newPathData))
|
self.msg("{}".format(newPathData))
|
||||||
combinedPath.path = Path(newPathData)
|
combinedPath.path = Path(newPathData)
|
||||||
if self.options.apply_style_to_trimmed is False:
|
if self.options.trimmed_style == "apply_from_trimmed":
|
||||||
combinedPath.style = trimNonIntersectedStyle
|
combinedPath.style = trimNonIntersectedStyle
|
||||||
if totalIntersectionsAtPath == 0:
|
if totalIntersectionsAtPath == 0:
|
||||||
combinedPath.style = nonTrimLineStyle
|
combinedPath.style = nonTrimLineStyle
|
||||||
@ -574,9 +593,10 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
|
|
||||||
|
|
||||||
def add_arguments(self, pars):
|
def add_arguments(self, pars):
|
||||||
pars.add_argument("--tab")
|
pars.add_argument("--nb_main")
|
||||||
|
pars.add_argument("--nb_settings_and_actions")
|
||||||
|
|
||||||
#Settings - General
|
#Settings - General Input/Output
|
||||||
pars.add_argument("--show_debug", type=inkex.Boolean, default=False, help="Show debug infos")
|
pars.add_argument("--show_debug", type=inkex.Boolean, default=False, help="Show debug infos")
|
||||||
pars.add_argument("--break_apart", type=inkex.Boolean, default=False, help="Break apart input paths into sub paths")
|
pars.add_argument("--break_apart", type=inkex.Boolean, default=False, help="Break apart input paths into sub paths")
|
||||||
pars.add_argument("--handle_groups", type=inkex.Boolean, default=False, help="Also looks for paths in groups which are in the current selection")
|
pars.add_argument("--handle_groups", type=inkex.Boolean, default=False, help="Also looks for paths in groups which are in the current selection")
|
||||||
@ -585,10 +605,15 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
pars.add_argument("--flatness", type=float, default=0.1, help="Minimum flatness = 0.001. The smaller the value the more fine segments you will get (quantization). Large values might destroy the line continuity.")
|
pars.add_argument("--flatness", type=float, default=0.1, help="Minimum flatness = 0.001. The smaller the value the more fine segments you will get (quantization). Large values might destroy the line continuity.")
|
||||||
pars.add_argument("--decimals", type=int, default=3, help="Accuracy for sub split lines / lines trimmed by shapely")
|
pars.add_argument("--decimals", type=int, default=3, help="Accuracy for sub split lines / lines trimmed by shapely")
|
||||||
pars.add_argument("--snap_tolerance", type=float, default=0.1, help="Snap tolerance for intersection points")
|
pars.add_argument("--snap_tolerance", type=float, default=0.1, help="Snap tolerance for intersection points")
|
||||||
pars.add_argument("--draw_subsplit", type=inkex.Boolean, default=False, help="Draw sub split lines (polylines)")
|
#Settings - General Style
|
||||||
pars.add_argument("--remove_subsplit_collinear", type=inkex.Boolean, default=True, help="Removes any duplicates by merging (multiple) overlapping line segments into longer lines. Not possible to apply for original paths because this routine does not support bezier type paths.")
|
pars.add_argument("--strokewidth", type=float, default=1.0, help="Stroke width (px)")
|
||||||
|
pars.add_argument("--dotsize_intersections", type=int, default=30, help="Dot size (px) for self-intersecting and global intersection points")
|
||||||
|
pars.add_argument("--removefillsetstroke", type=inkex.Boolean, default=False, help="Remove fill and define stroke for original paths")
|
||||||
|
pars.add_argument("--bezier_trimming", type=inkex.Boolean, default=False, help="If true we try to use the calculated t parameters from intersection points to receive splitted bezier curves")
|
||||||
|
pars.add_argument("--subsplit_style", default="default", help="Sub split line style")
|
||||||
|
pars.add_argument("--trimmed_style", default="apply_from_trimmed", help="Trimmed line style")
|
||||||
|
|
||||||
#Scanning - Removing
|
#Removing - Applying to original paths and sub split lines
|
||||||
pars.add_argument("--remove_relative", type=inkex.Boolean, default=False, help="relative cmd")
|
pars.add_argument("--remove_relative", type=inkex.Boolean, default=False, help="relative cmd")
|
||||||
pars.add_argument("--remove_absolute", type=inkex.Boolean, default=False, help="absolute cmd")
|
pars.add_argument("--remove_absolute", type=inkex.Boolean, default=False, help="absolute cmd")
|
||||||
pars.add_argument("--remove_mixed", type=inkex.Boolean, default=False, help="mixed cmd (relative + absolute)")
|
pars.add_argument("--remove_mixed", type=inkex.Boolean, default=False, help="mixed cmd (relative + absolute)")
|
||||||
@ -597,8 +622,13 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
pars.add_argument("--remove_opened", type=inkex.Boolean, default=False, help="opened")
|
pars.add_argument("--remove_opened", type=inkex.Boolean, default=False, help="opened")
|
||||||
pars.add_argument("--remove_closed", type=inkex.Boolean, default=False, help="closed")
|
pars.add_argument("--remove_closed", type=inkex.Boolean, default=False, help="closed")
|
||||||
pars.add_argument("--remove_self_intersecting", type=inkex.Boolean, default=False, help="self-intersecting")
|
pars.add_argument("--remove_self_intersecting", type=inkex.Boolean, default=False, help="self-intersecting")
|
||||||
|
#Removing - Applying to sub split lines only
|
||||||
|
pars.add_argument("--filter_subsplit_collinear", type=inkex.Boolean, default=True, help="Removes any duplicates by merging (multiple) overlapping line segments into longer lines. Not possible to apply for original paths because this routine does not support bezier type paths.")
|
||||||
|
pars.add_argument("--filter_subsplit_collinear_action", default="remove", help="What to do with collinear overlapping lines?")
|
||||||
|
#Removing - Applying to original paths only
|
||||||
|
pars.add_argument("--keep_original_after_split_trim", type=inkex.Boolean, default=False, help="Keep original paths after sub splitting / trimming")
|
||||||
|
|
||||||
#Scanning - Highlighting
|
#Highlighting - Applying to original paths and sub split lines
|
||||||
pars.add_argument("--highlight_relative", type=inkex.Boolean, default=False, help="relative cmd paths")
|
pars.add_argument("--highlight_relative", type=inkex.Boolean, default=False, help="relative cmd paths")
|
||||||
pars.add_argument("--highlight_absolute", type=inkex.Boolean, default=False, help="absolute cmd paths")
|
pars.add_argument("--highlight_absolute", type=inkex.Boolean, default=False, help="absolute cmd paths")
|
||||||
pars.add_argument("--highlight_mixed", type=inkex.Boolean, default=False, help="mixed cmd (relative + absolute) paths")
|
pars.add_argument("--highlight_mixed", type=inkex.Boolean, default=False, help="mixed cmd (relative + absolute) paths")
|
||||||
@ -606,17 +636,22 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
pars.add_argument("--highlight_beziers", type=inkex.Boolean, default=False, help="bezier paths")
|
pars.add_argument("--highlight_beziers", type=inkex.Boolean, default=False, help="bezier paths")
|
||||||
pars.add_argument("--highlight_opened", type=inkex.Boolean, default=False, help="opened paths")
|
pars.add_argument("--highlight_opened", type=inkex.Boolean, default=False, help="opened paths")
|
||||||
pars.add_argument("--highlight_closed", type=inkex.Boolean, default=False, help="closed paths")
|
pars.add_argument("--highlight_closed", type=inkex.Boolean, default=False, help="closed paths")
|
||||||
|
#Highlighting - Applying to sub split lines only
|
||||||
|
pars.add_argument("--draw_subsplit", type=inkex.Boolean, default=False, help="Draw sub split lines (polylines)")
|
||||||
|
pars.add_argument("--highlight_duplicates", type=inkex.Boolean, default=False, help="duplicates (only applies to sub split lines)")
|
||||||
|
pars.add_argument("--highlight_merges", type=inkex.Boolean, default=False, help="merges (only applies to sub split lines)")
|
||||||
|
#Highlighting - Intersection points
|
||||||
pars.add_argument("--highlight_self_intersecting", type=inkex.Boolean, default=False, help="self-intersecting paths")
|
pars.add_argument("--highlight_self_intersecting", type=inkex.Boolean, default=False, help="self-intersecting paths")
|
||||||
pars.add_argument("--visualize_self_intersections", type=inkex.Boolean, default=False, help="self-intersecting path points")
|
pars.add_argument("--visualize_self_intersections", type=inkex.Boolean, default=False, help="self-intersecting path points")
|
||||||
pars.add_argument("--visualize_global_intersections", type=inkex.Boolean, default=False, help="global intersection points")
|
pars.add_argument("--visualize_global_intersections", type=inkex.Boolean, default=False, help="global intersection points")
|
||||||
|
|
||||||
#Settings - Trimming
|
#Trimming - General trimming settings
|
||||||
pars.add_argument("--draw_trimmed", type=inkex.Boolean, default=False, help="Draw trimmed lines")
|
pars.add_argument("--draw_trimmed", type=inkex.Boolean, default=False, help="Draw trimmed lines")
|
||||||
pars.add_argument("--combine_nonintersects", type=inkex.Boolean, default=True, help="Combine non-intersected lines")
|
pars.add_argument("--combine_nonintersects", type=inkex.Boolean, default=True, help="Combine non-intersected lines")
|
||||||
pars.add_argument("--remove_duplicates", type=inkex.Boolean, default=True, help="Remove duplicate trim lines")
|
pars.add_argument("--remove_trim_duplicates", type=inkex.Boolean, default=True, help="Remove duplicate trim lines")
|
||||||
pars.add_argument("--reverse_removal_order", type=inkex.Boolean, default=False, help="Reverses the order of removal. Relevant for keeping certain styles of elements")
|
pars.add_argument("--reverse_trim_removal_order", type=inkex.Boolean, default=False, help="Reverses the order of removal. Relevant for keeping certain styles of elements")
|
||||||
pars.add_argument("--keep_original_after_trim", type=inkex.Boolean, default=False, help="Keep original paths after trimming")
|
pars.add_argument("--remove_subsplit_after_trimming", type=inkex.Boolean, default=True, help="Remove sub split lines after trimming")
|
||||||
|
#Trimming - Bentley-Ottmann sweep line settings
|
||||||
pars.add_argument("--bent_ott_use_ignore_segment_endings", type=inkex.Boolean, default=True, help="Whether to ignore intersections of line segments when both their end points form the intersection point")
|
pars.add_argument("--bent_ott_use_ignore_segment_endings", type=inkex.Boolean, default=True, help="Whether to ignore intersections of line segments when both their end points form the intersection point")
|
||||||
pars.add_argument("--bent_ott_use_debug", type=inkex.Boolean, default=False)
|
pars.add_argument("--bent_ott_use_debug", type=inkex.Boolean, default=False)
|
||||||
pars.add_argument("--bent_ott_use_verbose", type=inkex.Boolean, default=False)
|
pars.add_argument("--bent_ott_use_verbose", type=inkex.Boolean, default=False)
|
||||||
@ -624,16 +659,9 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
pars.add_argument("--bent_ott_use_vertical", type=inkex.Boolean, default=True)
|
pars.add_argument("--bent_ott_use_vertical", type=inkex.Boolean, default=True)
|
||||||
pars.add_argument("--bent_ott_number_type", default="native")
|
pars.add_argument("--bent_ott_number_type", default="native")
|
||||||
|
|
||||||
#Style - General Style
|
#Colors
|
||||||
pars.add_argument("--strokewidth", type=float, default=1.0, help="Stroke width (px)")
|
|
||||||
pars.add_argument("--dotsize_intersections", type=int, default=30, help="Dot size (px) for self-intersecting and global intersection points")
|
|
||||||
pars.add_argument("--removefillsetstroke", type=inkex.Boolean, default=False, help="Remove fill and define stroke for original paths")
|
|
||||||
pars.add_argument("--bezier_trimming", type=inkex.Boolean, default=False, help="If true we try to use the calculated t parameters from intersection points to receive splitted bezier curves")
|
|
||||||
pars.add_argument("--apply_style_to_subsplits", type=inkex.Boolean, default=True, help="Apply highlighting styles to sub split lines.")
|
|
||||||
pars.add_argument("--apply_style_to_trimmed", type=inkex.Boolean, default=True, help="Apply original path style to trimmed lines")
|
|
||||||
|
|
||||||
#Style - Scanning Colors
|
|
||||||
pars.add_argument("--color_subsplit", type=Color, default='1630897151', help="sub split lines")
|
pars.add_argument("--color_subsplit", type=Color, default='1630897151', help="sub split lines")
|
||||||
|
#Colors - path structure
|
||||||
pars.add_argument("--color_relative", type=Color, default='3419879935', help="relative cmd paths")
|
pars.add_argument("--color_relative", type=Color, default='3419879935', help="relative cmd paths")
|
||||||
pars.add_argument("--color_absolute", type=Color, default='1592519679', help="absolute cmd paths")
|
pars.add_argument("--color_absolute", type=Color, default='1592519679', help="absolute cmd paths")
|
||||||
pars.add_argument("--color_mixed", type=Color, default='3351636735', help="mixed cmd (relative + absolute) paths")
|
pars.add_argument("--color_mixed", type=Color, default='3351636735', help="mixed cmd (relative + absolute) paths")
|
||||||
@ -641,11 +669,14 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
pars.add_argument("--color_bezier", type=Color, default='258744063', help="bezier paths")
|
pars.add_argument("--color_bezier", type=Color, default='258744063', help="bezier paths")
|
||||||
pars.add_argument("--color_opened", type=Color, default='4012452351', help="opened paths")
|
pars.add_argument("--color_opened", type=Color, default='4012452351', help="opened paths")
|
||||||
pars.add_argument("--color_closed", type=Color, default='2330080511', help="closed paths")
|
pars.add_argument("--color_closed", type=Color, default='2330080511', help="closed paths")
|
||||||
|
#Colors - duplicates and merges
|
||||||
|
pars.add_argument("--color_duplicates", type=Color, default='897901823', help="duplicates")
|
||||||
|
pars.add_argument("--color_merges", type=Color, default='869366527', help="merges")
|
||||||
|
#Colors - intersections
|
||||||
pars.add_argument("--color_self_intersecting_paths", type=Color, default='2593756927', help="self-intersecting paths")
|
pars.add_argument("--color_self_intersecting_paths", type=Color, default='2593756927', help="self-intersecting paths")
|
||||||
pars.add_argument("--color_self_intersections", type=Color, default='6320383', help="self-intersecting path points")
|
pars.add_argument("--color_self_intersections", type=Color, default='6320383', help="self-intersecting path points")
|
||||||
pars.add_argument("--color_global_intersections", type=Color, default='4239343359', help="global intersection points")
|
pars.add_argument("--color_global_intersections", type=Color, default='4239343359', help="global intersection points")
|
||||||
|
#Colors - trimming
|
||||||
#Style - Trimming Color
|
|
||||||
pars.add_argument("--color_trimmed", type=Color, default='1923076095', help="trimmed lines")
|
pars.add_argument("--color_trimmed", type=Color, default='1923076095', help="trimmed lines")
|
||||||
pars.add_argument("--color_combined", type=Color, default='3227634687', help="non-intersected lines")
|
pars.add_argument("--color_combined", type=Color, default='3227634687', help="non-intersected lines")
|
||||||
pars.add_argument("--color_nonintersected", type=Color, default='3045284607', help="non-intersected paths")
|
pars.add_argument("--color_nonintersected", type=Color, default='3045284607', help="non-intersected paths")
|
||||||
@ -658,6 +689,20 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
if so.break_apart is True and so.show_debug is True:
|
if so.break_apart is True and so.show_debug is True:
|
||||||
self.msg("Warning: 'Break apart input' setting is enabled. Cannot check accordingly for relative, absolute or mixed paths for breaked elements (they are always absolute)!")
|
self.msg("Warning: 'Break apart input' setting is enabled. Cannot check accordingly for relative, absolute or mixed paths for breaked elements (they are always absolute)!")
|
||||||
|
|
||||||
|
#some configuration dependecies
|
||||||
|
if so.highlight_self_intersecting is True or \
|
||||||
|
so.highlight_duplicates is True or \
|
||||||
|
so.highlight_merges is True:
|
||||||
|
so.draw_subsplit = True
|
||||||
|
|
||||||
|
if so.highlight_duplicates is True or \
|
||||||
|
so.highlight_merges is True:
|
||||||
|
so.filter_subsplit_collinear = True
|
||||||
|
|
||||||
|
if so.filter_subsplit_collinear is True: #this is a must.
|
||||||
|
#if so.draw_subsplit is disabled bu we filter sub split lines and follow with trim operation we lose a lot of elements which may not be deleted!
|
||||||
|
so.draw_subsplit = True
|
||||||
|
|
||||||
#some constant stuff / styles
|
#some constant stuff / styles
|
||||||
relativePathStyle = {'stroke': str(so.color_relative), 'fill': 'none', 'stroke-width': so.strokewidth}
|
relativePathStyle = {'stroke': str(so.color_relative), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
absolutePathStyle = {'stroke': str(so.color_absolute), 'fill': 'none', 'stroke-width': so.strokewidth}
|
absolutePathStyle = {'stroke': str(so.color_absolute), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
@ -666,6 +711,8 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
bezierPathStyle = {'stroke': str(so.color_bezier), 'fill': 'none', 'stroke-width': so.strokewidth}
|
bezierPathStyle = {'stroke': str(so.color_bezier), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
openPathStyle = {'stroke': str(so.color_opened), 'fill': 'none', 'stroke-width': so.strokewidth}
|
openPathStyle = {'stroke': str(so.color_opened), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
closedPathStyle = {'stroke': str(so.color_closed), 'fill': 'none', 'stroke-width': so.strokewidth}
|
closedPathStyle = {'stroke': str(so.color_closed), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
|
duplicatesPathStyle = {'stroke': str(so.color_duplicates), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
|
mergesPathStyle = {'stroke': str(so.color_merges), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
selfIntersectingPathStyle = {'stroke': str(so.color_self_intersecting_paths), 'fill': 'none', 'stroke-width': so.strokewidth}
|
selfIntersectingPathStyle = {'stroke': str(so.color_self_intersecting_paths), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
basicSubSplitLineStyle = {'stroke': str(so.color_subsplit), 'fill': 'none', 'stroke-width': so.strokewidth}
|
basicSubSplitLineStyle = {'stroke': str(so.color_subsplit), 'fill': 'none', 'stroke-width': so.strokewidth}
|
||||||
|
|
||||||
@ -778,7 +825,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
subSplitId = "{}-{}-{}".format(idPrefixSubSplit, originalPathId, i)
|
subSplitId = "{}-{}-{}".format(idPrefixSubSplit, originalPathId, i)
|
||||||
line = inkex.PathElement(id=subSplitId)
|
line = inkex.PathElement(id=subSplitId)
|
||||||
#apply line path with composed negative transform from parent element
|
#apply line path with composed negative transform from parent element
|
||||||
line.attrib['d'] = 'M {},{} L {},{}'.format(x1, y1, x2, y2) #we set the path of trimLine using 'd' attribute because if we use trimLine.path the decimals get cut off unwantedly
|
line.attrib['d'] = 'M {},{} L {},{}'.format(x1, y1, x2, y2) #we set the path of Line using 'd' attribute because if we use trimLine.path the decimals get cut off unwantedly
|
||||||
#line.path = [['M', [x1, y1]], ['L', [x2, y2]]]
|
#line.path = [['M', [x1, y1]], ['L', [x2, y2]]]
|
||||||
if pathElement.getparent() != self.svg.root and pathElement.getparent() != None:
|
if pathElement.getparent() != self.svg.root and pathElement.getparent() != None:
|
||||||
line.path = line.path.transform(-pathElement.getparent().composed_transform())
|
line.path = line.path.transform(-pathElement.getparent().composed_transform())
|
||||||
@ -792,7 +839,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
line.attrib['originalPathStyle'] = str(pathElement.style)
|
line.attrib['originalPathStyle'] = str(pathElement.style)
|
||||||
subSplitLineArray.append(line)
|
subSplitLineArray.append(line)
|
||||||
|
|
||||||
if so.apply_style_to_subsplits is True:
|
if so.subsplit_style == "apply_from_highlightings":
|
||||||
if line.attrib['originalPathIsRelative'] == 'True':
|
if line.attrib['originalPathIsRelative'] == 'True':
|
||||||
if so.highlight_relative is True:
|
if so.highlight_relative is True:
|
||||||
line.style = relativePathStyle
|
line.style = relativePathStyle
|
||||||
@ -818,6 +865,8 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
else:
|
else:
|
||||||
if so.highlight_opened is True:
|
if so.highlight_opened is True:
|
||||||
line.style = openPathStyle
|
line.style = openPathStyle
|
||||||
|
elif so.subsplit_style == "apply_from_original":
|
||||||
|
line.style = line.attrib['originalPathStyle']
|
||||||
|
|
||||||
if so.draw_subsplit is True:
|
if so.draw_subsplit is True:
|
||||||
subSplitLineGroup.add(line)
|
subSplitLineGroup.add(line)
|
||||||
@ -830,7 +879,6 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
isSelfIntersecting = True
|
isSelfIntersecting = True
|
||||||
if so.show_debug is True:
|
if so.show_debug is True:
|
||||||
self.msg("{} in {} intersects itself with {} intersections!".format(subSplitId, originalPathId, len(selfIntersectionPoints)))
|
self.msg("{} in {} intersects itself with {} intersections!".format(subSplitId, originalPathId, len(selfIntersectionPoints)))
|
||||||
if so.draw_subsplit is True:
|
|
||||||
if so.highlight_self_intersecting is True:
|
if so.highlight_self_intersecting is True:
|
||||||
for subSplitLine in subSplitLineGroup:
|
for subSplitLine in subSplitLineGroup:
|
||||||
subSplitLine.style = selfIntersectingPathStyle #adjusts line color
|
subSplitLine.style = selfIntersectingPathStyle #adjusts line color
|
||||||
@ -838,7 +886,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
if so.remove_self_intersecting:
|
if so.remove_self_intersecting:
|
||||||
subSplitLineGroup.delete()
|
subSplitLineGroup.delete()
|
||||||
if so.visualize_self_intersections is True: #draw points (circles)
|
if so.visualize_self_intersections is True: #draw points (circles)
|
||||||
self.visualize_self_intersections(pathElement, selfIntersectionPoints)
|
selfIntersectionGroup = self.visualize_self_intersections(pathElement, selfIntersectionPoints)
|
||||||
|
|
||||||
#delete self-intersecting sub split lines and orginal paths
|
#delete self-intersecting sub split lines and orginal paths
|
||||||
if so.remove_self_intersecting:
|
if so.remove_self_intersecting:
|
||||||
@ -888,31 +936,81 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
if so.show_debug is True:
|
if so.show_debug is True:
|
||||||
self.msg("sub split line count: {}".format(len(subSplitLineArray)))
|
self.msg("sub split line count: {}".format(len(subSplitLineArray)))
|
||||||
|
|
||||||
if so.remove_subsplit_collinear is True:
|
'''
|
||||||
|
check for collinear lines and apply filters to remove or regroup and to restyle them
|
||||||
|
Run this action only if one of the options requires it (to avoid useless calculation cycles)
|
||||||
|
'''
|
||||||
|
if so.filter_subsplit_collinear is True or \
|
||||||
|
so.highlight_duplicates is True or \
|
||||||
|
so.highlight_merges is True:
|
||||||
if so.show_debug is True: self.msg("filtering collinear overlapping lines / duplicate lines")
|
if so.show_debug is True: self.msg("filtering collinear overlapping lines / duplicate lines")
|
||||||
if len(subSplitLineArray) > 0:
|
if len(subSplitLineArray) > 0:
|
||||||
output_set, dropped_ids = self.filter_collinear(subSplitLineArray)
|
output_set, dropped_ids = self.filter_collinear(subSplitLineArray)
|
||||||
|
deleteIndices = []
|
||||||
|
deleteIndice = 0
|
||||||
for subSplitLine in subSplitLineArray:
|
for subSplitLine in subSplitLineArray:
|
||||||
|
'''
|
||||||
|
Replace the overlapping items with the new merged output
|
||||||
|
'''
|
||||||
|
for output in output_set:
|
||||||
|
if output['id'] == subSplitLine.attrib['id']:
|
||||||
|
originalSplitLinePath = subSplitLine.path
|
||||||
|
output_line = 'M {},{} L {},{}'.format(
|
||||||
|
output['p0'][0], output['p0'][1], output['p1'][0], output['p1'][1])
|
||||||
|
output_line_reversed = 'M {},{} L {},{}'.format(
|
||||||
|
output['p1'][0], output['p1'][1], output['p0'][0], output['p0'][1])
|
||||||
|
subSplitLine.attrib['d'] = output_line #we set the path using 'd' attribute because if we use trimLine.path the decimals get cut off unwantedly
|
||||||
|
mergedSplitLinePath = subSplitLine.path
|
||||||
|
mergedSplitLinePathReversed = Path(output_line_reversed)
|
||||||
|
#subSplitLine.path = [['M', output['p0']], ['L', output['p1']]]
|
||||||
|
#self.msg("composed_transform = {}".format(output['composed_transform']))
|
||||||
|
#subSplitLine.transform = Transform(-output['composed_transform']) * subSplitLine.transform
|
||||||
|
|
||||||
|
subSplitLine.path = subSplitLine.path.transform(-output['composed_transform'])
|
||||||
|
if so.highlight_merges is True:
|
||||||
|
if originalSplitLinePath != mergedSplitLinePath and \
|
||||||
|
originalSplitLinePath != mergedSplitLinePathReversed: #if the path changed we are going to highlight it
|
||||||
|
subSplitLine.style = mergesPathStyle
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Delete or move sub split lines which are overlapping
|
||||||
|
'''
|
||||||
ssl_id = subSplitLine.get('id')
|
ssl_id = subSplitLine.get('id')
|
||||||
if ssl_id in dropped_ids:
|
if ssl_id in dropped_ids:
|
||||||
ssl_parent = subSplitLine.getparent()
|
if so.highlight_duplicates is True:
|
||||||
subSplitLine.delete() #delete the line
|
subSplitLine.style = duplicatesPathStyle
|
||||||
|
|
||||||
#and delete the containg group if empty
|
if so.filter_subsplit_collinear is True:
|
||||||
|
ssl_parent = subSplitLine.getparent()
|
||||||
|
if so.filter_subsplit_collinear_action == "remove":
|
||||||
|
if self.options.show_debug is True:
|
||||||
|
self.msg("Deleting sub split line {}".format(subSplitLine.get('id')))
|
||||||
|
subSplitLine.delete() #delete the line from XML tree
|
||||||
|
deleteIndices.append(deleteIndice) #store this id to remove it from stupid subSplitLineArray later
|
||||||
|
elif so.filter_subsplit_collinear_action == "separate_group":
|
||||||
|
if self.options.show_debug is True:
|
||||||
|
self.msg("Moving sub split line {}".format(subSplitLine.get('id')))
|
||||||
|
originalPathId = subSplitLine.attrib['originalPathId']
|
||||||
|
collinearGroupId = '{}-{}'.format(collinearVerb, originalPathId)
|
||||||
|
originalPathElement = self.svg.getElementById(originalPathId)
|
||||||
|
collinearGroup = self.find_group(collinearGroupId)
|
||||||
|
if collinearGroup is None:
|
||||||
|
collinearGroup = originalPathElement.getparent().add(inkex.Group(id=collinearGroupId))
|
||||||
|
collinearGroup.append(subSplitLine) #move to that group
|
||||||
|
#and delete the containg group if empty (can happen in "remove" or "separate_group" constellation
|
||||||
if ssl_parent is not None and len(ssl_parent) == 0:
|
if ssl_parent is not None and len(ssl_parent) == 0:
|
||||||
if self.options.show_debug is True:
|
if self.options.show_debug is True:
|
||||||
self.msg("Deleting group {}".format(ssl_parent.get('id')))
|
self.msg("Deleting group {}".format(ssl_parent.get('id')))
|
||||||
ssl_parent.delete()
|
ssl_parent.delete()
|
||||||
# and now we replace the overlapping items with the new merged output
|
|
||||||
for output in output_set:
|
deleteIndice += 1 #end the loop by incrementing +1
|
||||||
if output['id'] == subSplitLine.attrib['id']:
|
|
||||||
#self.msg(output['p0'])
|
#shrink the sub split line array to kick out all unrequired indices
|
||||||
subSplitLine.attrib['d'] = 'M {},{} L {},{}'.format(
|
for deleteIndice in sorted(deleteIndices, reverse=True):
|
||||||
output['p0'][0], output['p0'][1], output['p1'][0], output['p1'][1]) #we set the path of trimLine using 'd' attribute because if we use trimLine.path the decimals get cut off unwantedly
|
if self.options.show_debug is True:
|
||||||
#subSplitLine.path = [['M', output['p0']], ['L', output['p1']]]
|
self.msg("Deleting index {} from subSplitLineArray".format(deleteIndice))
|
||||||
#self.msg("composed_transform = {}".format(output['composed_transform']))
|
del subSplitLineArray[deleteIndice]
|
||||||
#subSplitLine.transform = Transform(-output['composed_transform']) * subSplitLine.transform
|
|
||||||
subSplitLine.path = subSplitLine.path.transform(-output['composed_transform'])
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
now we intersect the sub split lines to find the global intersection points using Bentley-Ottmann algorithm (contains self-intersections too!)
|
now we intersect the sub split lines to find the global intersection points using Bentley-Ottmann algorithm (contains self-intersections too!)
|
||||||
@ -950,7 +1048,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
for subSplitLine in subSplitLineArray:
|
for subSplitLine in subSplitLineArray:
|
||||||
csp = subSplitLine.path.to_arrays()
|
csp = subSplitLine.path.to_arrays()
|
||||||
lineString = [(csp[0][1][0], csp[0][1][1]), (csp[1][1][0], csp[1][1][1])]
|
lineString = [(csp[0][1][0], csp[0][1][1]), (csp[1][1][0], csp[1][1][1])]
|
||||||
if so.remove_duplicates is True:
|
if so.remove_trim_duplicates is True:
|
||||||
if lineString not in allSubSplitLineStrings:
|
if lineString not in allSubSplitLineStrings:
|
||||||
allSubSplitLineStrings.append(lineString)
|
allSubSplitLineStrings.append(lineString)
|
||||||
else:
|
else:
|
||||||
@ -990,19 +1088,23 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
|
|||||||
if so.show_debug is True: self.msg("trimming beziers - not working yet")
|
if so.show_debug is True: self.msg("trimming beziers - not working yet")
|
||||||
self.trim_bezier(allTrimGroups)
|
self.trim_bezier(allTrimGroups)
|
||||||
|
|
||||||
if so.remove_duplicates is True:
|
if so.remove_trim_duplicates is True:
|
||||||
if so.show_debug is True: self.msg("checking for duplicate trim lines and deleting them")
|
if so.show_debug is True: self.msg("checking for duplicate trim lines and deleting them")
|
||||||
self.remove_duplicates(allTrimGroups)
|
self.remove_trim_duplicates(allTrimGroups)
|
||||||
|
|
||||||
if so.combine_nonintersects is True:
|
if so.combine_nonintersects is True:
|
||||||
if so.show_debug is True: self.msg("glueing together all non-intersected sub split lines to larger path structures again (cleaning up)")
|
if so.show_debug is True: self.msg("glueing together all non-intersected sub split lines to larger path structures again (cleaning up)")
|
||||||
self.combine_nonintersects(allTrimGroups)
|
self.combine_nonintersects(allTrimGroups)
|
||||||
|
|
||||||
#clean original paths if selected. This option is explicitely independent from remove_open, remove_closed
|
if so.remove_subsplit_after_trimming is True:
|
||||||
if so.keep_original_after_trim is False:
|
if so.show_debug is True: self.msg("removing unwanted subsplit lines after trimming")
|
||||||
if so.show_debug is True: self.msg("cleaning original paths")
|
for subSplitLine in subSplitLineArray:
|
||||||
for pathElement in pathElements:
|
ssl_parent = subSplitLine.getparent()
|
||||||
pathElement.delete()
|
subSplitLine.delete()
|
||||||
|
if ssl_parent is not None and len(ssl_parent) == 0:
|
||||||
|
if self.options.show_debug is True:
|
||||||
|
self.msg("Deleting group {}".format(ssl_parent.get('id')))
|
||||||
|
ssl_parent.delete()
|
||||||
|
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
self.msg("Error calculating global intersections.\n\
|
self.msg("Error calculating global intersections.\n\
|
||||||
@ -1012,5 +1114,11 @@ You can try to fix this by:\n\
|
|||||||
- reduce or raise the 'flatness' setting (if quantization option is used at all; default is 0.100).")
|
- reduce or raise the 'flatness' setting (if quantization option is used at all; default is 0.100).")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
#clean original paths if selected.
|
||||||
|
if so.keep_original_after_split_trim is False:
|
||||||
|
if so.show_debug is True: self.msg("cleaning original paths after sub splitting / trimming")
|
||||||
|
for pathElement in pathElements:
|
||||||
|
pathElement.delete()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ContourScannerAndTrimmer().run()
|
ContourScannerAndTrimmer().run()
|
Reference in New Issue
Block a user