Archived
307 lines
23 KiB
HTML
307 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Results that change on upgrade | HiAPI-C# 2025 </title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="title" content="Results that change on upgrade | HiAPI-C# 2025 ">
|
||
|
||
|
||
<link rel="icon" href="../../img/HiAPI.favicon.ico">
|
||
<link rel="stylesheet" href="../../public/docfx.min.css">
|
||
<link rel="stylesheet" href="../../public/main.css">
|
||
<meta name="docfx:navrel" content="../../toc.html">
|
||
<meta name="docfx:tocrel" content="toc.html">
|
||
|
||
<meta name="docfx:rel" content="../../">
|
||
|
||
|
||
|
||
<meta name="loc:inThisArticle" content="In this article">
|
||
<meta name="loc:searchResultsCount" content="{count} results for "{query}"">
|
||
<meta name="loc:searchNoResults" content="No results for "{query}"">
|
||
<meta name="loc:tocFilter" content="Filter by title">
|
||
<meta name="loc:nextArticle" content="Next">
|
||
<meta name="loc:prevArticle" content="Previous">
|
||
<meta name="loc:themeLight" content="Light">
|
||
<meta name="loc:themeDark" content="Dark">
|
||
<meta name="loc:themeAuto" content="Auto">
|
||
<meta name="loc:changeTheme" content="Change theme">
|
||
<meta name="loc:copy" content="Copy">
|
||
<meta name="loc:downloadPdf" content="Download PDF">
|
||
|
||
<script type="module" src="./../../public/docfx.min.js"></script>
|
||
|
||
<script>
|
||
const theme = localStorage.getItem('theme') || 'auto'
|
||
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
|
||
</script>
|
||
|
||
</head>
|
||
|
||
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
|
||
<header class="bg-body border-bottom">
|
||
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
|
||
<div class="container-xxl flex-nowrap">
|
||
<a class="navbar-brand" href="../../index.html">
|
||
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
|
||
|
||
</a>
|
||
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
|
||
<i class="bi bi-three-dots"></i>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="navpanel">
|
||
<div id="navbar">
|
||
<form class="search" role="search" id="search">
|
||
<i class="bi bi-search"></i>
|
||
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
</header>
|
||
|
||
<main class="container-xxl">
|
||
<div class="toc-offcanvas">
|
||
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
|
||
<div class="offcanvas-header">
|
||
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
|
||
</div>
|
||
<div class="offcanvas-body">
|
||
<nav class="toc" id="toc"></nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="content">
|
||
<div class="actionbar">
|
||
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
|
||
<i class="bi bi-list"></i>
|
||
</button>
|
||
|
||
<nav id="breadcrumb"></nav>
|
||
</div>
|
||
|
||
<article data-uid="Release-Upgrading-ResultsThatChange">
|
||
<h1 id="results-that-change-on-upgrade">Results that change on upgrade</h1>
|
||
|
||
<p>None of the following breaks a build. All of them change what a simulation produces, so a
|
||
byte-for-byte comparison against 3.1.175 output will differ — usually because 3.1.175 was wrong.</p>
|
||
<p><strong>Silent wrong geometry, now fixed.</strong> Each of these produced a plausible-looking simulation of the
|
||
wrong thing.</p>
|
||
<ul>
|
||
<li><strong>The kinematic pivot anchor.</strong> The pivot-transform chain entry was built as <code>K(0)·K(abc)⁻¹</code>,
|
||
folding the whole machine-zero forward kinematic — its linear part included — into the pre-pivot
|
||
anchor. That linear part encodes each axis' motion sense and tool/workpiece-side ownership, so it
|
||
mirrored the program components of every workpiece-side linear axis before the IK ran. On a
|
||
table-side chain whose machine-zero linear part is <code>diag(1,1,-1)</code>, every program Z was mirrored and
|
||
a near-180° swing amplified it into metres of machine-Z error, floating the toolpath above the
|
||
workpiece. The anchor is now the translation alone, matching what HardNc has always kept. Machines
|
||
whose linear axes all ride the tool side are unaffected.</li>
|
||
<li><strong>G68 2D coordinate rotation was a silent no-op.</strong> <a class="xref" href="../../api/Hi.NcParsers.LogicSyntaxs.TiltTransformUtil.html">TiltTransformUtil</a>
|
||
judged the active mode from the current block but always took the matrix from the previous one, so
|
||
every G68 activation past the first block had its freshly authored rotation overwritten with an
|
||
identity that then propagated. G68 rotation did nothing at all while the block's term still read
|
||
<code>G68</code>, and the simulation machined the unrotated pattern. Re-running an existing G68 program now
|
||
gives different — correct — geometry.</li>
|
||
<li><strong>A blank line reset G90/G91.</strong> A piece with no parsing section — a blank line, a comment-only line,
|
||
<code>%</code>, an O-number — left the next block's single-step lookback empty, and it fell back to the G90
|
||
default. A program that was incremental throughout silently flipped to absolute mid-file. Such
|
||
pieces now carry the positioning section forward like every other modal syntax.</li>
|
||
<li><strong>A Siemens D offset with no <code>$TC_DP</code> row resolved to zero length, in silence.</strong> A whole TRAORI
|
||
program machined one tool length low. The read point now falls back to the generic tool-number-keyed
|
||
height and emits <code>SiemensToolOffset--TcdpRowMissing</code>.</li>
|
||
<li><strong>G43.4 with an unresolvable H word activated RTCP with a zero-length tool.</strong> It now reports
|
||
<code>Comp-ToolHeight--001</code> as a warning and keeps processing the block. An omitted H is no longer a
|
||
silent zero: it takes the equipped tool's own offset row first (HardNc parity), then a non-zero
|
||
modal id, and only then warns <code>Comp-ToolHeight--NoToolForOmittedH</code> (plain G43) or
|
||
<code>Comp-ToolHeight--NoToolForOmittedHRtcp</code> (G43.4). A vacant table row warns
|
||
<code>Comp-ToolHeight--RowMissing</code> instead of compensating a zero length.</li>
|
||
<li><strong><code>G43.4</code> survived <code>M02</code> / <code>M30</code> and a rotary-only <code>G53</code> still coupled through RTCP.</strong> A sequence
|
||
that ended one program with <code>M02</code> and started the next with a rotary rapid — without a <code>G49</code> —
|
||
kept tool-centre-point control on, pinned the tip, and dragged machine XYZ through the stock. On
|
||
SoftNc the program-end block is a <strong>reset edge</strong>: tool-length compensation (G43 / G44 / G43.4,
|
||
Siemens <code>TRAORI</code>, Heidenhain <code>M128</code>), tilt (G68 / G68.2 / CYCLE800 / <code>PLANE</code> / TRANS), G41/G42 and
|
||
G81–G89 cancel on the <em>next</em> block, written as explicit cancel sections so modal carry cannot copy
|
||
the previous state. The <code>M02</code> block itself still runs under the modal state it executed with, so a
|
||
retract on the same line as <code>M30</code> still carries tool length. A rotary-only <code>G53</code> under active
|
||
RTCP now holds the linear axes at the previous machine position and stays McLinear, which is what
|
||
G53 does on a control (no compensation). <strong>HardNc still carries G43.4 across <code>M02</code></strong> — the two
|
||
engines are deliberately divergent on that edge; 3.2's default is SoftNc. A tool change that
|
||
leaves <code>G43.4 Hm</code> alive for a different tool warns
|
||
<code>Comp-ToolHeight--RtcpOutlivesToolChange</code> on the change block.</li>
|
||
<li><strong>Fanuc <code>G54.1 Pn</code> (also written <code>G54 Pn</code>) was not resolved.</strong> Additional work coordinate systems
|
||
now resolve against the extended table; a selected row nobody has entered reports
|
||
<code>Coord-WorkOffset--AdditionalZero</code> and runs on the machine origin. <code>G59.1</code>–<code>G59.9</code> have a
|
||
provider on the ISO presets. See
|
||
<a href="../../anatomy/general-setup/controller/program-data/work-coordinates.html">Work Coordinates</a>.</li>
|
||
<li><strong>Heidenhain DIN/ISO arc centres.</strong> <code>I</code> / <code>J</code> / <code>K</code> are <strong>absolute</strong> circle centres on Heidenhain —
|
||
the ISO face of the klartext <code>CC</code> pole — not start-to-centre offsets. Reading them incrementally
|
||
turned arcs into near-full phantom circles. Fixed in all three engines: the HardNc reader
|
||
(<a class="xref" href="../../api/Hi.Numerical.NcArgs.ArcNcArg.html#Hi_Numerical_NcArgs_ArcNcArg_IsIjkAbsolute">IsIjkAbsolute</a>), the SoftNc reader
|
||
(<a class="xref" href="../../api/Hi.NcParsers.LogicSyntaxs.CircularMotionSyntax.html#Hi_NcParsers_LogicSyntaxs_CircularMotionSyntax_IsIjkAbsolute">IsIjkAbsolute</a>) and the optimizer's write-back.
|
||
<code>G91</code> is the exception: an incremental block switches the words back to start-to-centre offsets, so
|
||
an arc programmed under <code>G91</code> is unaffected by this change. Also delivered on the 3.1.175.x service
|
||
line.</li>
|
||
<li><strong>Feed per tooth, MRR and cutting forces follow the equipped tool's tip, not the controller's F
|
||
word.</strong> The physics used to read the commanded CL feedrate. It now reads the step's real tip
|
||
feedrate (<a class="xref" href="../../api/Hi.MachiningSteps.MachineMotionStep.html#Hi_MachiningSteps_MachineMotionStep_ActualTipFeedrate_mmds">ActualTipFeedrate_mmds</a>, client key
|
||
<a class="xref" href="../../api/Hi.MachiningSteps.MachiningStep.html#Hi_MachiningSteps_MachiningStep_ActualTipFeedrate_mmdmin">ActualTipFeedrate_mmdmin</a>): the tip's displacement relative
|
||
to the workpiece over the step duration. On XYZ moves, CL files and RTCP with the equipped tool's own
|
||
offset the two agree (the recomputation moves forces by at most about 1e-4 relative, from the
|
||
<code>TimeSpan</code>-quantised step duration); they part under RTCP with a tool-length offset that does not
|
||
describe the equipped tool, on rotary-axis-limited simultaneous five-axis blocks (the tip is slower
|
||
than F, up to a few percent), and wherever the commanded value was stale.</li>
|
||
<li><strong>A feed block that does not move lost its F word.</strong> A block that repeats the current position
|
||
(<code>X.. Y.. Z.. F1600.</code> right after the same point at <code>F800.</code>, a common CAM habit) returned on
|
||
zero distance before emitting anything, so every following block showed and cut at the old
|
||
feed. Its <code>ActFeedrate</code> now lands on a zero-length linear, arc or polar block alike. The same
|
||
stale commanded value is one of the cases the tip-feedrate split above parts on.</li>
|
||
<li><strong>A rotary-bearing act mixed tip travel with the commanded CL feed resolution.</strong> On a pure RTCP
|
||
rapid swing the CL point stands still, the commanded feed is ~0, and dividing the equipped tool's
|
||
tip sweep by that resolution saturated the step count (the play appeared to hang on one line). A
|
||
rotary-bearing act now splits by posture change (each rotary-axis delta over the rotary
|
||
resolution) and by spindle cycles when the spindle turns — never by tip travel over the CL-feed
|
||
resolution. Step counts of existing five-axis projects therefore shift on non-RTCP rotary
|
||
blocks; XYZ-only programs are unaffected.</li>
|
||
<li><strong>A <code>K0</code> word on a HardNc G02/G03 saturated the turn count.</strong> Under the default G17 plane a
|
||
written-but-zero plane-normal word divided the axial travel by zero, the additional-turn count
|
||
saturated to <code>int.MaxValue</code>, and one arc block became a spiral act of roughly 302 simulated years —
|
||
the play appeared to hang on a single NC line. The reading now falls back to the closed-circle rule
|
||
for a zero pace, matching the guard the SoftNc side already had.</li>
|
||
<li><strong>Indexed-rotary programs folded the pivot into plain moves.</strong> A block with no active G68.2 or
|
||
G43.4 no longer folds the kinematic pivot transform into a plain XYZ move, which is what a real
|
||
controller does with a table-side rotary program.</li>
|
||
<li><strong>The radius-compensation arc transient cache landed on the wrong block.</strong> Any non-motion line
|
||
between the corner and the arc orphaned the cache, so the arc lost its leading linear bridge and
|
||
began at the corner intersection instead of on the offset arc.</li>
|
||
<li><strong>Cutter radius compensation on G18 / G19 ran out of the plane.</strong> Both engines built the
|
||
perpendicular as <code>Z × axis</code> whatever plane was selected, so under G18 or G19 the whole offset went
|
||
one radius along the plane normal (a <code>G41</code> region in the ZX plane was displaced in Y), every arc of
|
||
the region reported <code>Arc-EndpointOffRadius</code>, and the <code>RadiusComp--Interference</code> gate could not fire
|
||
there. Both engines now take the perpendicular, the in-plane test for blocks without movement and
|
||
the interference gate from the selected plane: the offset lies in the plane, left/right is judged
|
||
looking down the plane's positive normal (the same convention that orients G02/G03), and the
|
||
diagnostics behave as on G17. G17 programs are unaffected.</li>
|
||
<li><strong>The G68.2 tool-axis IK fallback probed the mirrored tool axis.</strong> Both normal-only fallbacks in
|
||
<a class="xref" href="../../api/Hi.NcParsers.LogicSyntaxs.IsoG68p2TiltSyntax.html">IsoG68p2TiltSyntax</a> read the transposed third column instead of the
|
||
third row. On the no-hint path this only skewed a warning gate, but the explicit A/B/C path seeds
|
||
its hint blend from that solve, so a machine with fewer than three rotary axes composed a mirrored
|
||
tilted plane.</li>
|
||
<li><strong>A Fanuc <code>WHILE</code> forward jump bound to the wrong <code>END</code>.</strong> Sequential loops idiomatically reuse
|
||
<code>DO 1</code>, so a second <code>WHILE</code>'s falsy-condition exit bound to the <em>first</em> loop's <code>END 1</code> and
|
||
redirected execution to a point before the second <code>WHILE</code> — an unbounded loop the iteration
|
||
watchdog cannot see, because it only ticks on <code>END</code> reverse jumps. The jump now uses the anchored
|
||
label scan the Siemens loop family already used.</li>
|
||
<li><strong>Heidenhain G28 is MIRROR IMAGE, not a reference-point return.</strong> On the Heidenhain preset the
|
||
shared pipeline had been reading it as the Fanuc reference return, so a <code>G28 X</code> block minted a
|
||
phantom rapid to home while the mirror silently vanished. It is now simulated as a program-to-MC
|
||
transform, <code>ReferenceReturnSyntax</code> leaves the Heidenhain logic list, and klartext <code>CYCL DEF 8</code>
|
||
records the same mirror statement so one program mirrors identically in either dialect. <strong>HardNc
|
||
keeps the Fanuc reading</strong>, so the two engines are deliberately divergent on Heidenhain G28 files and
|
||
any parity comparison must account for it.</li>
|
||
<li><strong><code>MathUtil.Convert_inchdmin_To_mmds</code> returned mm/min, not mm/s.</strong> The body multiplied by 25.4 and
|
||
never divided by 60, so a caller trusting the name got a 60× feedrate; the sibling
|
||
<code>Convert_mmdmin_To_mmds</code> divides as its name demands. No shipped path called it — the CL/APT
|
||
feedrate route converts inches-per-minute to mm/min itself — so this changes nothing inside the
|
||
product, but a caller who had compensated for the old behaviour must remove that compensation.</li>
|
||
<li><strong><code>GetRByZ(List<PairZr>)</code> never interpolated.</strong> The list overload looked up its ceiling node with
|
||
the floor lookup, so floor equalled ceiling and the method degenerated into a step function. For a
|
||
sharp cone whose inner-beam Z–R list has no node between apex and rim, that pinned the inner radius
|
||
to 0 across the whole cone face and produced NaN flute vertices — the root cause of the transparent
|
||
cone-tip flute that 3.1.180 addressed at the display layer. It affects every consumer of
|
||
radius-by-Z interpolation, force geometry included.</li>
|
||
</ul>
|
||
<p><strong>Numerical results move even where nothing was renamed.</strong></p>
|
||
<ul>
|
||
<li>Five-axis IK is roughly <strong>1000× tighter</strong>. The rotary solver behind
|
||
<a class="xref" href="../../api/Hi.Numerical.Xyzabc.XyzabcSolver.html">XyzabcSolver</a> now runs coarse→polish: the coarse stage keeps the
|
||
original dot residual so solve success and failure
|
||
semantics are unchanged, then a <code>[dot, cross, rr-per-axis]</code> system polishes. The dot criterion is
|
||
quadratically blind to angle error — an envelope of about 1.4e-3 rad, and the resulting tip error is
|
||
that envelope times the tool length — so adding the cross term restores quadratic convergence and
|
||
drops the envelope to about 1e-6 rad. Failed solves no longer pollute the implicit seed, and the
|
||
measure-zero perfect-saddle case is escaped by a deterministic retry offset instead of by leftover
|
||
seed pollution.</li>
|
||
<li><a class="xref" href="../../api/Hi.Numerical.Acts.ActMcXyzLinearContour.html">ActMcXyzLinearContour</a> steps by <strong>euclidean</strong> tip travel instead of the
|
||
largest per-axis component, so <code>LinearResolution_mm</code> now caps actual tool-tip travel per step and a
|
||
diagonal move produces up to √3× more steps at the same setting.</li>
|
||
<li><strong>Cutter–workpiece engagement no longer depends on how the contact surface was tiled.</strong> The scan
|
||
that turns a step's contact contours into the engaged angle ranges of
|
||
<a class="xref" href="../../api/Hi.Milling.Engagements.LayerMillingEngagement.html">LayerMillingEngagement</a> switched to a bounding-box approximation on
|
||
dense steps, could pull a merged range back when a shorter slice nested inside it, started bottom
|
||
rings the floor never reached, and read the winding of each triangle — so the same cut produced a
|
||
different engagement, and therefore different cutting forces and temperatures, depending on how
|
||
many polygons the geometry kernel happened to cut the contact surface into. Every triangle is now
|
||
scanned exactly and orientation-free; a cylinder wall and a flat floor sector with known engagement
|
||
arcs reproduce them to 1e-9 rad at four tessellations, and the managed and native paths stay
|
||
bit-identical. Re-running an existing program moves the forces on most engaged steps (58–95% of
|
||
them on the bench fixtures); the largest moves are floors that used to register as empty — the
|
||
whole bottom disc dropped out — and side ranges that were fragmented into pieces separated by
|
||
phantom 1° gaps. Coefficient sets trained before this change embed the old engagement of their
|
||
training program; retrain on a replay if that fit matters.</li>
|
||
<li><strong>SoftNc canned cycles index a rotary axis.</strong> A G81–G89 repeat block carrying an A / B / C word
|
||
used to skip the hole (rotary-only repeat), drill it at the previous angle (mixed block) or let it
|
||
follow the table under <code>G68.2</code>. It now indexes the table and drills again, pre-positioning XYZ and
|
||
the table in one move, and under <code>G68.2</code> without RTCP the feature frame stays pinned in machine
|
||
space. Under <code>G91</code> only the words the block itself spoke are increments — an X-pitch repeat used
|
||
to add the previous cycle's Y a second time and drill upward. A bolt-circle program that ran
|
||
short of holes, or drilled them in the wrong place, now matches the HardNc replay.</li>
|
||
<li><strong>A rotary word under modal <code>G91</code> is an increment.</strong> <code>G91 … C20.</code> then <code>C20.</code> left the table at
|
||
20; it is 40 now. A spoken <code>G91 B270.</code> stays a +270° traverse instead of folding to −90°, and a
|
||
<code>G91 G28</code> rotary intermediate anchors on the previous modal angle instead of on zero (<code>G91 G28 C0</code> from C90 now holds the table and returns home instead of swinging to 0 first). Per-word
|
||
directives (<code>C=AC(90)</code>, <code>ACP</code> / <code>ACN</code>, <code>IC()</code>) are unchanged. HardNc keeps its absolute reading
|
||
and warns.</li>
|
||
<li><strong>Rotary IK from a pole seed.</strong> Every solve attempt now runs coarse then polish, so a small tilt
|
||
with a large azimuth that ran out of iterations a few milliradians short — and was refused by
|
||
<code>G53.1</code>, leaving the table at A = C = 0 with the tilt active and the path rotated off the part —
|
||
now solves to 1e-8 rad. On a Fanuc IJK grid from the pole seed 54 / 69 failures per A/C chain
|
||
become 0; a CL program goes from 44 dropped endpoints of 536 to 0.</li>
|
||
<li><strong>The rotary branch inside the travel wins.</strong> When the nearest IK solution lies outside an axis'
|
||
configured travel, <code>G53.1</code>, <code>G68.2</code>, <code>CYCLE800</code>, <code>PLANE SPATIAL</code> and <code>LN</code> now take the admissible
|
||
branch nearest the entry (sign, half-turn and supplement combinations are searched); a posture
|
||
the cradle cannot take is no longer commanded silently. This needs the travel to be known, and
|
||
it now is on every project: stroke limits declared only in the legacy machine-limits boxes are
|
||
copied into the controller parameter table on load for the axes carrying no end there, where they
|
||
used to be checked against nothing on SoftNc. An axis parked exactly on its limit is no longer
|
||
reported (<a class="xref" href="../../api/Hi.NcParsers.Dependencys.StrokeLimitUtil.html#Hi_NcParsers_Dependencys_StrokeLimitUtil_Tolerance">Tolerance</a>).</li>
|
||
<li>The SoftNc pipeline is the default engine.</li>
|
||
<li>Executed <code>SyntaxPiece</code>s freeze to UTF-8 (below), so their <code>JsonObject</code> is a fresh read-only snapshot
|
||
per call rather than a retained live graph.</li>
|
||
<li><code>SyntaxPiece.SentenceIndex</code> becomes a session-global execution-order counter and is no longer
|
||
contiguous per file.</li>
|
||
<li>Repeated NC diagnostics fold into per-run summaries at the run boundaries.</li>
|
||
<li><strong>HardNc tool changes</strong> fire on M06 / Heidenhain <code>TOOL CALL</code> rather than on a changed <code>T</code> word — a
|
||
bare <code>T</code> is magazine pre-selection, and a same-tool M06 still runs the changer cycle — and unset
|
||
<code>HardNcEnv</code> tooling defaults become the three-axis shape (XYZ = NaN, NaN, 0; ABC all NaN). The old
|
||
defaults swung all three rotary axes home on every M06, which no post expects.</li>
|
||
</ul>
|
||
|
||
</article>
|
||
|
||
<div class="contribution d-print-none">
|
||
</div>
|
||
|
||
<div class="next-article d-print-none border-top" id="nextArticle"></div>
|
||
|
||
</div>
|
||
|
||
<div class="affix">
|
||
<nav id="affix"></nav>
|
||
</div>
|
||
</main>
|
||
|
||
<div class="container-xxl search-results" id="search-results"></div>
|
||
|
||
<footer class="border-top text-secondary">
|
||
<div class="container-xxl">
|
||
<div class="flex-fill">
|
||
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
</body>
|
||
</html>
|