adjustments in lasercheck
This commit is contained in:
parent
d76b6faa20
commit
c129af2b80
@ -18,8 +18,13 @@
|
|||||||
<param name="bbox_offset" type="float" min="0.000" max="9999.000" precision="3" gui-text="Minimum required offset (mm)">5.000</param>
|
<param name="bbox_offset" type="float" min="0.000" max="9999.000" precision="3" gui-text="Minimum required offset (mm)">5.000</param>
|
||||||
<separator/>
|
<separator/>
|
||||||
<param name="cutting_estimation" type="bool" gui-text="Cutting time estimation">false</param>
|
<param name="cutting_estimation" type="bool" gui-text="Cutting time estimation">false</param>
|
||||||
|
<param name="cutting_speedfactors" type="string" gui-text="Speed factors (%)" gui-description="Whitespace separated list of speeds (%) to calculate for">100 90 80 70 60 50 40 30 20 10 9 8 7 6 5 4 3 2 1</param>
|
||||||
|
<separator/>
|
||||||
<param name="elements_outside_canvas" type="bool" gui-text="Elements outside canvas">false</param>
|
<param name="elements_outside_canvas" type="bool" gui-text="Elements outside canvas">false</param>
|
||||||
|
<separator/>
|
||||||
<param name="groups_and_layers" type="bool" gui-text="Groups and layers">false</param>
|
<param name="groups_and_layers" type="bool" gui-text="Groups and layers">false</param>
|
||||||
|
<param name="nest_depth_max" type="int" min="0" max="9999" gui-text="Max. allowed depth">2</param>
|
||||||
|
<separator/>
|
||||||
<param name="clones" type="bool" gui-text="Clones">false</param>
|
<param name="clones" type="bool" gui-text="Clones">false</param>
|
||||||
<param name="clippaths" type="bool" gui-text="Clippings">false</param>
|
<param name="clippaths" type="bool" gui-text="Clippings">false</param>
|
||||||
<param name="images" type="bool" gui-text="Images">false</param>
|
<param name="images" type="bool" gui-text="Images">false</param>
|
||||||
@ -28,12 +33,12 @@
|
|||||||
<param name="lowlevelstrokes" type="bool" gui-text="Low level strokes">false</param>
|
<param name="lowlevelstrokes" type="bool" gui-text="Low level strokes">false</param>
|
||||||
<separator/>
|
<separator/>
|
||||||
<param name="stroke_colors" type="bool" gui-text="Stroke colors">false</param>
|
<param name="stroke_colors" type="bool" gui-text="Stroke colors">false</param>
|
||||||
<param name="stroke_colors_max" type="int" gui-text="Max. allowed">3</param>
|
<param name="stroke_colors_max" type="int" min="0" max="9999" gui-text="Max. allowed">3</param>
|
||||||
</vbox>
|
</vbox>
|
||||||
<separator/>
|
<separator/>
|
||||||
<vbox>
|
<vbox>
|
||||||
<param name="stroke_widths" type="bool" gui-text="Stroke widths">false</param>
|
<param name="stroke_widths" type="bool" gui-text="Stroke widths">false</param>
|
||||||
<param name="stroke_widths_max" type="int" gui-text="Max. allowed">1</param>
|
<param name="stroke_widths_max" type="int" min="0" max="9999" gui-text="Max. allowed">1</param>
|
||||||
<separator/>
|
<separator/>
|
||||||
<param name="stroke_opacities" type="bool" gui-text="Stroke opacities">false</param>
|
<param name="stroke_opacities" type="bool" gui-text="Stroke opacities">false</param>
|
||||||
<param name="cosmestic_dashes" type="bool" gui-text="Cosmetic dash styles">false</param>
|
<param name="cosmestic_dashes" type="bool" gui-text="Cosmetic dash styles">false</param>
|
||||||
@ -66,6 +71,7 @@
|
|||||||
<param name="price_per_minute_gross" type="float" min="0.0" max="9999.0" precision="2" gui-text="Price/minute € (gross)">2.0</param>
|
<param name="price_per_minute_gross" type="float" min="0.0" max="9999.0" precision="2" gui-text="Price/minute € (gross)">2.0</param>
|
||||||
<param name="round_times" type="bool" gui-text="Round up to 30/60 seconds" gui-description="For pricing">true</param>
|
<param name="round_times" type="bool" gui-text="Round up to 30/60 seconds" gui-description="For pricing">true</param>
|
||||||
<param name="vector_grid_xy" type="float" min="0.0" max="9999.0" precision="2" gui-text="Vector grid (mm)">12.0</param>
|
<param name="vector_grid_xy" type="float" min="0.0" max="9999.0" precision="2" gui-text="Vector grid (mm)">12.0</param>
|
||||||
|
<param name="co2_power" type="float" min="0.0" max="9999.0" precision="2" gui-text="CO2 power (watts)">60.0</param>
|
||||||
</page>
|
</page>
|
||||||
<page name="tab_about" gui-text="About">
|
<page name="tab_about" gui-text="About">
|
||||||
<label appearance="header">Laser Check</label>
|
<label appearance="header">Laser Check</label>
|
||||||
|
@ -12,13 +12,10 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
ToDos:
|
ToDos:
|
||||||
- check for elements which have the attribute "display:none" (some groups have this)
|
|
||||||
- inx:
|
- inx:
|
||||||
- set speed manually or pick machine (epilog) - travel and cut speed are prefilled then
|
- set speed manually or pick machine (epilog) - travel and cut speed are prefilled then
|
||||||
- calculate cut estimation with linear or non-linear (epilog) speeds > select formula or like this
|
- calculate cut estimation with linear or non-linear (epilog) speeds > select formula or like this
|
||||||
- select time estimation for specific speed percentage or for all speeds (100,90, ...)
|
|
||||||
- select material (parameters -> how to???)
|
- select material (parameters -> how to???)
|
||||||
- select power of CO² source
|
|
||||||
- add fields for additional costs like configuring the machine or grabbing parts out of the machine (weeding), etc.
|
- add fields for additional costs like configuring the machine or grabbing parts out of the machine (weeding), etc.
|
||||||
- add mode select: cut, engrave
|
- add mode select: cut, engrave
|
||||||
- Handlungsempfehlungen einbauen
|
- Handlungsempfehlungen einbauen
|
||||||
@ -44,7 +41,6 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
- run as script to generate quick results for users
|
- run as script to generate quick results for users
|
||||||
- check for old styles which should be upgraded (cleanup styles tool)
|
- check for old styles which should be upgraded (cleanup styles tool)
|
||||||
- check for elements which have no style attribute (should be created) -> (cleanup styles tool)
|
- check for elements which have no style attribute (should be created) -> (cleanup styles tool)
|
||||||
- migrate styles from groups/layers to path styles (cleanup styles tool)
|
|
||||||
- self-intersecting paths
|
- self-intersecting paths
|
||||||
- number of parts (isles) to weed in total - this is an indicator for manually picking work; if we add bridges we have less work
|
- number of parts (isles) to weed in total - this is an indicator for manually picking work; if we add bridges we have less work
|
||||||
- number of parts which are smaller than vector grid
|
- number of parts which are smaller than vector grid
|
||||||
@ -67,6 +63,7 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
pars.add_argument('--job_time_offset', type=float, default=0.0)
|
pars.add_argument('--job_time_offset', type=float, default=0.0)
|
||||||
pars.add_argument('--price_per_minute_gross', type=float, default=2.0)
|
pars.add_argument('--price_per_minute_gross', type=float, default=2.0)
|
||||||
pars.add_argument('--vector_grid_xy', type=float, default=12.0) #TODO
|
pars.add_argument('--vector_grid_xy', type=float, default=12.0) #TODO
|
||||||
|
pars.add_argument('--co2_power', type=float, default=60.0) #TODO
|
||||||
pars.add_argument('--round_times', type=inkex.Boolean, default=True)
|
pars.add_argument('--round_times', type=inkex.Boolean, default=True)
|
||||||
|
|
||||||
pars.add_argument('--show_issues_only', type=inkex.Boolean, default=False)
|
pars.add_argument('--show_issues_only', type=inkex.Boolean, default=False)
|
||||||
@ -74,8 +71,10 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
pars.add_argument('--bbox', type=inkex.Boolean, default=False)
|
pars.add_argument('--bbox', type=inkex.Boolean, default=False)
|
||||||
pars.add_argument('--bbox_offset', type=float, default=5.000)
|
pars.add_argument('--bbox_offset', type=float, default=5.000)
|
||||||
pars.add_argument('--cutting_estimation', type=inkex.Boolean, default=False)
|
pars.add_argument('--cutting_estimation', type=inkex.Boolean, default=False)
|
||||||
|
pars.add_argument('--cutting_speedfactors', default="100 90 80 70 60 50 40 30 20 10 9 8 7 6 5 4 3 2 1")
|
||||||
pars.add_argument('--elements_outside_canvas', type=inkex.Boolean, default=False)
|
pars.add_argument('--elements_outside_canvas', type=inkex.Boolean, default=False)
|
||||||
pars.add_argument('--groups_and_layers', type=inkex.Boolean, default=False)
|
pars.add_argument('--groups_and_layers', type=inkex.Boolean, default=False)
|
||||||
|
pars.add_argument('--nest_depth_max', type=int, default=2)
|
||||||
pars.add_argument('--clones', type=inkex.Boolean, default=False)
|
pars.add_argument('--clones', type=inkex.Boolean, default=False)
|
||||||
pars.add_argument('--clippaths', type=inkex.Boolean, default=False)
|
pars.add_argument('--clippaths', type=inkex.Boolean, default=False)
|
||||||
pars.add_argument('--images', type=inkex.Boolean, default=False)
|
pars.add_argument('--images', type=inkex.Boolean, default=False)
|
||||||
@ -140,7 +139,7 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
like svg:defs, svg:desc, gradients, etc.
|
like svg:defs, svg:desc, gradients, etc.
|
||||||
'''
|
'''
|
||||||
nonShapes = []
|
nonShapes = []
|
||||||
shapes = []
|
shapes = [] #this may contains paths, rectangles, circles, groups and more
|
||||||
for element in selected:
|
for element in selected:
|
||||||
if not isinstance(element, inkex.ShapeElement):
|
if not isinstance(element, inkex.ShapeElement):
|
||||||
nonShapes.append(element)
|
nonShapes.append(element)
|
||||||
@ -232,6 +231,11 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
inkex.utils.debug("page height... fail: {:0.3f} mm".format(bb_height))
|
inkex.utils.debug("page height... fail: {:0.3f} mm".format(bb_height))
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
We check for possible deep nested groups/layers, empty groups/layers or groups/layers with styles.
|
||||||
|
We want to avoid styles at groups. Its better to style the elements like svg:path directly.
|
||||||
|
We can use "Cleanup Styles" extension to change this.
|
||||||
|
'''
|
||||||
if so.checks == "check_all" or so.groups_and_layers is True:
|
if so.checks == "check_all" or so.groups_and_layers is True:
|
||||||
inkex.utils.debug("\n---------- Groups and layers")
|
inkex.utils.debug("\n---------- Groups and layers")
|
||||||
global md
|
global md
|
||||||
@ -245,19 +249,23 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
maxDepth(self.document.getroot(), -1)
|
maxDepth(self.document.getroot(), -1)
|
||||||
if so.show_issues_only is False:
|
if so.show_issues_only is False:
|
||||||
inkex.utils.debug("Maximum group depth={}".format(md - 1))
|
inkex.utils.debug("Maximum group depth={}".format(md - 1))
|
||||||
if md - 1 > 2:
|
if md - 1 > so.nest_depth_max:
|
||||||
self.msg("Warning: this group depth might cause issues!")
|
inkex.utils.debug("Warning: maximum allowed group depth reached: {}".format(so.nest_depth_max))
|
||||||
groups = []
|
groups = []
|
||||||
layers = []
|
layers = []
|
||||||
|
styles = []
|
||||||
for element in selected:
|
for element in selected:
|
||||||
if element.tag == inkex.addNS('g','svg'):
|
if element.tag == inkex.addNS('g','svg'):
|
||||||
if element.get('inkscape:groupmode') == 'layer':
|
if element.get('inkscape:groupmode') == 'layer':
|
||||||
layers.append(element)
|
layers.append(element)
|
||||||
else:
|
else:
|
||||||
groups.append(element)
|
groups.append(element)
|
||||||
|
if element.style is not None:
|
||||||
|
styles.append(element)
|
||||||
if so.show_issues_only is False:
|
if so.show_issues_only is False:
|
||||||
inkex.utils.debug("{} groups in total".format(len(groups)))
|
inkex.utils.debug("{} groups in total".format(len(groups)))
|
||||||
inkex.utils.debug("{} layers in total".format(len(layers)))
|
inkex.utils.debug("{} layers in total".format(len(layers)))
|
||||||
|
inkex.utils.debug("{} groups/layers with style in total".format(len(styles)))
|
||||||
|
|
||||||
#check for empty groups
|
#check for empty groups
|
||||||
for group in groups:
|
for group in groups:
|
||||||
@ -269,6 +277,10 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
if len(layer) == 0:
|
if len(layer) == 0:
|
||||||
inkex.utils.debug("id={} is empty layer".format(layer.get('id')))
|
inkex.utils.debug("id={} is empty layer".format(layer.get('id')))
|
||||||
|
|
||||||
|
#check for groups/layers which have a style
|
||||||
|
for style in styles:
|
||||||
|
inkex.utils.debug("id={} has style".format(style.get('id')))
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Clones should be unlinked because they cause similar issues like transformations
|
Clones should be unlinked because they cause similar issues like transformations
|
||||||
'''
|
'''
|
||||||
@ -386,9 +398,7 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
strokeColors = []
|
strokeColors = []
|
||||||
for element in shapes:
|
for element in shapes:
|
||||||
strokeColor = element.style.get('stroke')
|
strokeColor = element.style.get('stroke')
|
||||||
if strokeColor is None or strokeColor == "none":
|
if strokeColor is not None and strokeColor not in strokeColors:
|
||||||
strokeColor = "none"
|
|
||||||
if strokeColor not in strokeColors:
|
|
||||||
strokeColors.append(strokeColor)
|
strokeColors.append(strokeColor)
|
||||||
if so.show_issues_only is False:
|
if so.show_issues_only is False:
|
||||||
inkex.utils.debug("{} different stroke colors in total".format(len(strokeColors)))
|
inkex.utils.debug("{} different stroke colors in total".format(len(strokeColors)))
|
||||||
@ -406,15 +416,13 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
strokeWidths = []
|
strokeWidths = []
|
||||||
for element in shapes:
|
for element in shapes:
|
||||||
strokeWidth = element.style.get('stroke-width')
|
strokeWidth = element.style.get('stroke-width')
|
||||||
if strokeWidth is None or strokeWidth == "none":
|
if strokeWidth is not None and strokeWidth not in strokeWidths:
|
||||||
strokeWidth = "none"
|
|
||||||
if strokeWidth not in strokeWidths:
|
|
||||||
strokeWidths.append(strokeWidth)
|
strokeWidths.append(strokeWidth)
|
||||||
if so.show_issues_only is False:
|
if so.show_issues_only is False:
|
||||||
inkex.utils.debug("{} different stroke widths in total".format(len(strokeWidths)))
|
inkex.utils.debug("{} different stroke widths in total".format(len(strokeWidths)))
|
||||||
if len(strokeWidths) > so.stroke_widths_max:
|
if len(strokeWidths) > so.stroke_widths_max:
|
||||||
for strokeWidth in strokeWidths:
|
for strokeWidth in strokeWidths:
|
||||||
swConverted = self.svg.uutounit(float(self.svg.unittouu(strokeWidth))) #possibly w/o units. we unify to some internal float
|
swConverted = self.svg.uutounit(float(self.svg.unittouu(strokeWidth))) #possibly w/o units. we unify to some internal float. The value "none" converts to 0.0
|
||||||
inkex.utils.debug("stroke width {}px ({}mm)".format(
|
inkex.utils.debug("stroke width {}px ({}mm)".format(
|
||||||
round(self.svg.uutounit(swConverted, "px"),4),
|
round(self.svg.uutounit(swConverted, "px"),4),
|
||||||
round(self.svg.uutounit(swConverted, "mm"),4),
|
round(self.svg.uutounit(swConverted, "mm"),4),
|
||||||
@ -431,9 +439,7 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
strokeDasharrays = []
|
strokeDasharrays = []
|
||||||
for element in shapes:
|
for element in shapes:
|
||||||
strokeDasharray = element.style.get('stroke-dasharray')
|
strokeDasharray = element.style.get('stroke-dasharray')
|
||||||
if strokeDasharray is None or strokeDasharray == "none":
|
if strokeDasharray is not None and strokeDasharray not in strokeDasharrays:
|
||||||
strokeDasharray = "none"
|
|
||||||
if strokeDasharray not in strokeDasharrays:
|
|
||||||
strokeDasharrays.append(strokeDasharray)
|
strokeDasharrays.append(strokeDasharray)
|
||||||
if so.show_issues_only is False:
|
if so.show_issues_only is False:
|
||||||
inkex.utils.debug("{} different stroke dash arrays in total".format(len(strokeDasharrays)))
|
inkex.utils.debug("{} different stroke dash arrays in total".format(len(strokeDasharrays)))
|
||||||
@ -615,9 +621,7 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
transparencies = []
|
transparencies = []
|
||||||
for element in shapes:
|
for element in shapes:
|
||||||
stroke_opacity = element.style.get('stroke-opacity')
|
stroke_opacity = element.style.get('stroke-opacity')
|
||||||
if stroke_opacity is None or stroke_opacity == "none":
|
if stroke_opacity is not stroke_opacity and stroke_opacity not in transparencies:
|
||||||
stroke_opacity = "none"
|
|
||||||
if stroke_opacity not in transparencies:
|
|
||||||
if stroke_opacity != "none":
|
if stroke_opacity != "none":
|
||||||
if float(stroke_opacity) < 1.0:
|
if float(stroke_opacity) < 1.0:
|
||||||
transparencies.append([element, stroke_opacity])
|
transparencies.append([element, stroke_opacity])
|
||||||
@ -716,22 +720,32 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
totalCuttingLength += stotal
|
totalCuttingLength += stotal
|
||||||
cuttingPathCount += 1
|
cuttingPathCount += 1
|
||||||
totalLength = totalCuttingLength + totalTravelLength
|
totalLength = totalCuttingLength + totalTravelLength
|
||||||
|
v_travel = so.max_travel_speed #this is always at maximum
|
||||||
inkex.utils.debug("total cutting paths={}".format(cuttingPathCount))
|
inkex.utils.debug("total cutting paths={}".format(cuttingPathCount))
|
||||||
inkex.utils.debug("total travel paths={}".format(travelPathCount))
|
inkex.utils.debug("total travel paths={}".format(travelPathCount))
|
||||||
inkex.utils.debug("(measured) cutting length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalCuttingLength), "mm"), self.svg.uutounit(str(totalCuttingLength), "mm")))
|
inkex.utils.debug("(measured) cutting length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalCuttingLength), "mm"), self.svg.uutounit(str(totalCuttingLength), "mm")))
|
||||||
inkex.utils.debug("(measured) travel length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalTravelLength), "mm"), self.svg.uutounit(str(totalTravelLength), "mm")))
|
inkex.utils.debug("(measured) travel length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalTravelLength), "mm"), self.svg.uutounit(str(totalTravelLength), "mm")))
|
||||||
inkex.utils.debug("(measured) total length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalLength), "mm"), self.svg.uutounit(str(totalLength), "mm")))
|
inkex.utils.debug("(measured) total length (mm) = {:0.2f} mm".format(self.svg.uutounit(str(totalLength), "mm"), self.svg.uutounit(str(totalLength), "mm")))
|
||||||
|
inkex.utils.debug("travel speed={:06.2f}mm/s".format(v_travel))
|
||||||
''' from https://www.epiloglaser.com/assets/downloads/fusion-material-settings.pdf
|
''' from https://www.epiloglaser.com/assets/downloads/fusion-material-settings.pdf
|
||||||
"Speed Settings: The speed setting scale of 1% to 100% is not linear –
|
"Speed Settings: The speed setting scale of 1% to 100% is not linear –
|
||||||
i.e. 100% speed will not be twice as fast as 50% speed. This non-linear
|
i.e. 100% speed will not be twice as fast as 50% speed. This non-linear
|
||||||
scale is very useful in compensating for the different factors that affect engraving time."
|
scale is very useful in compensating for the different factors that affect engraving time."
|
||||||
'''
|
'''
|
||||||
for speedFactor in [100,90,80,70,60,50,40,30,20,10,9,8,7,6,5,4,3,2,1]:
|
speedFactors = []
|
||||||
|
try:
|
||||||
|
for speed in re.findall(r"[+]?\d*\.\d+|\d+", self.options.cutting_speedfactors): #allow only positive values
|
||||||
|
if float(speed) > 0:
|
||||||
|
speedFactors.append(float(speed))
|
||||||
|
speedFactors = sorted(speedFactors)[::-1]
|
||||||
|
except:
|
||||||
|
inkex.utils.debug("Error parsing cutting estimation speeds. Please try again!")
|
||||||
|
exit(1)
|
||||||
|
for speedFactor in speedFactors:
|
||||||
speedFactorR = speedFactor / 100.0
|
speedFactorR = speedFactor / 100.0
|
||||||
adjusted_speed = 480.0 / so.max_cutting_speed #empiric - found out by trying for hours ...
|
adjusted_speed = 480.0 / so.max_cutting_speed #empiric - found out by trying for hours ...
|
||||||
empiric_scale = 1 + (speedFactorR**2) / 15.25 #empiric - found out by trying for hours ...
|
empiric_scale = 1 + (speedFactorR**2) / 15.25 #empiric - found out by trying for hours ...
|
||||||
v_cut = so.max_cutting_speed * speedFactorR
|
v_cut = so.max_cutting_speed * speedFactorR
|
||||||
v_travel = so.max_travel_speed #this is always at maximum
|
|
||||||
tsec_cut = (self.svg.uutounit(str(totalCuttingLength)) / (adjusted_speed * so.max_cutting_speed * speedFactorR)) * empiric_scale
|
tsec_cut = (self.svg.uutounit(str(totalCuttingLength)) / (adjusted_speed * so.max_cutting_speed * speedFactorR)) * empiric_scale
|
||||||
tsec_travel = self.svg.uutounit(str(totalTravelLength)) / v_travel
|
tsec_travel = self.svg.uutounit(str(totalTravelLength)) / v_travel
|
||||||
tsec_total = so.job_time_offset + tsec_cut + tsec_travel
|
tsec_total = so.job_time_offset + tsec_cut + tsec_travel
|
||||||
@ -749,7 +763,7 @@ class LaserCheck(inkex.EffectExtension):
|
|||||||
if "{:02.0f}".format(seconds) == "60": #for formatting reasons
|
if "{:02.0f}".format(seconds) == "60": #for formatting reasons
|
||||||
seconds = 0
|
seconds = 0
|
||||||
minutes += 1
|
minutes += 1
|
||||||
inkex.utils.debug("@{:03.0f}% (cut={:06.2f}mm/s | travel={:06.2f}mm/s) > {:03.0f}min {:02.0f}sec | cost={:02.0f}€".format(speedFactor, v_cut, v_travel, minutes, seconds, costs))
|
inkex.utils.debug("@{:05.1f}% (cut={:06.2f}mm/s > {:03.0f}min {:02.0f}sec | cost={:02.0f}€".format(speedFactor, v_cut, minutes, seconds, costs))
|
||||||
|
|
||||||
|
|
||||||
''' Measurements from Epilog Software Suite
|
''' Measurements from Epilog Software Suite
|
||||||
|
Reference in New Issue
Block a user