| /********************************************************************************
|
| * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
|
| *
|
| * See the NOTICE file(s) distributed with this work for additional
|
| * information regarding copyright ownership.
|
| *
|
| * This program and the accompanying materials are made available under the
|
| * terms of the Eclipse Public License v. 2.0 which is available at
|
| * http://www.eclipse.org/legal/epl-2.0.
|
| *
|
| * SPDX-License-Identifier: EPL-2.0
|
| *
|
| ********************************************************************************/
|
| |
| |
| package org.eclipse.mdm.openatfx.mdf.mdf4; |
| |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.channels.SeekableByteChannel; |
| import java.util.Arrays; |
| |
| /** |
| * <p> |
| * THE CHANNEL BLOCK <code>CNBLOCK</code> |
| * </p> |
| * The CNBLOCK describes a channel, i.e. it contains information about the |
| * recorded signal and how its signal values are stored in the MDF file. |
| * |
| * @author Christian Rechner |
| */ |
| class CNBLOCK extends BLOCK { |
| |
| public static String BLOCK_ID = "##CN"; |
| |
| /** Link section */ |
| |
| // Pointer to next channel block (CNBLOCK) (can be NIL) |
| // LINK |
| private long lnkCnNext; |
| |
| // Composition of channels: Pointer to channel array block (CABLOCK) or |
| // channel block (CNBLOCK) (can be NIL). |
| // LINK |
| private long lnkComposition; |
| |
| // Pointer to TXBLOCK with name (identification) of channel. |
| // LINK |
| private long lnkTxName; |
| |
| // Pointer to channel source (SIBLOCK) (can be NIL) |
| // Must be NIL for component channels (members of a structure or array |
| // elements) because they all must have the same |
| // source and thus simply use the SIBLOCK of their parent CNBLOCK (direct |
| // child of CGBLOCK). |
| // LINK |
| private long lnkSiSource; |
| |
| // Pointer to the conversion formula (CCBLOCK) (can be NIL, must be NIL for |
| // complex channel data types, i.e. for |
| // cn_data_type ≥ 10). |
| // LINK |
| private long lnkCcConversion; |
| |
| // Pointer to channel type specific signal data |
| // LINK |
| private long lnkData; |
| |
| // Pointer to TXBLOCK/MDBLOCK with designation for physical unit of signal |
| // data (after conversion) or (only for |
| // channel data types "MIME sample" and "MIME stream") to MIME context-type |
| // text. (can be NIL). |
| // LINK |
| private long lnkMdUnit; |
| |
| // Pointer to TXBLOCK/MDBLOCK with comment and additional information about |
| // the channel. (can be NIL) |
| // LINK |
| private long lnkMdComment; |
| |
| // List of attachments for this channel (references to ATBLOCKs in global |
| // linked list of ATBLOCKs). |
| // The length of the list is given by cn_attachment_count. It can be empty |
| // (cn_attachment_count = 0), i.e. there are |
| // no attachments for this channel. |
| // LINK |
| private long[] lnkAtReference; |
| |
| // Only present if "default X" flag (bit 12) is set. |
| // Reference to channel to be preferably used as X axis. |
| // The reference is a link triple with pointer to parent DGBLOCK, parent |
| // CGBLOCK and CNBLOCK for the channel (none |
| // of them must be NIL). |
| // The referenced channel does not need to have the same raster nor |
| // monotonously increasing values. It can be a |
| // master channel, e.g. in case several master channels are present. In case |
| // of different rasters, visualization may |
| // depend on the interpolation method used by the tool. |
| // In case no default X channel is specified, the tool is free to choose the |
| // X axis; usually a master channels would |
| // be used. |
| // LINK |
| private long[] lnkDefaultX; |
| |
| /** Data section */ |
| |
| // Channel type |
| // 0 = fixed length data channel channel value is contained in record. |
| // 1 = variable length data channel also denoted as "variable length signal |
| // data" (VLSD) channel |
| // 2 = master channel for all signals of this group |
| // 3 = virtual master channel |
| // 4 = synchronization channel |
| // 5 = maximum length data channel |
| // 6 = virtual data channel |
| // UINT8 |
| private byte channelType; |
| |
| // Sync type: |
| // 0 = None (to be used for normal data channels) |
| // 1 = Time (physical values must be seconds) |
| // 2 = Angle (physical values must be radians) |
| // 3 = Distance (physical values must be meters) |
| // 4 = Index (physical values must be zero-based index values) |
| // UINT8 |
| private byte syncType; |
| |
| // Channel data type of raw signal value |
| // 0 = unsigned integer (LE Byte order) |
| // 1 = unsigned integer (BE Byte order) |
| // 2 = signed integer (two’s complement) (LE Byte order) |
| // 3 = signed integer (two’s complement) (BE Byte order) |
| // 4 = IEEE 754 floating-point format (LE Byte order) |
| // 5 = IEEE 754 floating-point format (BE Byte order) |
| // 6 = String (SBC, standard ASCII encoded (ISO-8859-1 Latin), NULL |
| // terminated) |
| // 7 = String (UTF-8 encoded, NULL terminated) |
| // 8 = String (UTF-16 encoded LE Byte order, NULL terminated) |
| // 9 = String (UTF-16 encoded BE Byte order, NULL terminated) |
| // 10 = Byte Array with unknown content (e.g. structure) |
| // 11 = MIME sample (sample is Byte Array with MIME content-type specified |
| // in cn_md_unit) |
| // 12 = MIME stream (all samples of channel represent a stream with MIME |
| // content-type specified in cn_md_unit) |
| // 13 = CANopen date (Based on 7 Byte CANopen Date data structure, see Table |
| // 36) |
| // 14 = CANopen time (Based on 6 Byte CANopen Time data structure, see Table |
| // 37) |
| // UINT8 |
| private byte dataType; |
| |
| // Bit offset (0-7): first bit (=LSB) of signal value after Byte offset has |
| // been applied. |
| // If zero, the signal value is 1-Byte aligned. A value different to zero is |
| // only allowed for Integer data types |
| // (cn_data_type ≤ 3) and if the Integer signal value fits into 8 |
| // contiguous Bytes (cn_bit_count + cn_bit_offset ≤ |
| // 64). For all other cases, cn_bit_offset must be zero. |
| // UINT8 |
| private byte bitOffset; |
| |
| // Offset to first Byte in the data record that contains bits of the signal |
| // value. The offset is applied to the |
| // plain record data, i.e. skipping the record ID. |
| // UINT32 |
| private long byteOffset; |
| |
| // Number of bits for signal value in record. |
| // UINT32 |
| private long bitCount; |
| |
| // The value contains the following bit flags (Bit 0 = LSB): |
| // Bit 0: All values invalid flag |
| // Bit 1: Invalidation bit valid flag |
| // Bit 2: Precision valid flag |
| // Bit 3: Value range valid flag |
| // Bit 4: Limit range valid flag |
| // Bit 5: Extended limit range valid flag |
| // Bit 6: Discrete value flag |
| // Bit 7: Calibration flag |
| // Bit 8: Calculated flag |
| // Bit 10: Bus event flag |
| // Bit 11: Monotonous flag |
| // Bit 12: Default X axis flag |
| // UINT32 |
| private long flags; |
| |
| // Position of invalidation bit. |
| // The invalidation bit can be used to specify if the signal value in the |
| // current record is valid or not. |
| // Note: the invalidation bit is optional and can only be used if the |
| // "invalidation bit valid" flag (bit 1) is set. |
| // UINT32 |
| private long invalBitPos; |
| |
| // Precision for display of floating point values. |
| // 0xFF means unrestricted precision (infinite). |
| // Any other value specifies the number of decimal places to use for display |
| // of floating point values. |
| // Only valid if "precision valid" flag (bit 2) is set |
| // UINT8 |
| private byte precision; |
| |
| // Length N of cn_at_reference list, i.e. number of attachments for this |
| // channel. Can be zero. |
| // UINT16 |
| private int attachmentCount; |
| |
| // Minimum signal value that occurred for this signal (raw value) |
| // Only valid if "value range valid" flag (bit 3) is set. |
| // REAL |
| private double valRangeMin; |
| |
| // Maximum signal value that occurred for this signal (raw value) |
| // Only valid if "value range valid" flag (bit 3) is set. |
| // REAL |
| private double valRangeMax; |
| |
| // Lower limit for this signal (physical value for numeric conversion rule, |
| // otherwise raw value) |
| // Only valid if "limit range valid" flag (bit 4) is set. |
| // REAL |
| private double limitMin; |
| |
| // Upper limit for this signal (physical value for numeric conversion rule, |
| // otherwise raw value) |
| // Only valid if "limit range valid" flag (bit 4) is set. |
| // REAL |
| private double limitMax; |
| |
| // Lower extended limit for this signal (physical value for numeric |
| // conversion rule, otherwise raw value) |
| // Only valid if "extended limit range valid" flag (bit 5) is set. |
| // If cn_limit_min is valid, cn_limit_min must be larger or equal to |
| // cn_limit_ext_min. |
| // REAL |
| private double limitExtMin; |
| |
| // Upper extended limit for this signal (physical value for numeric |
| // conversion rule, otherwise raw value) |
| // Only valid if "extended limit range valid" flag (bit 5) is set. |
| // If cn_limit_max is valid, cn_limit_max must be less or equal to |
| // cn_limit_ext_max. |
| // REAL |
| private double limitExtMax; |
| |
| /** |
| * Constructor. |
| * |
| * @param sbc |
| * The byte channel pointing to the MDF file. |
| * @param pos |
| * The position of the block within the MDF file. |
| */ |
| private CNBLOCK(SeekableByteChannel sbc, long pos) { |
| super(sbc, pos); |
| } |
| |
| public long getLnkCnNext() { |
| return lnkCnNext; |
| } |
| |
| public long getLnkComposition() { |
| return lnkComposition; |
| } |
| |
| public long getLnkTxName() { |
| return lnkTxName; |
| } |
| |
| public long getLnkSiSource() { |
| return lnkSiSource; |
| } |
| |
| public long getLnkCcConversion() { |
| return lnkCcConversion; |
| } |
| |
| public long getLnkData() { |
| return lnkData; |
| } |
| |
| public long getLnkMdUnit() { |
| return lnkMdUnit; |
| } |
| |
| public long getLnkMdComment() { |
| return lnkMdComment; |
| } |
| |
| public long[] getLnkAtReference() { |
| return lnkAtReference; |
| } |
| |
| public long[] getLnkDefaultX() { |
| return lnkDefaultX; |
| } |
| |
| public byte getChannelType() { |
| return channelType; |
| } |
| |
| public byte getSyncType() { |
| return syncType; |
| } |
| |
| public byte getDataType() { |
| return dataType; |
| } |
| |
| public byte getBitOffset() { |
| return bitOffset; |
| } |
| |
| public long getByteOffset() { |
| return byteOffset; |
| } |
| |
| public long getBitCount() { |
| return bitCount; |
| } |
| |
| public long getFlags() { |
| return flags; |
| } |
| |
| public boolean isValueRangeValid() { // 3rd bit of the flags |
| return (flags & (byte) 0x8) != 0; |
| } |
| |
| public long getInvalBitPos() { |
| return invalBitPos; |
| } |
| |
| public byte getPrecision() { |
| return precision; |
| } |
| |
| public int getAttachmentCount() { |
| return attachmentCount; |
| } |
| |
| public double getValRangeMin() { |
| return valRangeMin; |
| } |
| |
| public double getValRangeMax() { |
| return valRangeMax; |
| } |
| |
| public double getLimitMin() { |
| return limitMin; |
| } |
| |
| public double getLimitMax() { |
| return limitMax; |
| } |
| |
| public double getLimitExtMin() { |
| return limitExtMin; |
| } |
| |
| public double getLimitExtMax() { |
| return limitExtMax; |
| } |
| |
| private void setLnkCnNext(long lnkCnNext) { |
| this.lnkCnNext = lnkCnNext; |
| } |
| |
| private void setLnkComposition(long lnkComposition) { |
| this.lnkComposition = lnkComposition; |
| } |
| |
| private void setLnkTxName(long lnkTxName) { |
| this.lnkTxName = lnkTxName; |
| } |
| |
| private void setLnkSiSource(long lnkSiSource) { |
| this.lnkSiSource = lnkSiSource; |
| } |
| |
| private void setLnkCcConversion(long lnkCcConversion) { |
| this.lnkCcConversion = lnkCcConversion; |
| } |
| |
| private void setLnkData(long lnkData) { |
| this.lnkData = lnkData; |
| } |
| |
| private void setLnkMdUnit(long lnkMdUnit) { |
| this.lnkMdUnit = lnkMdUnit; |
| } |
| |
| private void setLnkMdComment(long lnkMdComment) { |
| this.lnkMdComment = lnkMdComment; |
| } |
| |
| private void setLnkAtReference(long[] lnkAtReference) { |
| this.lnkAtReference = lnkAtReference; |
| } |
| |
| private void setLnkDefaultX(long[] lnkDefaultX) { |
| this.lnkDefaultX = lnkDefaultX; |
| } |
| |
| private void setChannelType(byte channelType) { |
| this.channelType = channelType; |
| } |
| |
| private void setSyncType(byte syncType) { |
| this.syncType = syncType; |
| } |
| |
| private void setDataType(byte dataType) { |
| this.dataType = dataType; |
| } |
| |
| private void setBitOffset(byte bitOffset) { |
| this.bitOffset = bitOffset; |
| } |
| |
| private void setByteOffset(long byteOffset) { |
| this.byteOffset = byteOffset; |
| } |
| |
| private void setBitCount(long bitCount) { |
| this.bitCount = bitCount; |
| } |
| |
| private void setFlags(long flags) { |
| this.flags = flags; |
| } |
| |
| private void setInvalBitPos(long invalBitPos) { |
| this.invalBitPos = invalBitPos; |
| } |
| |
| private void setPrecision(byte precision) { |
| this.precision = precision; |
| } |
| |
| private void setAttachmentCount(int attachmentCount) { |
| this.attachmentCount = attachmentCount; |
| } |
| |
| private void setValRangeMin(double valRangeMin) { |
| this.valRangeMin = valRangeMin; |
| } |
| |
| private void setValRangeMax(double valRangeMax) { |
| this.valRangeMax = valRangeMax; |
| } |
| |
| private void setLimitMin(double limitMin) { |
| this.limitMin = limitMin; |
| } |
| |
| private void setLimitMax(double limitMax) { |
| this.limitMax = limitMax; |
| } |
| |
| private void setLimitExtMin(double limitExtMin) { |
| this.limitExtMin = limitExtMin; |
| } |
| |
| private void setLimitExtMax(double limitExtMax) { |
| this.limitExtMax = limitExtMax; |
| } |
| |
| public CNBLOCK getCnNextBlock() throws IOException { |
| if (lnkCnNext > 0) { |
| return CNBLOCK.read(sbc, lnkCnNext); |
| } |
| return null; |
| } |
| |
| public TXBLOCK getCnTxNameBlock() throws IOException { |
| if (lnkTxName > 0) { |
| return TXBLOCK.read(sbc, lnkTxName); |
| } |
| return null; |
| } |
| |
| public SIBLOCK getSiSourceBlock() throws IOException { |
| if (lnkSiSource > 0) { |
| return SIBLOCK.read(sbc, lnkSiSource); |
| } |
| return null; |
| } |
| |
| public CCBLOCK getCcConversionBlock() throws IOException { |
| if (lnkCcConversion > 0) { |
| return CCBLOCK.read(sbc, lnkCcConversion); |
| } |
| return null; |
| } |
| |
| public BLOCK getDataBlock() throws IOException { |
| if (lnkData > 0) { |
| String blockType = getBlockType(sbc, lnkData); |
| // link points to a ATBLOCK |
| if (blockType.equals(ATBLOCK.BLOCK_ID)) { |
| return ATBLOCK.read(sbc, lnkData); |
| } |
| // links points to SDBLOCK |
| else if (blockType.equals(SDBLOCK.BLOCK_ID)) { |
| return SDBLOCK.read(sbc, lnkData); |
| } |
| // unknown |
| else { |
| throw new IOException("Unsupported block type for Channel Data: " + blockType); |
| } |
| } |
| return null; |
| } |
| |
| public BLOCK getMdUnitBlock() throws IOException { |
| if (lnkMdUnit > 0) { |
| String blockType = getBlockType(sbc, lnkMdUnit); |
| // link points to a MDBLOCK |
| if (blockType.equals(MDBLOCK.BLOCK_ID)) { |
| return MDBLOCK.read(sbc, lnkMdUnit); |
| } |
| // links points to TXBLOCK |
| else if (blockType.equals(TXBLOCK.BLOCK_ID)) { |
| return TXBLOCK.read(sbc, lnkMdUnit); |
| } |
| // unknown |
| else { |
| throw new IOException("Unsupported block type for MdUnit: " + blockType); |
| } |
| } |
| return null; |
| } |
| |
| public BLOCK getMdCommentBlock() throws IOException { |
| if (lnkMdComment > 0) { |
| String blockType = getBlockType(sbc, lnkMdComment); |
| // link points to a MDBLOCK |
| if (blockType.equals(MDBLOCK.BLOCK_ID)) { |
| return MDBLOCK.read(sbc, lnkMdComment); |
| } |
| // links points to TXBLOCK |
| else if (blockType.equals(TXBLOCK.BLOCK_ID)) { |
| return TXBLOCK.read(sbc, lnkMdComment); |
| } |
| // unknown |
| else { |
| throw new IOException("Unsupported block type for MdComment: " + blockType); |
| } |
| } |
| return null; |
| } |
|
|
| /**
|
| * Returns the CA- or CN-block, linked by the composition link.
|
| *
|
| * @return
|
| * @throws IOException
|
| */
|
| public BLOCK getCompositionBlock() throws IOException {
|
| if (lnkComposition > 0) {
|
| String blockType = getBlockType(sbc, lnkComposition);
|
| // link points to a CNBLOCK
|
| if (blockType.equals(CNBLOCK.BLOCK_ID)) {
|
| return CNBLOCK.read(sbc, lnkComposition);
|
| }
|
| // link points to CABLOCK
|
| else if (blockType.equals(CABLOCK.BLOCK_ID)) {
|
| return CABLOCK.read(sbc, lnkComposition);
|
| }
|
| // unknown
|
| else {
|
| throw new IOException("Unsupported block type for Composition: " + blockType);
|
| }
|
| }
|
| return null;
|
| }
|
| |
| @Override |
| public String toString() { |
| return "CNBLOCK [lnkCnNext=" + lnkCnNext + ", lnkComposition=" + lnkComposition + ", lnkTxName=" + lnkTxName |
| + ", lnkSiSource=" + lnkSiSource + ", lnkCcConversion=" + lnkCcConversion + ", lnkData=" + lnkData |
| + ", lnkMdUnit=" + lnkMdUnit + ", lnkMdComment=" + lnkMdComment + ", lnkAtReference=" |
| + Arrays.toString(lnkAtReference) + ", lnkDefaultX=" + Arrays.toString(lnkDefaultX) + ", channelType=" |
| + channelType + ", syncType=" + syncType + ", dataType=" + dataType + ", bitOffset=" + bitOffset |
| + ", byteOffset=" + byteOffset + ", bitCount=" + bitCount + ", flags=" + flags + ", invalBitPos=" |
| + invalBitPos + ", precision=" + precision + ", attachmentCount=" + attachmentCount + ", valRangeMin=" |
| + valRangeMin + ", valRangeMax=" + valRangeMax + ", limitMin=" + limitMin + ", limitMax=" + limitMax |
| + ", limitExtMin=" + limitExtMin + ", limitExtMax=" + limitExtMax + "]"; |
| } |
| |
| /** |
| * Reads a CNBLOCK from the channel starting at current channel position. |
| * |
| * @param channel |
| * The channel to read from. |
| * @param pos |
| * The position within the channel. |
| * @return The block data. |
| * @throws IOException |
| * The exception. |
| */ |
| public static CNBLOCK read(SeekableByteChannel channel, long pos) throws IOException { |
| CNBLOCK block = new CNBLOCK(channel, pos); |
| |
| // read block header |
| ByteBuffer bb = ByteBuffer.allocate(24); |
| bb.order(ByteOrder.LITTLE_ENDIAN); |
| channel.position(pos); |
| channel.read(bb); |
| bb.rewind(); |
| |
| // CHAR 4: Block type identifier |
| block.setId(MDF4Util.readCharsISO8859(bb, 4)); |
| if (!block.getId().equals(BLOCK_ID)) { |
| throw new IOException("Wrong block type - expected '" + BLOCK_ID + "', found '" + block.getId() + "'"); |
| } |
| |
| // BYTE 4: Reserved used for 8-Byte alignment |
| bb.get(new byte[4]); |
| |
| // UINT64: Length of block |
| block.setLength(MDF4Util.readUInt64(bb)); |
| |
| // UINT64: Number of links |
| block.setLinkCount(MDF4Util.readUInt64(bb)); |
| |
| // read block data |
| bb = ByteBuffer.allocate((int) block.getLength() - 24); |
| bb.order(ByteOrder.LITTLE_ENDIAN); |
| channel.position(pos + 24); |
| channel.read(bb); |
| bb.rewind(); |
| |
| // read links |
| long[] lnks = new long[(int) block.getLinkCount()]; |
| for (int i = 0; i < lnks.length; i++) { |
| lnks[i] = MDF4Util.readLink(bb); |
| } |
| |
| // read data |
| |
| // UINT8: Channel type |
| block.setChannelType(MDF4Util.readUInt8(bb)); |
| |
| // UINT8: Sync type |
| block.setSyncType(MDF4Util.readUInt8(bb)); |
| |
| // UINT8: Channel data type of raw signal value |
| block.setDataType(MDF4Util.readUInt8(bb)); |
| |
| // UINT8: Bit offset (0-7) |
| block.setBitOffset(MDF4Util.readUInt8(bb)); |
| |
| // UINT32: Offset to first Byte in the data record that contains bits of |
| // the signal value. |
| block.setByteOffset(MDF4Util.readUInt32(bb)); |
| |
| // UINT32: Number of bits for signal value in record. |
| block.setBitCount(MDF4Util.readUInt32(bb)); |
| |
| // UINT32: Flags |
| block.setFlags(MDF4Util.readUInt32(bb)); |
| |
| // UINT32: Position of invalidation bit. |
| block.setInvalBitPos(MDF4Util.readUInt32(bb)); |
| |
| // UINT8: Precision for display of floating point values. |
| block.setPrecision(MDF4Util.readUInt8(bb)); |
| |
| // BYTE: Reserved |
| bb.get(); |
| |
| // UINT16: Length N of cn_at_reference list, i.e. number of attachments |
| // for this channel. Can be zero. |
| block.setAttachmentCount(MDF4Util.readUInt16(bb)); |
| |
| // REAL: Minimum signal value that occurred for this signal (raw value) |
| block.setValRangeMin(MDF4Util.readReal(bb)); |
| |
| // REAL: Maximum signal value that occurred for this signal (raw value) |
| block.setValRangeMax(MDF4Util.readReal(bb)); |
| |
| // REAL: Lower limit for this signal (physical value for numeric |
| // conversion rule, otherwise raw value) |
| block.setLimitMin(MDF4Util.readReal(bb)); |
| |
| // REAL: Upper limit for this signal (physical value for numeric |
| // conversion rule, otherwise raw value) |
| block.setLimitMax(MDF4Util.readReal(bb)); |
| |
| // REAL: Lower extended limit for this signal (physical value for |
| // numeric conversion rule, otherwise raw value) |
| block.setLimitExtMin(MDF4Util.readReal(bb)); |
| |
| // REAL: Upper extended limit for this signal (physical value for |
| // numeric conversion rule, otherwise raw value) |
| block.setLimitExtMax(MDF4Util.readReal(bb)); |
| |
| // extract links after reading data (then we know how many attachments) |
| block.setLnkCnNext(lnks[0]); |
| block.setLnkComposition(lnks[1]); |
| block.setLnkTxName(lnks[2]); |
| block.setLnkSiSource(lnks[3]); |
| block.setLnkCcConversion(lnks[4]); |
| block.setLnkData(lnks[5]); |
| block.setLnkMdUnit(lnks[6]); |
| block.setLnkMdComment(lnks[7]); |
| long[] lnkAtRef = new long[block.getAttachmentCount()]; |
| for (int i = 0; i < block.getAttachmentCount(); i++) { |
| lnkAtRef[i] = lnks[i + 8]; |
| } |
| block.setLnkAtReference(lnkAtRef); |
| long[] lnkDefX = new long[3]; |
| if (lnks.length > block.getAttachmentCount() + 8) { |
| lnkDefX[0] = lnks[block.getAttachmentCount() + 8]; |
| lnkDefX[1] = lnks[block.getAttachmentCount() + 9]; |
| lnkDefX[2] = lnks[block.getAttachmentCount() + 10]; |
| } |
| block.setLnkDefaultX(lnkDefX); |
| |
| return block; |
| } |
| |
| } |