Деян Йосифов

 Personal Website & Blog

Проект по VRML

Written By: Деян - Jan• 13•15

С наближаването на сесията във ФМИ наближават и сроковете за предаването на проектите :). Този блог пост е посветен на първия проект, който направих за този семестър, който съответно е по VRML. Следва и описанието на проекта:

Risk game – Probabilities calculator

Written By: Деян - Aug• 02•14

Отдавна не бях сядал да пиша блог пост. Да не кажа че от година и нещо съвсем си зарязах сайта след завършването на академията на Телерик. 🙂 Все пак с този пост се надявам да възобновя този навик, от време на време да сядам и да пиша нещо по-интересно, с което съм се занимавал.

Такъв е и поводът на този пост, който смятам, че би могъл да бъде от полза на любителите на настолната игра “Риск”. Който не е чувал за нея или не я е играл, може да види повече информация за играта “Риск” в Wikipedia. Аз лично също съвсем отскоро я играя, но пък ми стана любопитно и реших да тествам уменията си като напиша един калкулатор на javascript. Какво прави този калкулатор накратко:

  • Задава се броя на атакуващите и на защитаващите войници в една битка.
  • Калкулаторът изчислява всички шансове, при които битката завършва до край (или защитниците или нападателите остават с 0 войници).
  • Като резултат калкулаторът изкарва две таблички. В първата са шансовете на победа на нападателя, като срещу всеки брой оцелели на края на битката пише каква е вероятността за това събитие. Втората таблица е аналогична, но за защитаващата се армия.
  • Тези таблици имат 4 колони:
    1. Брой оцелели след края на битката
    2. Процент вероятност да има точно толкова оцелели
    3. Процент вероятност да има толкова или повече оцелели
    4. Процент вероятност да има по-малко оцелели

Имайки този калкулатор, човек може да си прецени шансовете и да реши дали би влязъл в битка в зависимост от броя войници, с които иска да остане след битката. 🙂 Това е накратко. Ето и едно демо с опростен UI на калкулатора:

Ако искате да го отворите в отделна страница, може да отворите този линк към калкулатора в отделен прозорец.

Надявам се този пост да бъде от полза на някого. Всякакви коментари по темата са добре дошли. 🙂

BigInteger за JavaScript

Written By: Деян - Apr• 21•13

Малко преди първия изпит по JavaScript в академията на Телерик, реших да си напиша един BigInteger за всеки случай, ако случайно ми се наложи да правя сметки с големи цели числа. Като цяло си припомних детските години, в които ни учеха да събираме, изваждаме и делим числа :), което ако трябва да  съм честен се оказа не чак толкова  тривиална задачка. 🙂

За имплементацията на моя BigInteger съм използвал представяне на числата под формата на масив от цифри. Не е най-ефективното решение предвид че за всяка цифра от 1 до 9 използвам число в JavaScript, което по принцип е в доста по-големи граници. Бих се пробвал като намеря време да направя нова имплементация, която вътрешно използва не масив от всички цифри, ами такъв от по-големи числа, така че да не се налага да заделям толкова излишна памет за едно многоцифрено число.

Въпреки тези недостатъци трябва да кажа, че все пак имплементацията работи доста бързо и поне аз след известно тестване не съм намерил някакви проблеми с бързодействието.

Ето линк към моя GitHub профил, където може да се свали имплементацията:

GitHub link: BigIntegerDPY

Към самия файл най-отгоре съм прикачил и кратка документация, обясняваща основните функционалности на обекта. Ето и един цитат към нея.

///This JS object was written by Deyan Yosifov.
///BigInteger keeps an integer number with infinite number of digits.
///It supports functions plus, minus and multiply which return another BigInteger object
///Also has toString() and toNum functions in it.
///for parsing long numbers from string use parseToBigInteger(str)
///for parsing from small number use new BigInteger(num)
///for parsing from array of digits use new BigInteger(array, hasMinusSign)

Мисля че след известно тестване съм изчистил всички бъгове и обекта работи напълно коректно, но все пак ако някой открие някакви такива бих се радвал да сподели. 🙂

Tetris game – Telerik OOP Teamwork

Written By: Деян - Mar• 22•13

След тази седмичната екипна работа в Телерик, реших да споделя и резултата от работата на моя отбор – Chipmunk :). Като начало ние си бяхме поставили за цел да направим имплементация на популярната игра Тетрис и като краен резултат, въпреки краткия срок и многото други домашни и задачки, които трябваше да движим успоредно с отборната работа, мисля че като цяло крайния резултат не се получи зле. За краткото време направихме един ООП проект на C#, във вид на конзолно приложение.

Ето и скрийншот от крайния резултат:

chipmunkTetrisScreenShot

Ако някой иска да погледне кода, може да го види качен в моя GitHub профил на този линк: TetrisGame SourceCode.

Там може да бъде видяна и кратка документация с кратки обяснения на основните класове и организацията на кода.

За да свалите играта може да използвате този линк: TetrisGame DownLoad!

Поне към момента не са ми известни бъгове, но ако някой открие, ще се радвам ако ги сподели. 🙂 Enjoy!

Пресмятане на Аритметичен израз с приоритети на операторите

Written By: Деян - Mar• 02•13

Реших да разкажа, за моята имплементация, на една от задачите от втората част от курса по C# в Telerik Academy, а именно примерна имплементация на алгоритъма Shunting yard, за пресмятане на аритметичен израз.

В случая като въведение в проблема, ще дам един пример, за такъв аритметичен израз и неговото пресмятане със съответните приоритети на операторите:

2 + 3 * 5 + ( 4 * sqrt ( 4 ) ^ 5 – 5 ) – 1 = 139

Важно е тук да се спомене, че най-висок приоритет имат скобите, след тях е операцията повдигане на степен (която е дясно-асоциативна за разлика от другите ляво-асоциативни операции), след това са операциите умножение и деление и накрая с най-нисък приоритет са операциите събиране и изваждане.  В израза също така участва и едно-аргументната функция корен квадратен (sqrt), чийто приоритет в случая е излишно да бъде търсен, тъй като за нейния запис използваме скоби, ограждащи аргумента й.

За построяването на дървото на операциите, необходимо ни, за да знаем, коя операция да извършим първа, коя втора и така нататък, докато пресметнем целия израз, съм използвал така наречения Shunting Yard Algorithm. Цъкайки на дадения линк към wikipedia, по надолу в статията ще достигнете и до описанието на последователността на действията в самия алгоритъм, които са:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-While there are tokens to be read:
--Read a token.
--If the token is a number, then add it to the output queue.
--If the token is a function token, then push it onto the stack.
--If the token is a function argument separator (e.g., a comma):
----Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue. If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched.
--If the token is an operator, o1, then:
----while there is an operator token, o2, at the top of the stack, and
either o1 is left-associative and its precedence is less than or equal to that of o2,
or o1 has precedence less than that of o2,
pop o2 off the stack, onto the output queue;
----push o1 onto the stack.
--If the token is a left parenthesis, then push it onto the stack.
--If the token is a right parenthesis:
----Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
----Pop the left parenthesis from the stack, but not onto the output queue.
----If the token at the top of the stack is a function token, pop it onto the output queue.
----If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
-When there are no more tokens to read:
--While there are still operator tokens in the stack:
----If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
----Pop the operator onto the output queue.
-Exit.

Преди да започна с имплементацията на алгоритъма, аз си създавам една структура PredefinedFunction, която ще ми описва основните параметри на всяка една предефинирана в програмата функция – брой параметри, приемаща функцията; дали е ляво или дясно асоциативна; самите действия, които ще прави функцията върху параметрите си, записани като поле от тип делегат; приоритета на конкретната функция. Ето и имплементацията на тази структура:

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public struct PredefinedFunction
    {
        public int parametersCount;
        public bool isLeftAssociated;
        public Function.Calculate calculateFunction;
        public int priority;
        public PredefinedFunction(int parametersCount, bool isLeftAssociated, Function.Calculate calculateFunction, int priority)
            : this()
        {
            this.parametersCount = parametersCount;
            this.isLeftAssociated = isLeftAssociated;
            this.calculateFunction = calculateFunction;
            this.priority = priority;
        }
    }

След това дефинирам класа Function, който ще съдържа речник от предефинирани функции, като всяка ще знае как да работи, благодарение на свойството Result ползващо полето делегат calculateFunction, на съответната предефинирана функция. Ето и имплементацията на този клас.

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    public class Function
    {
        public delegate double Calculate(double[] parameters);
 
        private double[] parameters;
        private string functionName;
        private static Dictionary<string, PredefinedFunction> functionsLibrary;
 
        static Function()
        {
            functionsLibrary = new Dictionary<string, PredefinedFunction>();
            functionsLibrary.Add("+", new PredefinedFunction(2, true, new Calculate(x => x[0] + x[1]), 1));
            functionsLibrary.Add("-", new PredefinedFunction(2, true, new Calculate(x => x[0] - x[1]), 1));
            functionsLibrary.Add("*", new PredefinedFunction(2, true, new Calculate(x => x[0] * x[1]), 2));
            functionsLibrary.Add("/", new PredefinedFunction(2, true, new Calculate(x => x[0] / x[1]), 2));
            functionsLibrary.Add("^", new PredefinedFunction(2, false, new Calculate(x => Math.Pow(x[0], x[1])), 3));
            functionsLibrary.Add("pow", new PredefinedFunction(2, true, new Calculate(x => Math.Pow(x[0], x[1])), 4));
            functionsLibrary.Add("ln", new PredefinedFunction(1, true, new Calculate(x => Math.Log(x[0])), 4));
            functionsLibrary.Add("sqrt", new PredefinedFunction(1, true, new Calculate(x => Math.Sqrt(x[0])), 4));
        }
 
        public Function(string functionName)
        {
            this.functionName = functionName;
            this.parameters = new double[functionsLibrary[functionName].parametersCount];
        }
 
        public static Dictionary<string, PredefinedFunction> Library
        {
            get
            {
                return functionsLibrary;
            }
        }
 
        public string FunctionName
        {
            get
            {
                return this.functionName;
            }
        }
 
        public double this[int index]
        {
            get
            {
                return this.parameters[index];
            }
            set
            {
                this.parameters[index] = value;
            }
        }
 
        public int ParameterCount
        {
            get
            {
                return functionsLibrary[functionName].parametersCount;
            }
        }
 
        public int Precedence
        {
            get
            {
                return functionsLibrary[functionName].priority;
            }
        }
 
        public bool IsLeftAssociated
        {
            get
            {
                return functionsLibrary[functionName].isLeftAssociated;
            }
        }
 
        public double Result
        {
            get
            {
                return functionsLibrary[this.functionName].calculateFunction(this.parameters);
            }
        }
    }

Статичния конструктор Function() има за цел да зареди речника от предефинирани функции, преди използването на класа. Тук е мястото да отбележа, че използването на този речник, свързващ символ в израза с конкретна функция, прави изключително лесно в последствие добавянето на нови и нови функции в кода. Ако искаме да добавим 1 нова функция, трябва да добавим само 1 ред към съществуващия код, описващ как е записа на тази функция в аритметичния израз и какви са нейните предефинирани характеристики (приоритет, асоциативност, конкретни пресмятания с аргументите и броя аргументи).

Тук следва да бъде спомената и имплементацията на Shunting Yard Algorithm:

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
    public static void ShuntingYard(string token, Stack<string> output, Stack<string> operators)
    {
        #region Number
        double tempNumber;
        if (double.TryParse(token, out tempNumber))
        {
            output.Push(token);
            return;
        }
        #endregion Number
 
        #region Function or Operator
        if (Function.Library.ContainsKey(token))
        {
            if (Function.Library[token].priority == 4)
            {
                operators.Push(token);
            }
            else
            {
                while (IsThereHigherOperatorWaiting(token, operators))
                {
                    output.Push(operators.Pop());
                }
                operators.Push(token);
            }
            return;
        }
        #endregion Function or Operator
 
        #region Parenthesis
        if (token == "(")
        {
            operators.Push(token);
            return;
        }
        if (token == ")")
        {
            GoToPreviousLeftParenthesisAndRemoveThem(output, operators);
            return;
        }
        #endregion Parenthesis
 
        #region Comma
        if (token == ",")
        {
            GoToPreviousLeftParenthesis(output, operators);
            return;
        }
        #endregion Comma
    }

Имплементацията следва точно горе-описания с думи алгоритъм.
В крайна сметка тази имплементация търси последователността на сметките, които трябва да се извършат, като най-първото пресмятане, ще бъде открито последно. Поради тази причина използвам една глобална променлива от тип стек, която пълня с всеки следващ намерен от алгоритъма “ход”. Изпразвайки стека елемент по елемент в последствие аз ще получа правилната последователност на извършваните операциите, която съответства и на съответното дърво на операциите. Ето и дървото за горния пример за по-голяма яснота.

ArithmeticTree

Пресмятането става посредством метода RNP (идващо от Reversed Polish Notation), който вътрешно използва метода ParseNumberOrFunction().

295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
    public static double CalculateRPN()
    {
        return ParseNumberOrFunction();
    }
 
    private static double ParseNumberOrFunction()
    {
        double result = 0;
        if (double.TryParse(RPN.Peek(), out result))
        {
            RPN.Pop();
            return result;
        }
 
        Function function = new Function(RPN.Pop());
        if (function.IsLeftAssociated)
        {
            for (int i = function.ParameterCount - 1; i >= 0; i--)
            {
                function[i] = ParseNumberOrFunction();
            }
        }
        else
        {
            for (int i = function.ParameterCount - 1; i >= 0; i--)
            {
                function[i] = ParseNumberOrFunction();
            }
        }
 
        result = function.Result;
 
        return result;
    }

Идеята е, че се чете поредния елемент от стека RPN, който е генериран предварително от Shunting Yard алгоритъма и съхранен като глобална променлива Stack<string> RNP. Тук е редно да се спомене, че по принцип не е добре да се използва глобална променлива, което го отчитам като недостатък на кода. Това обаче може лесно да се корегира, като променливата се дефинира да речем в Main метода и после се предава като аргумент на необходимите методи. И все пак в конкретния случай съм използвал глобална променлива като едно неформално решение на проблема. При четене на съответния елемент в стека, ако той е число то то се връща като стойност на метода, а ако е функция или оператор, тогава се извиква рекурсивно същия метод за всеки от аргументите на последната извикана функция. По този начин дървото на пресмятанията се обхожда в дълбочина пресмятайки, най-накрая чрез рекурсията крайния резултат.
Ето и пълния код на моята C# имплементация:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
using System;
using System.Collections.Generic;
using System.Threading;
using System.Globalization;
 
class CalculateArithmeticalExpression
{
    public struct PredefinedFunction
    {
        public int parametersCount;
        public bool isLeftAssociated;
        public Function.Calculate calculateFunction;
        public int priority;
        public PredefinedFunction(int parametersCount, bool isLeftAssociated, Function.Calculate calculateFunction, int priority)
            : this()
        {
            this.parametersCount = parametersCount;
            this.isLeftAssociated = isLeftAssociated;
            this.calculateFunction = calculateFunction;
            this.priority = priority;
        }
    }
 
    public class Function
    {
        public delegate double Calculate(double[] parameters);
 
        private double[] parameters;
        private string functionName;
        private static Dictionary<string, PredefinedFunction> functionsLibrary;
 
        static Function()
        {
            functionsLibrary = new Dictionary<string, PredefinedFunction>();
            functionsLibrary.Add("+", new PredefinedFunction(2, true, new Calculate(x => x[0] + x[1]), 1));
            functionsLibrary.Add("-", new PredefinedFunction(2, true, new Calculate(x => x[0] - x[1]), 1));
            functionsLibrary.Add("*", new PredefinedFunction(2, true, new Calculate(x => x[0] * x[1]), 2));
            functionsLibrary.Add("/", new PredefinedFunction(2, true, new Calculate(x => x[0] / x[1]), 2));
            functionsLibrary.Add("^", new PredefinedFunction(2, false, new Calculate(x => Math.Pow(x[0], x[1])), 3));
            functionsLibrary.Add("pow", new PredefinedFunction(2, true, new Calculate(x => Math.Pow(x[0], x[1])), 4));
            functionsLibrary.Add("ln", new PredefinedFunction(1, true, new Calculate(x => Math.Log(x[0])), 4));
            functionsLibrary.Add("sqrt", new PredefinedFunction(1, true, new Calculate(x => Math.Sqrt(x[0])), 4));
        }
 
        public Function(string functionName)
        {
            this.functionName = functionName;
            this.parameters = new double[functionsLibrary[functionName].parametersCount];
        }
 
        public static Dictionary<string, PredefinedFunction> Library
        {
            get
            {
                return functionsLibrary;
            }
        }
 
        public string FunctionName
        {
            get
            {
                return this.functionName;
            }
        }
 
        public double this[int index]
        {
            get
            {
                return this.parameters[index];
            }
            set
            {
                this.parameters[index] = value;
            }
        }
 
        public int ParameterCount
        {
            get
            {
                return functionsLibrary[functionName].parametersCount;
            }
        }
 
        public int Precedence
        {
            get
            {
                return functionsLibrary[functionName].priority;
            }
        }
 
        public bool IsLeftAssociated
        {
            get
            {
                return functionsLibrary[functionName].isLeftAssociated;
            }
        }
 
        public double Result
        {
            get
            {
                return functionsLibrary[this.functionName].calculateFunction(this.parameters);
            }
        }
    }
 
    public static Stack<string> RPN;
 
    static void Main()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
        PrintTestInput("2 + 3 * 5 / ( 4 - 5 ) - 1");
        PrintTestInput("2 + 3 * 5 + ( 4 * pow ( ( sqrt ( 4 + ln ( 1 ) ) - 1 ) , 5 ) - 5 ) - 1");
 
        PrintTestInput("2 + 3 * 5 + ( 4 * pow ( ( sqrt ( 4 ) ) , 5 ) - 5 ) - 1");
        PrintTestInput("2 + 3 * 5 + ( 4 * ( sqrt ( 4 ) ^ 5 ) - 5 ) - 1");
        PrintTestInput("2 + 3 * 5 + ( 4 * sqrt ( 4 ) ^ 5 - 5 ) - 1");
        PrintTestInput("2 + 3 * 5 + ( 2 ^ 2 * sqrt ( 4 ) ^ 5 - 5 ) - 1");
        PrintTestInput("2 + 3 * 5 + ( 2 ^ 2 * sqrt ( 4 ) ^ 2 ^ 2 * 2 - 5 ) - 1");
 
        PrintTestInput("( 2 + 3 ) * 5");
    }
 
    public static void PrintTestInput(string input)
    {
        Console.WriteLine("At the begining we have the input expression:\n{0}", input);
        RPN = GenerateRNP(input);
        Console.WriteLine("First we generate its Polish Notation equivalent:");
        PrintRNP();
        Console.WriteLine();
        RPN = GenerateRNP(input);
        Console.WriteLine("Finally the calculated result: {0}", CalculateRPN());
        Console.WriteLine();
    }
 
    public static void PrintFunctionLibrary()
    {
        foreach (string name in Function.Library.Keys)
        {
            Console.WriteLine("function {0}, priority {1}", name, Function.Library[name].priority);
        }
    }
 
    public static Stack<string> GenerateRNP(string expression)
    {
        Stack<string> output = new Stack<string>();
        Stack<string> operators = new Stack<string>();
        string[] tokens = expression.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        for (int i = 0; i < tokens.Length; i++) 
        {
            ShuntingYard(tokens[i], output, operators); 
        }
 
        while (operators.Count > 0)
        {
            output.Push(operators.Pop());
        }
        return output;
    }
 
    public static void ShuntingYard(string token, Stack<string> output, Stack<string> operators)
    {
        #region Number
        double tempNumber;
        if (double.TryParse(token, out tempNumber))
        {
            output.Push(token);
            return;
        }
        #endregion Number
 
        #region Function or Operator
        if (Function.Library.ContainsKey(token))
        {
            if (Function.Library[token].priority == 4)
            {
                operators.Push(token);
            }
            else
            {
                while (IsThereHigherOperatorWaiting(token, operators))
                {
                    output.Push(operators.Pop());
                }
                operators.Push(token);
            }
            return;
        }
        #endregion Function or Operator
 
        #region Parenthesis
        if (token == "(")
        {
            operators.Push(token);
            return;
        }
        if (token == ")")
        {
            GoToPreviousLeftParenthesisAndRemoveThem(output, operators);
            return;
        }
        #endregion Parenthesis
 
        #region Comma
        if (token == ",")
        {
            GoToPreviousLeftParenthesis(output, operators);
            return;
        }
        #endregion Comma
    }
 
    private static void GoToPreviousLeftParenthesis(Stack<string> output, Stack<string> operators)
    {
        while (operators.Count > 0)
        {
            if (operators.Peek() == "(")
            {
                return;
            }
            output.Push(operators.Pop());
        }
    }
 
    private static void GoToPreviousLeftParenthesisAndRemoveThem(Stack<string> output, Stack<string> operators)
    {
        while (operators.Count > 0)
        {
            if (operators.Peek() == "(")
            {
                operators.Pop();
 
                if (operators.Count > 0 && Function.Library.ContainsKey(operators.Peek()))
                {
                    if (Function.Library[operators.Peek()].priority == 4)
                    {
                        output.Push(operators.Pop());
                    }
                }
 
                return;
            }
 
            output.Push(operators.Pop());
        }
    }
 
    private static bool IsThereHigherOperatorWaiting(string token, Stack<string> operators)
    {
        if (operators.Count == 0) return false;
        if (Function.Library.ContainsKey(operators.Peek()) && Function.Library[operators.Peek()].priority < 4)
        {
            if (Function.Library[token].isLeftAssociated)
            {
                if (Function.Library[token].priority <= Function.Library[operators.Peek()].priority)
                {
                    return true;
                }
            }
            else
            {
                if (Function.Library[token].priority < Function.Library[operators.Peek()].priority)
                {
                    //Console.WriteLine("No left associatives in this homework!");
                    return true;
                }
            }
        }
        return false;
    }
 
    public static void PrintRNP()
    {
        while (RPN.Count > 0)
        {
            Console.Write("{0} ", RPN.Pop());
        }
    }
 
    public static double CalculateRPN()
    {
        return ParseNumberOrFunction();
    }
 
    private static double ParseNumberOrFunction()
    {
        double result = 0;
        if (double.TryParse(RPN.Peek(), out result))
        {
            RPN.Pop();
            return result;
        }
 
        Function function = new Function(RPN.Pop());
        if (function.IsLeftAssociated)
        {
            for (int i = function.ParameterCount - 1; i >= 0; i--)
            {
                function[i] = ParseNumberOrFunction();
            }
        }
        else
        {
            for (int i = function.ParameterCount - 1; i >= 0; i--)
            {
                function[i] = ParseNumberOrFunction();
            }
        }
 
        result = function.Result;
 
        return result;
    }
}

Дипломна работа – Футболен стадион

Written By: Деян - Feb• 17•13

Реших, че е не лоша идея, предвид че от доста познати получих искането да им пратя таблата на дипломната ми работа, да се престраша в крайна сметка и макар да не съм на 100% доволен от постигнатия резултат предимно от липса на време (да си потърся извинение веднага :)) реших да кача таблата в сайта си.

Малко предистория – дипломната работа е на тема “Нов национален футболен стадион” и успях да я защитя с отличие към края на януари 2013 година. Обектът е ситуиран в кв. Обеля, гр. София, в близост до метрото, но това ще може да го види, който иска, по-подробно в таблата към проекта. За моделирането на формата съм използвал писан от мен софтуер – GDL обект за Архикад,  на име BezierSurfaceDPY-v14–11-01-2013.gsm, за който може да прочетете повече подробности, следвайки линка към тази статия.

Ето и галерия с таблата:

Как да си намерим и модифицираме тема в WordPress

Written By: Деян - Feb• 16•13

Тази статия я започнах по питане на една колежка като лично съобщение в системата на Телерик, но се оказа че съобщенията са с максимална дължина от 1500 символа и прецених, че написаното явно е по-подходящо за статия в блога :). Ето и моят опит след първите ми 2-3 дена работа с wordpress, споделен под формата на лично обръщение 🙂

Здрасти! Ами честно казано ми харесва твоята тема, изчистена е. Може би бих потърсил начин да се случи някакъв лек фон на текста на всяка статия, за да не е съвсем цялото с една шарка (може например да е някакъв леко прозрачен фон), но това по-скоро ще го прецениш като сложиш няколко статии и видиш как ще стои. Ако искаш да си намериш друга тема най-лесно е като отидеш в Appearance -> Themes -> Install themes, тъй като там има различни критерии, по които можеш да търсиш. Ако сложиш много критерии много вероятно е да не откриеш нищо. Харесай си 1 или 2, които според теб са най-важни и пусни да търси. Някои теми са платени, така че с тях не се занимавай. Пробвай само безплатните. Принципно аз установих, че която и тема да си свалиш, все ще има един куп неща, които да те дразнят. За щастие това се оправя сравнително лесно. В Appearance -> Editor имаш достъп както до php файловете така и до css файловете. PHP-то поне за мен си е малко hardcore (просто не съм се занимавал никога), но като цяло css-a би трябвало да ти е напълно достатъчен за повечето козметични промени. Хубавото е че темите в общия случай са доста добре направени семантично и всички основни елементи са с някакви добре именувани класове (ако си погледнеш html-а на страницата да речен с помощта на Chrome, ще видиш че почти всеки таг си има някакво id или някакъв или дори няколко класа). Това ти помага много, за да можеш лесно да си селектираш това, което искаш със css и да си му променяш стила. Аз например си скрих коментарите навсякъде освен в блога с помощта на css, сложих си радиусчета на някои рамки, сложих някои фонове и т.н., докато спря да ме дразни толкова цялостния изглед. Ето пример за моите добавки към css-a:
/**************************** DPY start ***********************************************/

body.page div#respond{
display:none;
}
a{
text-decoration:none !important;
}
a:hover{
font-weight:bold !important;
}

div.entry a{
color:#cc0014;
}

a.rar-download{
background:url(‘https://deyan-yosifov.com/pics/rar.png’);
background-repeat:no-repeat;
padding-left:25px;
background-size: 20px;
}

a.gdl-download{
background:url(‘https://deyan-yosifov.com/pics/gdl.png’);
background-repeat:no-repeat;
padding-left:16px;
background-size: 12px;
}

div#side-bar{
background:#fcfbe1 !important;
border-radius:15px;
}
/*Gallery*/
div#post-76 div.entry p{
padding: 10px !important;
border:1px solid black !important;
border-radius:15px !important;
background:#fcfbe1 !important;
text-align:center !important;
}
div#post-76 div.entry p a{
display:block !important;
}
/*END Gallery*/

/*Menu item*/
ul#menu-dpy-menu{
margin:2px !important;
}
/*End Menu item*/

/****************************** DPY end **********************************************/

На много места след селектора и стойността съм използвал !important, като по този начин си гарантирам, че няма някой друг css по-надолу да бие моя. 🙂
Освен това, когато си пишеш някакъв пост или страница в редактора за писане може да си, както в режим Visual, така и в режим Text, като това второто си е html. То ест там можеш да си слагаш разни тагове, да им слагаш класове, които после да използваш за селектиранe от css-a и т.н. Дори можеш да си добавяш script тагове, в които да пишеш javascript, с който да си правиш разни магии 🙂 Това е за сега. Пробвай да си пооцветиш страничката, пък ако си харесаш друга тема пробвай и с нея. При всички положения едва ли ще намериш тема, която да ти хареса на 100% :).
Поздрави!

Ще очаквам коментари и въпроси под темата, ако има такива разбира се. 🙂

Дефиниране на клас Матрица – C# – Част 2 – Задача 6 – Multidimensional Arrays

Written By: Деян - Feb• 16•13

Условието на задачата е:
6.* Write a class Matrix, to holds a matrix of integers. Overload the operators for adding, subtracting and multiplying of matrices, indexer for accessing the matrix content and ToString().

Тази задача лично на мен ми беше изключително полезна и интересна, тъй като за първи път се сблъсках с дефинирането на клас до детайли като дефиниране на оператор (+, – и т.н.), както и дефиниране на индексатор. За да се имплементира коректно поведението на матриците според установените в математиката дефиниции, е необходимо да се подходи с внимание към проверките при различните оператори, свързани с размерностите на самите матрици, a също така е важна и капсулацията на полетата. Към операциите събиране, изваждане и умножение съм направил проверка за размерностите на матриците и хвърлям изключение ако има проблем с размерите (при събиране и изваждане дименсиите на двете матрици трябва да са равни, а при умножението броя колони на първата матрица трябва да е равен на броя редове на втората). Полетата на класа matrix, rows, columns са private, за да може да са достъпни единствено през публичното свойство Value, което съответно се ползва и от конструкторите и операторите. По този начин си гарантирам че няма да стане някаква грешка и някой да промени информацията за броя на редовете примерно без реално да е променил броя на редовете на самата матрица. В Main метода съм направил няколко теста за различните оператори и случаи.

Ето и примерната имплементация:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
using System;
using System.Text;
 
class Matrix
{
    private int[,] matrix;
    private int rows;
    private int columns;
 
    public int[,] Value
    {
        get
        {
            return this.matrix;
        }
        set
        {
            this.matrix = value;
            this.rows = value.GetLength(0);
            this.columns = value.GetLength(1);
        }
    }
 
    public int Rows
    {
        get
        {
            return this.rows;
        }
    }
 
    public int Columns
    {
        get
        {
            return this.columns;
        }
    }
 
    public Matrix(int[,] matrix)
    {
        this.Value = (int[,])matrix.Clone();
    }
 
    public Matrix(int rows, int cols) : this(new int[rows, cols]) { }
 
    public static Matrix operator +(Matrix a, Matrix b)
    {
        if (a.Rows != b.Rows || a.Columns != b.Columns)
        {
            throw new FormatException("Matrixes must have same dimensions!");
        }
        else
        {
            Matrix result = new Matrix(a.Rows, a.Columns);
            for (int i = 0; i < a.Rows; i++)
            {
                for (int j = 0; j < a.Columns; j++)
                {
                    result[i, j] = a[i, j] + b[i, j];
                }
            }
            return result;
        }
    }
 
    public static Matrix operator -(Matrix a, Matrix b)
    {
        if (a.Rows != b.Rows || a.Columns != b.Columns)
        {
            throw new FormatException("Matrixes must have same dimensions!");
        }
        else
        {
            Matrix result = new Matrix(a.Rows, a.Columns);
            for (int i = 0; i < a.Rows; i++)
            {
                for (int j = 0; j < a.Columns; j++)
                {
                    result[i, j] = a[i, j] - b[i, j];
                }
            }
            return result;
        }
    }
 
    public static Matrix operator *(Matrix a, Matrix b)
    {
        if (a.Columns != b.Rows)
        {
            throw new FormatException("First matrix columns number must be equal to second matrix rows number!");
        }
        else
        {
            Matrix result = new Matrix(a.Rows, b.Columns);
            for (int i = 0; i < a.Rows; i++)
            {
                for (int j = 0; j < b.Columns; j++)
                {
                    for (int k = 0; k < a.Columns; k++)
                    {
                        result[i, j] += a[i, k] * b[k, j];
                    }
                }
            }
            return result;
        }
    }
 
    public int this[int i, int j]
    {
        get
        {
            return this.Value[i, j];
        }
        set
        {
            this.Value[i, j] = value;
        }
    }
 
    public override string ToString()
    {
        StringBuilder result = new StringBuilder();
        result.Append(this[0, 0]);
        for (int j = 1; j < this.Columns; j++)
        {
            result.AppendFormat(" {0}", this[0, j]);
        }
        for (int i = 1; i < this.Rows; i++)
        {
            result.AppendFormat("\n{0}", this[i, 0]);
            for (int j = 1; j < this.Columns; j++)
            {
                result.AppendFormat(" {0}", this[i, j]);
            }
        }
        return result.ToString();
    }
 
    static void Main()
    {
        Matrix a = new Matrix(3, 4);
        a.Value[1, 1] = 5;
        Matrix b = new Matrix(3, 4);
        b.Value[1, 1] = 3;
        Matrix c = new Matrix(4, 2);
        c.Value[1, 1] = 3;
 
        Console.WriteLine("a.Rows: {0}; a.Columns: {1}", a.Rows, a.Columns);
        Console.WriteLine(a);
        Console.WriteLine();
 
        Console.WriteLine("b.Rows: {0}; b.Columns: {1}", b.Rows, b.Columns);
        Console.WriteLine(b);
        Console.WriteLine();
 
        Console.WriteLine("c.Rows: {0}; c.Columns: {1}", c.Rows, c.Columns);
        Console.WriteLine(c);
        Console.WriteLine();
 
        Console.WriteLine("\na+b=\n{0}", a + b);
        Console.WriteLine("\na-b=\n{0}", a - b);
        Console.WriteLine("\na*c=\n{0}", a * c);
 
        Console.WriteLine("\na+c=");
        try
        {
            Console.WriteLine("\na+c=\n{0}", a + c);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
 
        Console.WriteLine("\nb-c=");
        try
        {
            Console.WriteLine("\nb-c=\n{0}", b - c);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
 
        Console.WriteLine("\na*b=");
        try
        {
            Console.WriteLine("\na*b=\n{0}", a * b);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
 
        int [,] v0 = {{4, 8, 15, 16, 23, 42}};
        Matrix v = new Matrix(v0);
        int[,] t0 = { { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 } };
        Matrix t = new Matrix(t0);
 
        Console.WriteLine();
        Console.WriteLine("v.Rows: {0}; v.Columns: {1}", v.Rows, v.Columns);
        Console.WriteLine(v);
        Console.WriteLine();
 
        Console.WriteLine("t.Rows: {0}; t.Columns: {1}", t.Rows, t.Columns);
        Console.WriteLine(t);
        Console.WriteLine();
 
        Console.WriteLine("\nv*t=\n{0}", v * t);
 
        Console.WriteLine();
        int i = 0;
        int j = 0;
        Console.WriteLine("(v*t)[{1},{2}]={0}", (v * t)[i, j], i, j);
    }
}

Имплементация на Shooter Game Navigation като Plug-in за Google Sketch Up

Written By: Деян - Feb• 15•13

sketchupВероятно, който се е занимавал с моделиране на 3д обекти, под някаква форма се е сблъсквал с програмата Google Sketch Up. Личното ми мнение е, че това е една страхотна програма, изключително лека (зарежда със скоростта на Notepad), а в същото време дава доста големи възможности за свободно моделиране на всевъзможни форми и като цяло поне за мен за създаването на малки по размер 3д обекти и дизайни, както и за концептуални модели, програмата е наистина безценна.
Още повече се запалих по тази програма преди няколко години, когато разбрах, че за Sketch Up си има една цяла общност от ентусиасти (Sketchucation.com), които пишат и публикуват безплатни плъгини (направени за собствени нужди или по поръчка на някой пишещ във форума), които плъгини добавени към програмата вече могат да я направят наистина мощно средство за лесно създаване на модели за дизайн и архитектура например.

Така след като известно време ползвах различни плъгини към програмата, в един момент реших и аз да се пробвам да се науча и да пиша такива. И това се оказа далеч не толкова трудна задача – макар и никога дотогава да не бях и чувал за Ruby, в интернет има изключително удобна и подробна документация на Sketch Up Ruby API и след прочитането на една малка книжка за основите на езика, аз вече можех спокойно да пиша първите си плъгини.

Цялата идея е да се напише един файл с разширение .rb, който да се сложи в инсталационната директория на програмата в папка Plugins. При зареждане на Sketch Up, програмата  компилира всички файлове в папка Plugins, и те вече стават активни в текущо отворения прозорец на Sketch Up. Тук е момента и да кажа, че ако си свалите стотина Plugin-a в папката, то вече Sketch Up далеч няма да зарежда вече със скоростта на Notepad, ами по скоро ще се доближи до тази на 3DSMax, което както и да го погледнем не е на добре. 🙂 Затова излишните Plugin-и по-добре ги скрийте в една директория да речем HiddenPlugins, и когато ви потрябват си ги извадете от там.

До към септември 2012 си бях написал десетина плъгина за лични нужди, свързани с някои от архитектурните проекти, върху които работех и честно казано от тогава не ми остава време сядам да се занимавам с това (странно защо този времеви период съвпада със започването ми като студент в академията на Телерик 🙂 ). И все пак въпреки, че имах желание да пипна малко въпросните плъгини, за да бъдат малко по-подходящи за гледане от други хора :), реших да представя поне един от тях, който позволява движението в моделното пространство на Sketch Up да бъде като това в познатите ни компютърни игри като CounterStrike например. Поне според мен този plugin значително улеснява придвижването, особено в по-големи модели, където “зуумванете” и “орбитирането” (стандартните Tool-ове в Sketch Up за навигиране), просто не работят както трябва и навигирането става изключително затруднено.

Ето и линк към моя tool: DPY-Camera-24-09-2012.rar

За да го инсталирате, както казах и по-горе, просто слагате .rar-файла в директорията Plugins на SketchUp и го екстрактвате. В самия архив освен .rb файла има една папка, съдържаща картинки използвани от моят tool, както и Win32API.so файл, който е необходим, тъй като в скрипта съм използвал външни за Ruby функции, за да мога да манипулирам успешно мишката под Windows, тъй като за правилната работа на този tool е необходимо да мога да местя курсора на определена позиция.

Ще започна първо с обяснение за хората, които искат да използват моят tool, но не държат да знаят точно как работи кодът му. След като сте инсталирали плъгина, отваряте Sketch Up, а ако е отворен от преди го рестартирайте (тъй като плъгина се компилира и зарежда като функциониращ tool, при стартиране на програмата и няма да се зареди ако не рестартирате Sketch Up).  Намерете иконката в Toolbar-а, както е показано на картиката по-долу.

IncludeDPYCameraTool

DPY-Camera се стартира при цъкане на иконката в Toolbar-a. Изключването на tool-a, става посредством натискането на spacebar (което активира tool-a за селекция в Sketch Up), или при натискането на друг бутон, който активира съответен tool (например М за move). Най-долу в message bar-a се изписват помощни съобщения по време на използването на tool-a, указващи клавишите за навигация, стойност на скоростта и др. Скоростта се задава в m/s като ако искате да се движите по-бързо или по-бавно просто пишете цифрено новата стойност и натискате Enter. Това е удобно тъй като в зависимост от размерите на обекта, който искате да огледате, може да се налага да се приближавате детайлно близо(примерно при моделиране дизайна на една мебел), или пък да се придвижвате с много голяма скорост (ако моделирате един градски пейзаж да речем).

StartDPYCameraTool

След като сте стартирали и направили някакво движение ще забележите, че долу при съобщенията се появява един надпис “Click for MouseCorrection!”. Това е поради един бъг, който не успях да намеря по-умен начин да корегирам, свързан с малко отместване на данните за позицията на мишка от Win32API и от SketchUpRubyAPI. Просто цъкнете веднъж с мишката и ще можете да използвате пълната функционалност на tool-a от тук нататък. Ако не цъкнете – ами отново ще може да се придвижвате безпроблемно, но няма да можете да използвате бонус опцията наречена “Gun Mode” :), при която в последствие ще можете да си включите един мерник и при цъкане да “гръмнете” (или в случая да изтриете от модела) обекта който ви е на мушката (edge, face, group, component или каквото ви се изпречи насреща 🙂 ).

MouseCorrectionDPYCameraTool

След като веднъж сте кликнали с мишката ще видите, че долу в ляво надписа “MouseCorrection” е изчезнал и на негово място имате опция “GunMode On/Off”. GunMode се включва и изключва с Escape, както е и описано в message bar-a, и при включен GunMode мерникът става кръстче с точка по средата (ако е изключен стои само точката).

GunModeDPYCameraTool

Когато цъкнете при включен GunMode до курсора се изписва “BOOM!”  и се изтрива обекта, който е на мушката, така че бъдете внимателни при включен мерник 🙂 (не че не можете с undo да си върнете изтритото, но все пак).

BoomDPYCameraTool

В общи линии това е по обяснението за използването на Tool-a. Надявам се да ви харесва и да ви бъде полезен. Имах желанието (но не и времето) за добавяне на повече функционалност – не само можеш да триеш при кликане с мишката ами примерно да рисуваш нещо върху модела или някаква друга функционално каквато може да се сети някой. Ако някой има идеи за подобрение нека пише или нека направо работи върху кода, който ще обясня накратко по-надолу.

Като начало каква е идеята на самия код, за да работи в конкретния случай. Движението със стрелките е просто и ясно – при задържане на някоя стрелките камерата започва да се движи с необходимата скорост в съответната посока. Как работи въртенето на камерата? Ами също не толкова сложно – при преместване на мишката tool-a отчита новата позиция на мишката, запазва мястото на камерата, премества фокусната точка върху някоя точка по лъча получен при свързването на позицията на камерата и новата точка на мишката. След като камерата е преместена нашият tool връща мястото на мишката в първоначалната позиция. Така мишката винаги стои по средата на екрана, във фокуса на камерата и съвпадайки с посоката на движение при задържана стрелка напред.

Поглеждайки в Sketch Up Ruby API можем да видим, че на пръв поглед един tool в скеч ъп има контрол над всичките ни необходими команди за прихващане на събития свързани с мишката и клавиатурата. На втори и трети поглед, почвайки да пишем кода, се сблъскваме с няколко проблема, които са свързани както с непълнотии в API-то, така и с бъгове в някои от съществуващите опции.

На първо време нека кажа какво трябва да се направи, за да си дефинираш един tool в Sketch Up. Ами всъщност е доста просто – просто си правиш един нов клас и него започваш да го пълниш с методите описани в документацията, необходими за да бъде разпозната в последствие от Sketch Up създадената инстанция на този клас като tool, а не като някакъв друг произволен клас. Важен в случая е метода activate, който активира tool-a при създаване на инстанцията на класа. В този метод се случват и началните действия, необходими за работата на нашия tool. Ето я и конкретната имплементация на този метод:

74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
   def activate
     camera = Sketchup.active_model.active_view.camera
     newTarget = Geom::Point3d.linear_combination 1.0, camera.eye, 1.0, camera.zaxis.to_a
     camera.set camera.eye, newTarget, [0,0,1]
 
     @@mouseDouble = false  
     @@boom = false
     @@eraser = false
     @@mouseCorrected = false
     @@vk_up = false
	 @@vk_down = false
	 @@vk_left = false
	 @@vk_right = false
     @firstPress = true
	 @@animate = true
	 @c = Sketchup.active_model.active_view.center
     @corX = 0
     @corY = 0	 	 
	 @@v = 6/0.0254
	 @msg = "Use arrow keys and mouse to navigate! Velocity = " + (@@v*0.0254).to_s + " m/s. Change it by typing a desired velocity and pressing Enter."
	 Sketchup::set_status_text(@msg)
 
	 setCursorPos = Win32API.new("user32", "SetCursorPos", ['I', 'I'], 'V')
	 setCursorPos.Call(@c[0]+@corX,@c[1]+@corY)	 
   end

Тук е и момента да помоля за извинения читателя на тази статия за странното форматиране на кода. Истината е, че в Notepad++ индентирането си изглежда нормално, но пействайки го тук става някакво разминаване, което трябва да оправям ръчно. В момента, в който ми се отвори време, ще се опитам да го оправя, но в случай, че на някой му е неприятно да го гледа от тук, би могъл да отвори файла DPY_CAMERA_24_09_2012.rb, който е в горния архив.

В  методa activate си инициализирам началните стойности на глобалните и неглобалните променливи на класа. В самия му край (ред 96 и 97) може да видите и задаването на началното преместване на курсора в центъра на екрана. Тук е и първия проблем със Sketch Up Ruby API, с който трябваше да се сблъсквам – няма метод който да ми позволява преместването на мишката на определена позиция. След извесно търсене в нета, намерих решението на този проблем с външен метод от Win32API, който първо го дефинирам и после го извиквам. Това е един от най-малките проблеми, с които трябваше да се сблъскам, пишейки този плъгин. 🙂

Освен метода activate tool-a може да съдържа още един куп методи, които кръстени по определен начин, описан в Sketch Up Ruby API, биват разпознавани в последствие от Sketch Up и биват използвани за конкретни цели. Такива са методите OnMouseMove, OnKeyDown и други. Няма да ги описвам подробно. Който иска може да си прочете за тях в документацията, в която има и доста подходящи примери.
Накратко ще спомена и другите проблеми, с които се сблъсках. Метода, който използва Sketch Up за отчитане на координатите на текущата позиция на мишката се различава резултатите за координатите от външните методи, които ползвам от Win32API. Това наложи да използвам метод от Win32API и за взимането на координатите, за да няма разминавания. Това се случва в метода onMouseMove:

178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
   def onMouseMove(flags, x, y, view)
 
     getCursorPos = Win32API.new("user32", "GetCursorPos", ['P'], 'V')
	 lpPoint = " " * 8 # store two LONGs
	 getCursorPos.Call(lpPoint)	 
	 p, q = lpPoint.unpack("LL") # get the actual values
     ray = view.pickray p-@corX, q-@corY
 
	 status = ray[1].samedirection? [0,0,1]
	 if(!status)       
	   view.camera.set ray[0], ray[1], [0,0,1]
	 end
 
	 setCursorPos = Win32API.new("user32", "SetCursorPos", ['I', 'I'], 'V')
	 setCursorPos.Call(@c[0]+@corX,@c[1]+@corY)
 
   end

Дотук добре. Истинският проблем обаче се появява, в момента, в който решавам, че искам да мога да включвам един мерник и с цъкане на мишката да “стрелям” и да изтривам обектите пред мерника. В Sketch Up Ruby API има готови класове и методи, с които мога да разбера кой е обекта под мишката ми и да го селектирам и съответно променя или направо изтрия. Това е чудесно, но с наличните разминавания на координатите на позициита на курсора, би било неприятно мерника ми да е нарисуван и да сочи към един обект, а аз като “стрелям” да изтрия някой съседен на него такъв. След като търсих начин да успея да унифицирам координатите от Sketch Up Ruby API и Win32API, за съжаление най-умното, което успях измисля е да поискам от потребителя на моя tool да направи просто един клик с мишката и отчитайки разликата от координатите на двете API-та, аз оттук нататък да използвам две променливи за корекция по x и корекция по y, като по този начин съм сигурен че мерника ми ще е напълно точен :). Ако някой успее да измисли по-добро решение на този проблем, ще се радвам да го сподели.

138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
   def onLButtonDown(flags, x, y, view)
       if(!@@mouseCorrected)
	     @@mouseCorrected = true
		 @corX = @c[0] - x
		 @corY = @c[1] - y
	   end   
       if(@@eraser and @@mouseCorrected)
	     @@boom = true
		 ph = view.pick_helper
		 ph.do_pick x,y
		 best = ph.best_picked
		 if(!best.deleted?) then best.erase! end
	   end
 
=begin	  ### Dictionaries and Attributes ###
	   if(!@@eraser and @@mouseCorrected)
		 ph = view.pick_helper
		 ph.do_pick x,y
		 best = ph.best_picked
		 inputpoint = view.inputpoint x, y
		 if(!best.deleted?)
		    best.set_attribute best.typename, "test", inputpoint.position
			best.attribute_dictionaries.each { |dict|
			    print=""
				dict.each { |key,value|
					print += dict.name.to_s + " --> " + key.to_s + " = " + value.to_s + "\n"
				}
				UI.messagebox(print)
			}			
		 end
	   end
=end	   
 
   end #end of LButtonDown

Големият закоментиран код в метода onLButtonDown (от ред 152 до ред 169), не е оставен там случайно. Тук съм си правил ескперименти с различни други функционалности, които биха могли да се добавят при кликане с мишката, освен сегашната функционалност за стреляне. Ако някои има идеи за реализация на рисуване с мишката или каквото и да било друго, може би тук е мястото, където трябва да добави малко код и да добави необходимата според него допълнителна функционалност на tool-а.

Последният проблем, с който се сблъсках, беше свързан с един бъг в Sketch Up Ruby API, свързан със събитието задържан клавиш. В документацията е записано, че при onKeyDown аз мога да имам информация за това дали клавиша е натиснат еднократно или е задържан. Това е вярно, но за съжаление работи само под Mac. Под Windows има някакъв бъг и тази опция просто не работи. Затова, вече решил проблема с ротацията на камерата, аз се сблъсках неочаквано и с проблем с нейното транслиране, тъй като искам при задържан бутон (стрелка) камерата да се транслира плавно с определена скорост в дадена посока. За да реализирам това успешно използвам комбинация от методите onKeyDown и onKeyUp, които ми дават информация кога някоя стрелка е натисната и кога е пусната. Тази информация съответно я пазя в една булева променлива за всяка от стрелките като тези променливи ги правя глобални за класа на моя tool. Всичко, което остава да направя, е да направя анимация на транслирането на камерата в зависимост от състоянието на тези булеви променливи. Анимация в Sketch Up се прави като се дефинира един допълнителен клас за анимацията.  Този клас трябва да има един метод на име nextFrame, чието име мисля че достатъчно ясно говори за какво служи :). Ето и конкретната имплементация на класа за анимацията:

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Animation
   def initialize
     @@DPY = DPYmoveCAMERAtool.new
     @camera = Sketchup.active_model.active_view.camera
	 @time = Sketchup.active_model.active_view.average_refresh_time * 10
	 @v = @@DPY.velocity
	 @msg = ""
	 @newEye = Geom::Point3d.new
	 @newTarget = Geom::Point3d.new
   end
 
   def nextFrame(view)
 
     @camera = view.camera
	 @time = view.average_refresh_time * 10
	 @v = @@DPY.velocity
 
	 if (@@DPY.vk_up)
	     @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, @v*@time, @camera.zaxis.to_a
         @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, @v*@time, @camera.zaxis.to_a	 
         view.camera.set @newEye, @newTarget, [0,0,1]
	 end
 
	 if (@@DPY.vk_down)
	     @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, -@v*@time, @camera.zaxis.to_a
         @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, -@v*@time, @camera.zaxis.to_a	 
         view.camera.set @newEye, @newTarget, [0,0,1] 
	 end
 
	 if (@@DPY.vk_left)
	     @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, -@v*@time, @camera.xaxis.to_a
         @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, -@v*@time, @camera.xaxis.to_a
         view.camera.set @newEye, @newTarget, [0,0,1]  
	 end
 
	 if (@@DPY.vk_right)
         @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, @v*@time, @camera.xaxis.to_a
         @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, @v*@time, @camera.xaxis.to_a	
         view.camera.set @newEye, @newTarget, [0,0,1] 
	 end
	 @msg = "Velocity = " + (@v*0.0254).to_s + " m/s. Position in space --> " + view.camera.eye[0].to_s + " " + view.camera.eye[1].to_s + " " + view.camera.eye[2].to_s
	 if(!@@DPY.mouseCorrected) then @msg = @msg + ". Click for MouseCorrection!" end
	 if(@@DPY.mouseCorrected)
	     @msg = @msg + ". Esc for GunMode On/Off: " 
		 if(@@DPY.eraser) then @msg = @msg + "On" end
		 if(!@@DPY.eraser) then @msg = @msg + "Off" end
	 end	 
	 if(!@@DPY.animate) 
	     @msg = ""
	 end
	 Sketchup::set_status_text(@msg)
	 view.show_frame
	 return @@DPY.animate
   end
 
end

Самата анимация се стартира при създаване на инстанция на класа Animation, което аз правя при първото извикване на метода onKeyDown от класа на моя tool:

197
198
199
200
201
202
203
204
205
206
207
208
209
   def onKeyDown(key, repeat, flags, view)   	 
 
     if(@firstPress)
	     @firstPress = false
		 Sketchup.active_model.active_view.animation =  Animation.new
	 end
 
	 if (key == VK_UP) then @@vk_up = true end	     
	 if (key == VK_DOWN) then @@vk_down = true end 
	 if (key == VK_RIGHT) then @@vk_right = true end
	 if (key == VK_LEFT) then @@vk_left = true end	 
 
   end

Кореспонденцията между двата класа я правя посредством глобалните променливи на класа на моя tool (това са тези променливи, започващи с двете кльомби :)).
И така имайки вече цялата функционално – tool + animation, можем да заредим нашия tool в toolbar-a и при кликане да го стартираме:

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
######Creating new toolbar item
toolbar = UI::Toolbar.new "DPY_CAMERA"
 
######Adding new command to the toolbar item
DPY_CAMERA = UI::Command.new("DPY_CAMERA") { 
 
     Sketchup.active_model.start_operation 'DPY_CAMERA', true     
	 Sketchup.active_model.select_tool DPYmoveCAMERAtool.new
     Sketchup.active_model.commit_operation
 
 }
DPY_CAMERA.small_icon = File.join("DPY_CAMERA", "DPY_CAMERA_small.jpg")
DPY_CAMERA.large_icon = File.join("DPY_CAMERA", "DPY_CAMERA_large.jpg")
 
toolbar = toolbar.add_item DPY_CAMERA
toolbar.show

И това е всичко! Събирайки целия код в едно получаваме финалния резултат:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
require 'Win32API'
 
class Animation
   def initialize
     @@DPY = DPYmoveCAMERAtool.new
     @camera = Sketchup.active_model.active_view.camera
	 @time = Sketchup.active_model.active_view.average_refresh_time * 10
	 @v = @@DPY.velocity
	 @msg = ""
	 @newEye = Geom::Point3d.new
	 @newTarget = Geom::Point3d.new
   end
 
   def nextFrame(view)
 
     @camera = view.camera
	 @time = view.average_refresh_time * 10
	 @v = @@DPY.velocity
 
	 if (@@DPY.vk_up)
	     @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, @v*@time, @camera.zaxis.to_a
         @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, @v*@time, @camera.zaxis.to_a	 
         view.camera.set @newEye, @newTarget, [0,0,1]
	 end
 
	 if (@@DPY.vk_down)
	     @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, -@v*@time, @camera.zaxis.to_a
         @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, -@v*@time, @camera.zaxis.to_a	 
         view.camera.set @newEye, @newTarget, [0,0,1] 
	 end
 
	 if (@@DPY.vk_left)
	     @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, -@v*@time, @camera.xaxis.to_a
         @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, -@v*@time, @camera.xaxis.to_a
         view.camera.set @newEye, @newTarget, [0,0,1]  
	 end
 
	 if (@@DPY.vk_right)
         @newEye = Geom::Point3d.linear_combination 1.0, @camera.eye, @v*@time, @camera.xaxis.to_a
         @newTarget = Geom::Point3d.linear_combination 1.0, @camera.target, @v*@time, @camera.xaxis.to_a	
         view.camera.set @newEye, @newTarget, [0,0,1] 
	 end
	 @msg = "Velocity = " + (@v*0.0254).to_s + " m/s. Position in space --&gt; " + view.camera.eye[0].to_s + " " + view.camera.eye[1].to_s + " " + view.camera.eye[2].to_s
	 if(!@@DPY.mouseCorrected) then @msg = @msg + ". Click for MouseCorrection!" end
	 if(@@DPY.mouseCorrected)
	     @msg = @msg + ". Esc for GunMode On/Off: " 
		 if(@@DPY.eraser) then @msg = @msg + "On" end
		 if(!@@DPY.eraser) then @msg = @msg + "Off" end
	 end	 
	 if(!@@DPY.animate) 
	     @msg = ""
	 end
	 Sketchup::set_status_text(@msg)
	 view.show_frame
	 return @@DPY.animate
   end
 
end
 
class DPYmoveCAMERAtool
 
   def initialize 
	 cursorPath2 = Sketchup.find_support_file ("DpyCameraCursor2.png", "Plugins/DPY_CAMERA/")
	 @@cursor2 = UI.create_cursor(cursorPath2, 5, 5)	
	 #cursorPath1 = Sketchup.find_support_file ("DpyCameraCursor1.png", "Plugins/DPY_CAMERA/")
	 #@@cursor1 = UI.create_cursor(cursorPath1, 5, 5)
   end
 
    def onSetCursor	
     UI.set_cursor(@@cursor2)
    end
 
   def activate
     camera = Sketchup.active_model.active_view.camera
	 newTarget = Geom::Point3d.linear_combination 1.0, camera.eye, 1.0, camera.zaxis.to_a
     camera.set camera.eye, newTarget, [0,0,1]
 
     @@mouseDouble = false  
	 @@boom = false
	 @@eraser = false
	 @@mouseCorrected = false
     @@vk_up = false
	 @@vk_down = false
	 @@vk_left = false
	 @@vk_right = false
     @firstPress = true
	 @@animate = true
	 @c = Sketchup.active_model.active_view.center
     @corX = 0
     @corY = 0	 	 
	 @@v = 6/0.0254
	 @msg = "Use arrow keys and mouse to navigate! Velocity = " + (@@v*0.0254).to_s + " m/s. Change it by typing a desired velocity and pressing Enter."
	 Sketchup::set_status_text(@msg)
 
	 setCursorPos = Win32API.new("user32", "SetCursorPos", ['I', 'I'], 'V')
	 setCursorPos.Call(@c[0]+@corX,@c[1]+@corY)	 
   end
 
   def draw(view)
     if(@@eraser and @@mouseCorrected)
		 view.drawing_color = "red"
		 d = 5
		 l = 25
		 view.draw2d GL_LINES, [@c[0],@c[1] - d,0],[@c[0],@c[1] - l,0] , [@c[0],@c[1] + d,0],[@c[0],@c[1] + l,0] , [@c[0] - d,@c[1],0],[@c[0] - l,@c[1],0] , [@c[0] + d,@c[1],0],[@c[0] + l,@c[1],0]
		 if(@@boom) then status = view.draw_text [@c[0] + d,@c[1] + d,0], "BOOM!" end
		 if(@@mouseDouble = true)
			 @@boom = false
			 @@mouseDouble = false
		 end
	 end
   end
 
   def deactivate(view)
     @@animate = false
   end
 
   def onCancel(reason, view)
     @@eraser = !@@eraser
   end
 
   def onUserText(text, view)
     if(text.to_f) then @@v = text.to_f/0.0254 end
   end
 
   def onLButtonDoubleClick(flags, x, y, view)
       if(@@eraser and @@mouseCorrected)
	     @@mouseDouble = true
	     @@boom = true
		 ph = view.pick_helper
		 ph.do_pick x,y
		 best = ph.best_picked
		 if(!best.deleted?) then best.erase! end
	   end	   
   end
 
   def onLButtonDown(flags, x, y, view)
       if(!@@mouseCorrected)
	     @@mouseCorrected = true
		 @corX = @c[0] - x
		 @corY = @c[1] - y
	   end   
       if(@@eraser and @@mouseCorrected)
	     @@boom = true
		 ph = view.pick_helper
		 ph.do_pick x,y
		 best = ph.best_picked
		 if(!best.deleted?) then best.erase! end
	   end
 
=begin	  ### Dictionaries and Attributes ###
	   if(!@@eraser and @@mouseCorrected)
		 ph = view.pick_helper
		 ph.do_pick x,y
		 best = ph.best_picked
		 inputpoint = view.inputpoint x, y
		 if(!best.deleted?)
		    best.set_attribute best.typename, "test", inputpoint.position
			best.attribute_dictionaries.each { |dict|
			    print=""
				dict.each { |key,value|
					print += dict.name.to_s + " --&gt; " + key.to_s + " = " + value.to_s + "\n"
				}
				UI.messagebox(print)
			}			
		 end
	   end
=end	   
 
   end #end of LButtonDown
 
   def onLButtonUp(flags, x, y, view)
     @@boom = false
   end
 
   def onMouseMove(flags, x, y, view)
 
     getCursorPos = Win32API.new("user32", "GetCursorPos", ['P'], 'V')
	 lpPoint = " " * 8 # store two LONGs
	 getCursorPos.Call(lpPoint)	 
	 p, q = lpPoint.unpack("LL") # get the actual values
     ray = view.pickray p-@corX, q-@corY
 
	 status = ray[1].samedirection? [0,0,1]
	 if(!status)       
	   view.camera.set ray[0], ray[1], [0,0,1]
	 end
 
	 setCursorPos = Win32API.new("user32", "SetCursorPos", ['I', 'I'], 'V')
	 setCursorPos.Call(@c[0]+@corX,@c[1]+@corY)
 
   end
 
   def onKeyDown(key, repeat, flags, view)   	 
 
     if(@firstPress)
	     @firstPress = false
		 Sketchup.active_model.active_view.animation =  Animation.new
	 end
 
	 if (key == VK_UP) then @@vk_up = true end	     
	 if (key == VK_DOWN) then @@vk_down = true end 
	 if (key == VK_RIGHT) then @@vk_right = true end
	 if (key == VK_LEFT) then @@vk_left = true end	 
 
   end
 
   def onKeyUp(key, repeat, flags, view)
     if (key == VK_UP) then @@vk_up = false end 
	 if (key == VK_DOWN) then @@vk_down = false end 
	 if (key == VK_LEFT) then @@vk_left = false end 
	 if (key == VK_RIGHT) then @@vk_right = false end 
   end
 
   def animate
     @@animate
   end
 
   def vk_up
     @@vk_up
   end
 
   def vk_down
     @@vk_down
   end
 
   def vk_left
     @@vk_left
   end
 
   def vk_right
     @@vk_right
   end
 
   def velocity
     @@v
   end
 
   def mouseCorrected
     @@mouseCorrected
   end
 
   def eraser
     @@eraser
   end  
 
end # end class DPYmoveCAMERAtool definition
 
######Dobawqne na toolbar
toolbar = UI::Toolbar.new "DPY_CAMERA"
 
######Dobawqne na komanda tetrahedron kam toolbara
DPY_CAMERA = UI::Command.new("DPY_CAMERA") { 
 
     Sketchup.active_model.start_operation 'DPY_CAMERA', true     
	 Sketchup.active_model.select_tool DPYmoveCAMERAtool.new
     Sketchup.active_model.commit_operation
 
 }
DPY_CAMERA.small_icon = File.join("DPY_CAMERA", "DPY_CAMERA_small.jpg")
DPY_CAMERA.large_icon = File.join("DPY_CAMERA", "DPY_CAMERA_large.jpg")
 
toolbar = toolbar.add_item DPY_CAMERA
toolbar.show

Имплементация на повърхнина на Безие на GDL script за Архикад

Written By: Деян - Feb• 15•13

bezier-dpy

Вероятно всеки, който се е занимавал професионално с проектиране (най-вече в областта на строителството, дизайна и други), има своите предпочитания към софтуерния продукт, който ползва ежедневно и който му спестява един куп усилия, за по-лесно и удобно решаване на проектантски проблеми и задачи. За съжаление всеки продукт обикновено заедно със своите предимства, носи и своите ограничения и недостатъци, независимо дали говорим за Архикад, Ревид, 3DsMax, АutoCad и др. Може би единствено AutoCAD не беше подходящ за горния списък, тъй като личното ми мнение е, че той носи  със себе си единствено и само недостатъци, без нито едно предимство, пред който и да е друг софтуер за 3D моделиране и изготвяне на чертежи  :). В съвременния свят, предоставящ възможност за работа едновременно в 3D и 2D без късане на връзката между модела, чертежа и количествата, смятам за крайно неадекватно, че в практиката все още масово се работи с някакви двумерни драсканици, наречени чертежи, които не дават никаква пространствена представа за цялостното решение, включващо всички специалности (архитектура, ОВК, ВиК, конструкция и т.н.)  и по този начин се дава предпоставка за допускане на множество конфликтни места в проекта, които се установяват обикновено на късен етап, по време на строителството. Дотук с AutoCAD и производните нему проблеми. 🙂 Сега ще започна с истинската идея на тази статия, а именно една добавка под формата на GDL обект за Архикад.

Ето линк към GDL обекта: BezierSurfaceDPY-v14–11-01-2013.gsm

Принципно обекта е писан за ArchiCAD 14. Разбира се съвсем спокойно ще тръгне на по-новите версии, както между впрочем и на по-ниските (за по ниските ще трябва първо да се зареди като библиотечен елемент в Архикад 14 и след това документа да се запише на по-ниска версия).

Каква е идеята на обекта и каква беше причината да хвърля усилието и да го напиша? По принцип, започвайки да работя на Архикад преди години, в началото мислех, че това е една ужасна програма, поставяща огромни да ги наречем творчески ограничения върху проектантската дейност. В последствие обаче, посвиквайки с основните идеи на програмата, трябва да кажа, че наистина отчитам голямото усилие, което е хвърлено, за да може програмата наистина да бъде изключително в помощ на проектанта за бързо и качествено изготвяне на целия проект от концептуалното моделиране и измисляне, до създаване на детайлен 3D модел и съпътстващите го задължителни двумерни чертежи. Най-хубавото е, че веднъж хвърлено усилието за правилно изграждане на работния модел, в последствие всички промени, които се налагат (като каприз на инвеститора или дори собствен каприз на проектанта) не представляват особен проблем и времето за нанасянето на корекциите и отразяването им на всички чертежи става наистина малко поради неразривната връзка между модела и чертежа.

И все пак наред с предимствата, за съжаление до голяма степен проблема с творческото ограничение, свързано с ограниченията в тримерното моделиране, които налага програмата, все още не е добре решен (макар и в новите версии на Архикад да се правят такива опити, все още са доста назад от това, което бих желал да се случи). Използването на външни програми за моделиране като 3DsMax например изобщо не е добър вариант, тъй като от една страна се къса връзка с модела, от друга страна 3DsMax работи равнини елементи (лица, ръбове и върхове), което при големи модели отнема ужасно много памет, с която съвременните компютри просто не разполагат (в офиса, в който работя съм използвал наистина мощни съвременни машини и дори те срещат сериозни затруднения със справянето с такива модели). Дори и да можеха да се справят с големите модели, самият факт че обемите не са плътни (имат само лица, ръбове и върхове) усложнява работата върху чертежите, тъй като при разрез през такъв обект, вместо да се получи разрез през плътно тяло се получава разрез през куха кутия, което в последствие трябва да се оправя ръчно и е доста досадно.

Ето защо, решавайки всички тези проблеми, аз се обърнах към вградения в Архикад GDL script, който позволява сравнително лесно моделиране на параметрични геометрични (макар и не задължително) обекти. Хубавото е, че тези обекти дават възможност да бъдат плътни, което при големи обекти работи много по-добре откъм памет в сравнение с кухите многостени, а освен това се поддържат всички настройки за разрез и 2D графика, които се поддържат в Архикад.


В случая за целта аз съм използвал небезизвестната повърнина, на името на открилия я френския математик и инженер Пиер Безие. Повърхнината се дефинира посредством интерполирането между няколко наброй наредени точки в пространството. Нямам за цел в статията да обяснявам математическата дефиниция на самата повърхнина, макар и да я смятам за доста интересна. На читателя, който се интересува, бих препоръчал две статии от wikipedia: Bezier surface и Bezier curve, като особено втората статия считам за изключително полезна, тъй като кривите на Безие са в основата на цялата идея и разбирайки ги тях почти няма да има проблем с разбирането на повърхнините на Безие (повърнините се получават в следствие на интерполиране между няколко наредени пространствени криви на Безие). Във втората статия има много полезни анимирани картинки, които наистина много нагледно показват идеята за формообразуването в зависимост от степента на кривата (от броя на контролните точки).

Bezier-animation

 

След като заредите обекта като библиотечен елемент в Архикад (през Library manager), ще можете да го намерите при другите GDL обекти в Embedded Library и маркирайки елемента ще видите подобен на долния екран с настройките на съответния обект.

bezier-menu

Тук е мястото да кажа, че самият обект е разработван за лични цели(за да мога успешно да моделирам дипломния си проект, към който ще дам снимки по-долу), поради което не всички функционалности са напълно завършени (оказа се че съчетаването едновременно на работа в архитектурно ателие, ученето за Telerik Academy и правенето на дипломна работа в университета не е много лесно да се реализира :)). Ще се опитам спомена по-надолу накратко какво съм успял да постигна и за какво не ми е стигнало времето, но съм имал доброто желание 🙂 и може би в някакъв бъдещ момент в следваща версия на GDL обекта, ще направя необходимите добавки.

По менюто в частта Parameters – тук се съдържат абсолютно всички параметри за контролирането на обекта. Ще карам точка по точка:

  • Точки на Безие – Това са координатите на 16те точки контролиращи кривата. На практика тези параметри не се използват от потребителя (координатите автоматично си се генерират и прегенерират при местенето на hotspot-овете и при прилагането на различни трансформации към обекта (като хомотетия например). Важна обаче е опцията режим на кривата – при линейния режим се работи само с 4 контролни точки, което е в пъти по лесно за контролиране, отколкото кубичния режим с 16 точки. С линейния режим обаче може да се постига единствено формата Хиперболичен-параболоид (което е би-линейна повърхнина на Безие и съответно често е наричана и праволинейна повърхнина). Кубичният режим позволява много по-голям контрол над кривината и тангентите на повърхнината, поради което могат да се постигат много по-разнообразни и интересни форми. Моят принцип на работа е следния – в началото си пускам линейния режим докато си наместя годе долу контурните точки, както си ги представям, че трябва да станат. След това включвам кубичния режим и си играя с другите точки така че да измоделирам желаната финална форма.
  • Гъстота на мрежата – тук имате възможност да контролирате гъстотата на мрежата в двете направления на повърхнината, което е важно както за гладкостта на кривата, така и за растера (ако например моделирате остъклена фасада и търсите постигане на някакъв растер с определени ограничения в размерите на полетата). Мрежата се начленява на равни интервали за u и v (двата параметъра дефиниращи формообразуването на тази параметрична крива).
  • 3D детайли – Тук се намират основните параметри, които ще са предполагам доста често използвани. Това се параметрите за оразмеряване на сеченията на елементите, както и за включването/изключването на всеки вид от тях (линейни елементи – могат да бъдат както цилиндрични, така и тънки линии, така и да липсват; повърхнинни елементи – могат да имат дебелина, могат и да са плоски, могат и да липсват; контролни елементи – линии и точки използвани за визуализирането на контролните точки дефиниращи кривата).
  • Hotspots – Това е доста важна секция, даваща поне за мен най-добрата функционалност на обекта с възможността той да бъде моделиран посредством “горещи точки” :). За по удобна работа в 3D в зависимост от целите, може да се включва/изключва възможността за придвижване по всяка една от 3-те посоки x, y или z, както в 3D така и в план.
  • Материали – ами тук се настройват материалите на различните елементи(линейни, повърхнини и др.)
  • 2D графика – това са рапидографите и щрихите използвани при изготвянето на двумерните чертежи
  • Скрий – това е една функционалност, която добавих в последния момент за направата на дипломната ми, поради необходимостта да скрия диагоналните елементи в мрежата.  Тази функционалност не е довършена в частите си различни от скриване на диагонали.

По менюто в UI частта кръстена “Повърхнина на Безие – Деян Йосифов – 11.01.2013 година” – тази секция съдържа елементи от частта Parameters, които съм сметнал за най-важни и за които съм искал да има бърз и лесен достъп чрез UI частта. Единствената част, която е нова в сравнение с частта Parameters, е секцията “Трансформации”, където може да се извърши грубо оразмеряване на повърхнината с хомотетии по x, y или z. Това е полезно, когато например начално имате повърнина с размери 2 на 2 метра, а вие искате да направите нещо доста по-голямо с размерите да речем 20 на 20. Вместо да местите всяка от контролните точки, може просто да напишете необходимия коефициент за хомотетия в необходимата посока (за горния пример коефициента е 10 тъй като искаме 10 пъти да се увеличи размера). След като сте въвели коефициента, за да стане активно оразмеряването трябва да цъкнете на бутона до съответната форма за коефициента (например бутона “Хомотетия по X”).

Това е накратко за постигнатите към този момент опции и функционалности. Ето и няколко картинки от моята дипломна работа (на тема футболен стадион), за моделирането на която съм използвал показания горе GDL обект:

На края на статията ще се опитам с няколко думи да обясня идеята на имплементацията. Като начало каква е идеята на писането на GDL script и къде човек може да се научи как точно се прави това? Ами отговорът е доста лесен – в Help менюто на Архикад може да намерите pdf файл на име GDL Reference Guide, където е описана цялата идея в сравнително кратка документация с линкове между страниците. Ето и screenshot къде точно се намира документацията:

reference

Като цяло GDL е изключително прост и окастрен откъм опции скриптов език. Под окастрен имам предид, че няма възможност да пишете собствени методи и функции, няма while цикъл и други такива иначе толкова полезни неща, имащи ги в нормалните езици за програмиране :). От към цикли езикът разполага с For цикъл. Също така има If-Else функционалност, което е добре :). Има GoTo, което по принцип е ужасно, но в краен случай може и до него да опрете, предвид липсата на while цикъл. Има опции да вкарвате някакви данни във буфера с командата Put и в последствие с командата Get или командата Use да използвате определен брой стойности от буфера, който работи на принципа на опашката (пръв влязъл пръв излиза). Идеята на този буфер е ако генерирате с някакъв цикъл да речем някакви параметри (да кажем координати) след това да може да ги използвате в някоя от вградените в GDL функции. Разгледайте документацията. Любопитно е :).

Как е организиран конкретния GDL обект. За да го отворите трябва да отидете във File -> Libraries and Objects -> Open object и ако сте маркирали преди това обекта то така ще отворите неговия script.

open-gdl

В секцията Parameters се дефинират параметрите на обекта като всеки параметър си има име, тип(дължина, ъгъл, рапитограф, щрих, булев, текст и др.) и начална стойност. Имената на параметрите ще се използват в последствие в скрипта.

GDL-Parameters

Следващата важна секция е Master script. Там се съдържа както 2D, така и 3D, така и всякакъв друг скрипт, който е важно да се знае, че се изпълнява преди всеки друг скрипт. Тук аз си инициализирам началните масиви, необходими за запаметяване на координатите на точките от мрежата, както и си пресмятам всички точки от мрежата в зависимост от режима на кривата (линеен или кубичен). Ето го и скрипта:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
UNid = 1
 
!!!---Transform Variables Declaration---!!!
DIM ii[3]
DIM jj[3]
DIM kk[3]
length = 0
!!!---END Transform Variables Declaration---!!!
 
!!!---HyperbolicParapoloidMode---!!!
IF mode = "ëèíååí" THEN
PARAMETERS bx[1][2] = (2/3)*bx[1][1] + (1/3)*bx[1][4]
PARAMETERS bx[1][3] = (1/3)*bx[1][1] + (2/3)*bx[1][4]
PARAMETERS by[1][2] = (2/3)*by[1][1] + (1/3)*by[1][4]
PARAMETERS by[1][3] = (1/3)*by[1][1] + (2/3)*by[1][4]
PARAMETERS bz[1][2] = (2/3)*bz[1][1] + (1/3)*bz[1][4]
PARAMETERS bz[1][3] = (1/3)*bz[1][1] + (2/3)*bz[1][4]
 
PARAMETERS bx[4][2] = (2/3)*bx[4][1] + (1/3)*bx[4][4]
PARAMETERS bx[4][3] = (1/3)*bx[4][1] + (2/3)*bx[4][4]
PARAMETERS by[4][2] = (2/3)*by[4][1] + (1/3)*by[4][4]
PARAMETERS by[4][3] = (1/3)*by[4][1] + (2/3)*by[4][4]
PARAMETERS bz[4][2] = (2/3)*bz[4][1] + (1/3)*bz[4][4]
PARAMETERS bz[4][3] = (1/3)*bz[4][1] + (2/3)*bz[4][4]
 
PARAMETERS bx[2][1] = (2/3)*bx[1][1] + (1/3)*bx[4][1]
PARAMETERS bx[3][1] = (1/3)*bx[1][1] + (2/3)*bx[4][1]
PARAMETERS by[2][1] = (2/3)*by[1][1] + (1/3)*by[4][1]
PARAMETERS by[3][1] = (1/3)*by[1][1] + (2/3)*by[4][1]
PARAMETERS bz[2][1] = (2/3)*bz[1][1] + (1/3)*bz[4][1]
PARAMETERS bz[3][1] = (1/3)*bz[1][1] + (2/3)*bz[4][1]
 
PARAMETERS bx[2][4] = (2/3)*bx[1][4] + (1/3)*bx[4][4]
PARAMETERS bx[3][4] = (1/3)*bx[1][4] + (2/3)*bx[4][4]
PARAMETERS by[2][4] = (2/3)*by[1][4] + (1/3)*by[4][4]
PARAMETERS by[3][4] = (1/3)*by[1][4] + (2/3)*by[4][4]
PARAMETERS bz[2][4] = (2/3)*bz[1][4] + (1/3)*bz[4][4]
PARAMETERS bz[3][4] = (1/3)*bz[1][4] + (2/3)*bz[4][4]
 
PARAMETERS bx[2][2] = (2/3)*bx[1][2] + (1/3)*bx[4][2]
PARAMETERS bx[3][2] = (1/3)*bx[1][2] + (2/3)*bx[4][2]
PARAMETERS by[2][2] = (2/3)*by[1][2] + (1/3)*by[4][2]
PARAMETERS by[3][2] = (1/3)*by[1][2] + (2/3)*by[4][2]
PARAMETERS bz[2][2] = (2/3)*bz[1][2] + (1/3)*bz[4][2]
PARAMETERS bz[3][2] = (1/3)*bz[1][2] + (2/3)*bz[4][2]
 
PARAMETERS bx[2][3] = (2/3)*bx[1][3] + (1/3)*bx[4][3]
PARAMETERS bx[3][3] = (1/3)*bx[1][3] + (2/3)*bx[4][3]
PARAMETERS by[2][3] = (2/3)*by[1][3] + (1/3)*by[4][3]
PARAMETERS by[3][3] = (1/3)*by[1][3] + (2/3)*by[4][3]
PARAMETERS bz[2][3] = (2/3)*bz[1][3] + (1/3)*bz[4][3]
PARAMETERS bz[3][3] = (1/3)*bz[1][3] + (2/3)*bz[4][3]
ENDIF
!!!---END HyperbolicParapoloidMode---!!!
 
!!!---Points Preparations---!!!
IF MAX(u, v)>-1 AND MAX(u, v)9 AND MAX(u, v)19 AND MAX(u, v)29 AND MAX(u, v)39 AND MAX(u, v)49 AND MAX(u, v)59 AND MAX(u, v)69 AND MAX(u, v)79 AND MAX(u, v)89 AND MAX(u, v)

Тук е мястото да спомена, че коментарите в GDL започват със знака “!”. Такива съм използвал доста, дори само за лично удобство, за да се ориентирам по-лесно в кода (както казах тук няма методи и други такива улеснения, така че писането е доста).

Следва 2D скрипта. Там се дефинира двумерната графика в плана, която в случая съм задал да бъде просто Монжова проекция на обекта отгоре. Тук също така се дефинират и хотспотовете, които са за плана.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pen linii
fill fill_type
project2{2} 3, 270, 32+3, back_pen, 0, 0, 90
 
!!!---Hotspots2D---!!!
IF Hotspots2D THEN
	FOR i=1 TO 4
		FOR j=1 TO 4
			IF mode = "êóáè÷åí" OR (mode = "ëèíååí" AND (i=1 OR i=4) AND (j=1 OR j=4)) THEN
				hotspot2 bx[i][j], by[i][j], UNid, bx[i][j], 2 : UNid=UNid+1
				hotspot2 0, by[i][j], UNid, bx[i][j], 1 + 128 : UNid=UNid+1
				hotspot2 -1, by[i][j], UNid, bx[i][j], 3 : UNid=UNid+1
 
				hotspot2 bx[i][j], by[i][j], UNid, by[i][j], 2 : UNid=UNid+1
				hotspot2 bx[i][j], 0, UNid, by[i][j], 1 + 128 : UNid=UNid+1
				hotspot2 bx[i][j], -1, UNid, by[i][j], 3 : UNid=UNid+1
			ENDIF
		NEXT j
	NEXT i
ENDIF
!!!---END Hotspots2D---!!!

Следва 3D скрипта, който разбира се е основната част от имплементацията. Там в зависимост от стойностите на параметрите генерирам необходимите 3-мерни обекти, както и hotspots, отново ако са включени от потребителя като параметри. В тази част от кода има множество линейни трансформации, повечето от който ги правя чрез матрица на трансформациите, тъй като повечето обекти в GDL скрипт се генерират от готови функции, изискващи смяната на координатната система.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
!PRINT SYMB_POS_X, SYMB_POS_Y, SYMB_POS_Z
 
!RULED{2} 3, 1+2+4 +16+32,
!
!0, 0, 1,
!1, 0, 1,
!0, 1, 1,
!
!0, 0, 5,
!1, 0, 5,
!1, 1, 5
 
!!!---Create LINEAR Geometry---!!!
 
!!!---Linii---!!!
IF linearElements = "ëèíèè" AND surfaceElements <> "coon" THEN
	PEN colorLines
	FOR i=1 TO u
		FOR j=1 TO v
			IF NOT(hideDiagonals) THEN
				LIN_ px[i][j], py[i][j], pz[i][j], px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1]
			ENDIF
			LIN_ px[i][j], py[i][j], pz[i][j], px[i+1][j], py[i+1][j], pz[i+1][j]
			LIN_ px[i][j], py[i][j], pz[i][j], px[i][j+1], py[i][j+1], pz[i][j+1]
			IF i = u THEN
				LIN_ px[i+1][j], py[i+1][j], pz[i+1][j], px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1]
			ENDIF
			IF j = v THEN
				LIN_ px[i][j+1], py[i][j+1], pz[i][j+1], px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1]
			ENDIF
		NEXT j
	NEXT i
ENDIF
 
IF linearElements = "ëèíèè" AND surfaceElements = "coon" THEN
PEN colorLines
	FOR i=1 TO u
		LIN_ px[i][1], py[i][1], pz[i][1], px[i+1][1], py[i+1][1], pz[i+1][1]
		LIN_ px[i][v+1], py[i][v+1], pz[i][v+1], px[i+1][v+1], py[i+1][v+1], pz[i+1][v+1]
	NEXT i
	FOR i=1 TO v
		LIN_ px[1][i], py[1][i], pz[1][i], px[1][i+1], py[1][i+1], pz[1][i+1]
		LIN_ px[u+1][i], py[u+1][i], pz[u+1][i], px[u+1][i+1], py[u+1][i+1], pz[u+1][i+1]
	NEXT i
ENDIF
!!!---END Linii---!!!
 
!!!---Mnogosteni---!!!
MATERIAL matLinears
PEN linii
 
IF linearElements = "ìíîãîñòåíè" AND rr > 0 AND surfaceElements = "coon" THEN
	RESOL sides
 
	FOR i=1 TO u
			kk[1] = px[i+1][1] - px[i][1]
			kk[2] = py[i+1][1] - py[i][1]
			kk[3] = pz[i+1][1] - pz[i][1]
			IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
				IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 					ii[1] = 1 					ii[2] = 0 					ii[3] = 0 				ELSE 					ii[1] = -kk[2] 					ii[2] = kk[1] 					ii[3] = 0 					length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 					ii[1] = ii[1]/length 					ii[2] = ii[2]/length 				ENDIF 				length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 				kk[1] = kk[1]/length 				kk[2] = kk[2]/length 				kk[3] = kk[3]/length 				jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 				jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 				jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 				XFORM 	ii[1], jj[1], kk[1], px[i][1], 						ii[2], jj[2], kk[2], py[i][1], 						ii[3], jj[3], kk[3], pz[i][1] 				CYLIND length, rr 				DEL 1 			ENDIF 			kk[1] = px[i+1][v+1] - px[i][v+1] 			kk[2] = py[i+1][v+1] - py[i][v+1] 			kk[3] = pz[i+1][v+1] - pz[i][v+1] 			IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
				IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 					ii[1] = 1 					ii[2] = 0 					ii[3] = 0 				ELSE 					ii[1] = -kk[2] 					ii[2] = kk[1] 					ii[3] = 0 					length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 					ii[1] = ii[1]/length 					ii[2] = ii[2]/length 				ENDIF 				length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 				kk[1] = kk[1]/length 				kk[2] = kk[2]/length 				kk[3] = kk[3]/length 				jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 				jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 				jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 				XFORM 	ii[1], jj[1], kk[1], px[i][v+1], 						ii[2], jj[2], kk[2], py[i][v+1], 						ii[3], jj[3], kk[3], pz[i][v+1] 				CYLIND length, rr 				DEL 1 			ENDIF 	NEXT i 	FOR i=1 TO v 			kk[1] = px[1][i] - px[1][i+1] 			kk[2] = py[1][i] - py[1][i+1] 			kk[3] = pz[1][i] - pz[1][i+1] 			IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
				IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 					ii[1] = 1 					ii[2] = 0 					ii[3] = 0 				ELSE 					ii[1] = -kk[2] 					ii[2] = kk[1] 					ii[3] = 0 					length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 					ii[1] = ii[1]/length 					ii[2] = ii[2]/length 				ENDIF 				length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 				kk[1] = kk[1]/length 				kk[2] = kk[2]/length 				kk[3] = kk[3]/length 				jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 				jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 				jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 				XFORM 	ii[1], jj[1], kk[1], px[1][i+1], 						ii[2], jj[2], kk[2], py[1][i+1], 						ii[3], jj[3], kk[3], pz[1][i+1] 				CYLIND length, rr 				DEL 1 			ENDIF 			kk[1] = px[u+1][i] - px[u+1][i+1] 			kk[2] = py[u+1][i] - py[u+1][i+1] 			kk[3] = pz[u+1][i] - pz[u+1][i+1] 			IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
				IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 					ii[1] = 1 					ii[2] = 0 					ii[3] = 0 				ELSE 					ii[1] = -kk[2] 					ii[2] = kk[1] 					ii[3] = 0 					length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 					ii[1] = ii[1]/length 					ii[2] = ii[2]/length 				ENDIF 				length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 				kk[1] = kk[1]/length 				kk[2] = kk[2]/length 				kk[3] = kk[3]/length 				jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 				jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 				jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 				XFORM 	ii[1], jj[1], kk[1], px[u+1][i+1], 						ii[2], jj[2], kk[2], py[u+1][i+1], 						ii[3], jj[3], kk[3], pz[u+1][i+1] 				CYLIND length, rr 				DEL 1 			ENDIF 	NEXT i ENDIF !!!--- Not coon ---!!! IF linearElements = "ìíîãîñòåíè" AND rr > 0 AND surfaceElements <> "coon" THEN
	RESOL sides
	FOR i=1 TO u
		FOR j=1 TO v
 
			IF NOT(hideDiagonals) THEN
				kk[1] = px[i+1][j+1] - px[i][j]
				kk[2] = py[i+1][j+1] - py[i][j]
				kk[3] = pz[i+1][j+1] - pz[i][j]
				IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
					IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 						ii[1] = 1 						ii[2] = 0 						ii[3] = 0 					ELSE 						ii[1] = -kk[2] 						ii[2] = kk[1] 						ii[3] = 0 						length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 						ii[1] = ii[1]/length 						ii[2] = ii[2]/length 					ENDIF 					length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 					kk[1] = kk[1]/length 					kk[2] = kk[2]/length 					kk[3] = kk[3]/length 					jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 					jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 					jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 					XFORM 	ii[1], jj[1], kk[1], px[i][j], 							ii[2], jj[2], kk[2], py[i][j], 							ii[3], jj[3], kk[3], pz[i][j] 					CYLIND length, rr 					DEL 1 				ENDIF 			ENDIF 			kk[1] = px[i+1][j] - px[i][j] 			kk[2] = py[i+1][j] - py[i][j] 			kk[3] = pz[i+1][j] - pz[i][j] 			IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
				IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 					ii[1] = 1 					ii[2] = 0 					ii[3] = 0 				ELSE 					ii[1] = -kk[2] 					ii[2] = kk[1] 					ii[3] = 0 					length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 					ii[1] = ii[1]/length 					ii[2] = ii[2]/length 				ENDIF 				length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 				kk[1] = kk[1]/length 				kk[2] = kk[2]/length 				kk[3] = kk[3]/length 				jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 				jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 				jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 				XFORM 	ii[1], jj[1], kk[1], px[i][j], 						ii[2], jj[2], kk[2], py[i][j], 						ii[3], jj[3], kk[3], pz[i][j] 				CYLIND length, rr 				DEL 1 			ENDIF 			kk[1] = px[i][j+1] - px[i][j] 			kk[2] = py[i][j+1] - py[i][j] 			kk[3] = pz[i][j+1] - pz[i][j] 			IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
				IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 					ii[1] = 1 					ii[2] = 0 					ii[3] = 0 				ELSE 					ii[1] = -kk[2] 					ii[2] = kk[1] 					ii[3] = 0 					length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 					ii[1] = ii[1]/length 					ii[2] = ii[2]/length 				ENDIF 				length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 				kk[1] = kk[1]/length 				kk[2] = kk[2]/length 				kk[3] = kk[3]/length 				jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 				jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 				jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 				XFORM 	ii[1], jj[1], kk[1], px[i][j], 						ii[2], jj[2], kk[2], py[i][j], 						ii[3], jj[3], kk[3], pz[i][j] 				CYLIND length, rr 				DEL 1 			ENDIF 			IF i = u THEN 				kk[1] = px[i+1][j+1] - px[i+1][j] 				kk[2] = py[i+1][j+1] - py[i+1][j] 				kk[3] = pz[i+1][j+1] - pz[i+1][j] 				IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
					IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 						ii[1] = 1 						ii[2] = 0 						ii[3] = 0 					ELSE 						ii[1] = -kk[2] 						ii[2] = kk[1] 						ii[3] = 0 						length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 						ii[1] = ii[1]/length 						ii[2] = ii[2]/length 					ENDIF 					length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 					kk[1] = kk[1]/length 					kk[2] = kk[2]/length 					kk[3] = kk[3]/length 					jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 					jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 					jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 					XFORM 	ii[1], jj[1], kk[1], px[i+1][j], 							ii[2], jj[2], kk[2], py[i+1][j], 							ii[3], jj[3], kk[3], pz[i+1][j] 					CYLIND length, rr 					DEL 1 				ENDIF 			ENDIF 			IF j = v THEN 				kk[1] = px[i+1][j+1] - px[i][j+1] 				kk[2] = py[i+1][j+1] - py[i][j+1] 				kk[3] = pz[i+1][j+1] - pz[i][j+1] 				IF kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] > 0 THEN
					IF ABS(kk[1]) < 0.0001 AND ABS(kk[2]) < 0.0001 THEN 						ii[1] = 1 						ii[2] = 0 						ii[3] = 0 					ELSE 						ii[1] = -kk[2] 						ii[2] = kk[1] 						ii[3] = 0 						length = SQR(ii[1]*ii[1] + ii[2]*ii[2]) 						ii[1] = ii[1]/length 						ii[2] = ii[2]/length 					ENDIF 					length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]) 					kk[1] = kk[1]/length 					kk[2] = kk[2]/length 					kk[3] = kk[3]/length 					jj[1] = ii[2]*kk[3] - ii[3]*kk[2] 					jj[2] = ii[3]*kk[1] - ii[1]*kk[3] 					jj[3] = ii[1]*kk[2] - ii[2]*kk[1]			 					XFORM 	ii[1], jj[1], kk[1], px[i][j+1], 							ii[2], jj[2], kk[2], py[i][j+1], 							ii[3], jj[3], kk[3], pz[i][j+1] 					CYLIND length, rr 					DEL 1 				ENDIF 			ENDIF 		NEXT j 	NEXT i ENDIF !!!---END Mnogosteni---!!! !!!---END Create LINEAR Geometry---!!! !!!---SURFACE Geometry---!!! MATERIAL matSurface !PEN linii PEN liniiSurface !!!---Create FACE Geometry---!!! IF surfaceElements = "ëèöà" THEN 	FOR i=1 TO u 		FOR j=1 TO v 			ii[1] = px[i+1][j+1] - px[i][j] 			ii[2] = py[i+1][j+1] - py[i][j] 			ii[3] = pz[i+1][j+1] - pz[i][j] 			jj[1] = px[i+1][j] - px[i][j] 			jj[2] = py[i+1][j] - py[i][j] 			jj[3] = pz[i+1][j] - pz[i][j] 			kk[1] = ii[2]*jj[3] - ii[3]*jj[2] 			kk[2] = ii[3]*jj[1] - ii[1]*jj[3] 			kk[3] = ii[1]*jj[2] - ii[2]*jj[1] 			length = kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3] 			IF (length > 0.0001) THEN
				PLANE_ 3,
				px[i][j], py[i][j], pz[i][j], 1,
				px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1], 1,
				px[i+1][j], py[i+1][j], pz[i+1][j], 1
			ENDIF
 
			ii[1] = px[i+1][j+1] - px[i][j]
			ii[2] = py[i+1][j+1] - py[i][j]
			ii[3] = pz[i+1][j+1] - pz[i][j]
			jj[1] = px[i][j+1] - px[i][j]
			jj[2] = py[i][j+1] - py[i][j]
			jj[3] = pz[i][j+1] - pz[i][j]
			kk[1] = ii[2]*jj[3] - ii[3]*jj[2]
			kk[2] = ii[3]*jj[1] - ii[1]*jj[3]
			kk[3] = ii[1]*jj[2] - ii[2]*jj[1]
			length = kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3]
			IF (length > 0.0001) THEN
				PLANE_ 3,
				px[i][j], py[i][j], pz[i][j], 1,
				px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1], 1,
				px[i][j+1], py[i][j+1], pz[i][j+1], 1
			ENDIF
 
		NEXT j
	NEXT i
ENDIF
!!!---END Create FACE Geometry---!!!
 
!!!---Create COONS Geometry---!!!
IF surfaceElements = "coons" THEN
	FOR i=1 TO u
		FOR j=1 TO v
			COONS 2, 2, 0,
				px[i][j], py[i][j], pz[i][j] ,px[i][j+1], py[i][j+1], pz[i][j+1],
				px[i+1][j], py[i+1][j], pz[i+1][j] ,px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1],
				px[i][j], py[i][j], pz[i][j] ,px[i+1][j], py[i+1][j], pz[i+1][j],
				px[i][j+1], py[i][j+1], pz[i][j+1] ,px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1]
		NEXT j
	NEXT i
ENDIF
!!!---END Create COONS Geometry---!!!
 
!!!---Create COON Geometry---!!!
IF surfaceElements = "coon" THEN
	FOR i=1 to u+1
		PUT px[i][1]
		PUT py[i][1]
		PUT pz[i][1]
	NEXT i
	FOR i=1 to u+1
		PUT px[i][v+1]
		PUT py[i][v+1]
		PUT pz[i][v+1]
	NEXT i
 
	FOR i=1 to v+1
		PUT px[1][i]
		PUT py[1][i]
		PUT pz[1][i]
	NEXT i
	FOR i=1 to v+1
		PUT px[u+1][i]
		PUT py[u+1][i]
		PUT pz[u+1][i]
	NEXT i
 
	COONS u+1, v+1, 0, GET(6*(u+v+2))
 
ENDIF
!!!---END Create COON Geometry---!!!
 
!!!---Create PRISM Geometry---!!!
IF surfaceElements = "ïðèçìè" and thickness > 0 THEN
	FOR i=1 TO u
		FOR j=1 TO v
 
			ii[1] = px[i+1][j+1] - px[i][j]
			ii[2] = py[i+1][j+1] - py[i][j]
			ii[3] = pz[i+1][j+1] - pz[i][j]
			jj[1] = px[i+1][j] - px[i][j]
			jj[2] = py[i+1][j] - py[i][j]
			jj[3] = pz[i+1][j] - pz[i][j]
			kk[1] = ii[2]*jj[3] - ii[3]*jj[2]
			kk[2] = ii[3]*jj[1] - ii[1]*jj[3]
			kk[3] = ii[1]*jj[2] - ii[2]*jj[1]
			length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3])
			IF (length > 0.0001) THEN
				XFORM 	ii[1], jj[1], kk[1]/length, px[i][j] - (kk[1]/length)*(thickness/2),
						ii[2], jj[2], kk[2]/length, py[i][j] - (kk[2]/length)*(thickness/2),
						ii[3], jj[3], kk[3]/length, pz[i][j] - (kk[3]/length)*(thickness/2)
 
				PRISM 3, thickness,
				0.0, 0.0, 
				1.0, 0.0, 
				0.0, 1.0
 
				DEL 1
			ENDIF
 
			ii[1] = px[i][j+1] - px[i][j]
			ii[2] = py[i][j+1] - py[i][j]
			ii[3] = pz[i][j+1] - pz[i][j]
			jj[1] = px[i+1][j+1] - px[i][j]
			jj[2] = py[i+1][j+1] - py[i][j]
			jj[3] = pz[i+1][j+1] - pz[i][j]
			kk[1] = ii[2]*jj[3] - ii[3]*jj[2]
			kk[2] = ii[3]*jj[1] - ii[1]*jj[3]
			kk[3] = ii[1]*jj[2] - ii[2]*jj[1]
			length = SQR(kk[1]*kk[1] + kk[2]*kk[2] + kk[3]*kk[3])
			IF (length > 0.0001) THEN
				XFORM 	ii[1], jj[1], kk[1]/length, px[i][j] - (kk[1]/length)*(thickness/2),
						ii[2], jj[2], kk[2]/length, py[i][j] - (kk[2]/length)*(thickness/2),
						ii[3], jj[3], kk[3]/length, pz[i][j] - (kk[3]/length)*(thickness/2)
 
				PRISM 3, thickness,
				0.0, 0.0, 
				1.0, 0.0, 
				0.0, 1.0
 
				DEL 1
			ENDIF
 
		NEXT j
	NEXT i
ENDIF
!!!---END Create PRISM Geometry---!!!
 
!!!---END SURFACE Geometry---!!!
 
!!!---Hotspots And Controls Geometry---!!!
IF HotspotsX OR HotspotsY OR HotspotsZ THEN
FOR i=1 TO 4
	FOR j=1 TO 4
		IF mode = "êóáè÷åí" OR (mode = "ëèíååí" AND (i=1 OR i=4) AND (j=1 OR j=4)) THEN
			IF HotspotsX THEN
				hotspot bx[i][j], by[i][j], bz[i][j], UNid, bx[i][j], 2 : UNid=UNid+1
				hotspot 0, by[i][j], bz[i][j], UNid, bx[i][j], 1 + 128 : UNid=UNid+1
				hotspot -1, by[i][j], bz[i][j], UNid, bx[i][j], 3 : UNid=UNid+1
			ENDIF
 
			IF HotspotsY THEN
				hotspot bx[i][j], by[i][j], bz[i][j], UNid, by[i][j], 2 : UNid=UNid+1
				hotspot bx[i][j], 0, bz[i][j], UNid, by[i][j], 1 + 128 : UNid=UNid+1
				hotspot bx[i][j], -1, bz[i][j], UNid, by[i][j], 3 : UNid=UNid+1
			ENDIF
 
			IF HotspotsZ THEN
				hotspot bx[i][j], by[i][j], bz[i][j], UNid, bz[i][j], 2 : UNid=UNid+1
				hotspot bx[i][j], by[i][j], 0, UNid, bz[i][j], 1 + 128 : UNid=UNid+1
				hotspot bx[i][j], by[i][j], -1, UNid, bz[i][j], 3 : UNid=UNid+1
			ENDIF
 
			IF VertexCubesSide > 0 THEN
				MATERIAL matPoints
				ADD bx[i][j] - VertexCubesSide/2, by[i][j] - VertexCubesSide/2, bz[i][j] - VertexCubesSide/2
				BLOCK VertexCubesSide, VertexCubesSide, VertexCubesSide
				DEL 1
			ENDIF
		ENDIF
		IF lines AND mode = "êóáè÷åí" THEN
			PEN penLinii
 
			tx = 1
			ty = 1
			LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1]
			LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty]
			LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty]
			LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1]
 
			tx = 1
			ty = 3
			LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1]
			LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty]
			LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty]
			LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1]
 
			tx = 3
			ty = 1
			LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1]
			LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty]
			LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty]
			LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1]
 
			tx = 3
			ty = 3
			LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1]
			LIN_ bx[tx][ty], by[tx][ty], bz[tx][ty], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty]
			LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx + 1][ty], by[tx + 1][ty], bz[tx + 1][ty]
			LIN_ bx[tx + 1][ty + 1], by[tx + 1][ty + 1], bz[tx + 1][ty + 1], bx[tx][ty + 1], by[tx][ty + 1], bz[tx][ty + 1]
		ENDIF
 
	NEXT j
NEXT i
ENDIF
!!!---END Hotspots---!!!

Следващият скрипт е Parameter script, където се задават възможните стойности на някои параметрите с изборна стойност, както и там се дефинира кореспонденцията между UI бутоните за хомотетия и пресмятането на новите стойности на координатите след хомотетията.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
VALUES "mode", "êóáè÷åí", "ëèíååí"
VALUES "surfaceElements", "ïðèçìè", "ëèöà", "áåç", "coons", "coon"
VALUES "linearElements", "áåç", "ìíîãîñòåíè", "ëèíèè"
 
IF linearElements <> "ìíîãîñòåíè" THEN
	LOCK "rr", "sides"
ENDIF
 
IF linearElements <> "ëèíèè" THEN
	LOCK "colorLines"
ENDIF
 
IF surfaceElements = "áåç" OR surfaceElements = "ëèöà" THEN
	LOCK "thickness"
ENDIF
 
IF lines = 0 THEN
	LOCK "penLinii"
ENDIF
 
!!!---Transformation Group---!!!
IF GLOB_UI_BUTTON_ID = 1 THEN
	temp = 0
	FOR i=1 TO 4
		FOR j=1 TO 4
			temp = temp + bx[i][j]
		NEXT j
	NEXT i
	temp = temp/16
	FOR i=1 TO 4
		FOR j=1 TO 4
			PARAMETERS bx[i][j] = homX * bx[i][j] + (1 - homX)*temp
		NEXT j
	NEXT i
	PARAMETERS homX = 1
ENDIF
 
IF GLOB_UI_BUTTON_ID = 2 THEN
	temp = 0
	FOR i=1 TO 4
		FOR j=1 TO 4
			temp = temp + by[i][j]
		NEXT j
	NEXT i
	temp = temp/16
	FOR i=1 TO 4
		FOR j=1 TO 4
			PARAMETERS by[i][j] = homY * by[i][j] + (1- homY)*temp
		NEXT j
	NEXT i
	PARAMETERS homY = 1
ENDIF
 
IF GLOB_UI_BUTTON_ID = 3 THEN
	temp = 0
	FOR i=1 TO 4
		FOR j=1 TO 4
			temp = temp + bz[i][j]
		NEXT j
	NEXT i
	temp = temp/16
	FOR i=1 TO 4
		FOR j=1 TO 4
			PARAMETERS bz[i][j] = homZ * bz[i][j] + (1- homZ)*temp
		NEXT j
	NEXT i
	PARAMETERS homZ = 1
ENDIF
!!!---END Transformation Group---!!!

И последният скрипт е Interface script, където се дефинира прозореца от UI частта с бутоните и fieldset-ове.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
UI_DIALOG "Ïîâúðõíèíà íà Áåçèå - Äåÿí Éîñèôîâ - 11.01.2013 ãîäèíà"
 
!!!---Size is 444 x 266---!!!
 
!!!---Transformation Group---!!!
x = 10
y = 10
UI_GROUPBOX "Òðàíñôîðìàöèè", x, y, 202, 125
 
UI_BUTTON UI_FUNCTION, "Õîìîòåòèÿ ïî X", x+10, y+20, 100,20, 1
UI_INFIELD{3} homX, x+120, y+20, 60, 20
!UI_OUTFIELD "> 0", x+135+33, y+20+3, 30, 20
 
UI_BUTTON UI_FUNCTION, "Õîìîòåòèÿ ïî Y", x+10, y+45, 100,20, 2
UI_INFIELD{3} homY, x+120, y+45, 60, 20
!UI_OUTFIELD "> 0", x+135+33, y+45+3, 30, 20
 
UI_BUTTON UI_FUNCTION, "Õîìîòåòèÿ ïî Z", x+10, y+70, 100,20, 3
UI_INFIELD{3} homZ, x+120, y+70, 60, 20
!UI_OUTFIELD "> 0", x+135+33, y+70+3, 30, 20
 
UI_OUTFIELD "Õîìîòåòèèòå ñà ñïðÿìî öåíòúðà íà", x+10, y+95+0, 180, 12
UI_OUTFIELD "òåæåñòòà íà êîíòðîëíèòå òî÷êè.", x+10, y+95+13, 180, 12
!!!---END Transformation Group---!!!
 
!!!---Hotspots Group---!!!
x = 202 + 2*10
y = 10
UI_GROUPBOX "Hotspots", x, y, 202, 125
 
UI_INFIELD{3} HotspotsX, x+10, y+20, 15, 15
UI_OUTFIELD "3D Hotspots X", x+28, y+20, 80, 15
 
UI_INFIELD{3} HotspotsY, x+10, y+38, 15, 15
UI_OUTFIELD "3D Hotspots Y", x+28, y+38, 80, 15
 
UI_INFIELD{3} HotspotsZ, x+10, y+56, 15, 15
UI_OUTFIELD "3D Hotspots Z", x+28, y+56, 80, 15
 
UI_INFIELD{3} Hotspots2D, x+10, y+74, 15, 15
UI_OUTFIELD "2D Hotspots", x+28, y+74, 80, 15
!!!---END Hotspots Group---!!!
 
!!!---3D Details Group---!!!
x = 10
y = 140
UI_GROUPBOX "3Ä Äåòàéëè", x, y, 202, 120
 
UI_INFIELD{3} surfaceElements, x+10, y+20, 15+50, 15
UI_OUTFIELD "Ïîâúðõíèííè åëåìåíòè", x+28+50, y+20, 120, 15
 
UI_INFIELD{3} linearElements, x+10, y+40, 15+50, 15
UI_OUTFIELD "Ëèíåéíè åëåìåíòè", x+28+50, y+40, 120, 15
 
UI_INFIELD{3} VertexCubesSide, x+10, y+60, 15+50-20, 15
UI_OUTFIELD "Ñòðàíà êîíòðîëíè êóá÷åòà", x+28+50-20, y+60, 120+20, 15
 
UI_INFIELD{3} lines, x+10+15, y+80, 15+50-20-15, 15
UI_OUTFIELD "Êîíòðîëíè ëèíèè", x+28+50-20, y+80, 120+20, 15
 
UI_INFIELD{3} mode, x+10, y+100, 15+50, 15
UI_OUTFIELD "Ðåæèì íà êðèâàòà", x+28+50, y+100, 120, 15
!!!---END 3D Details Group---!!!
 
!!!---Hide Group---!!!
x = 202 + 2*10
y = 140
UI_GROUPBOX "Ñêðèé", x, y, 202, 120
 
UI_INFIELD{3} hideDiagonals, x+10, y+20, 15, 15
UI_OUTFIELD "Ñêðèé äèàãîíàëè", x+28, y+20, 160, 15
 
UI_INFIELD{3} hideConture, x+10, y+38, 15, 15
UI_OUTFIELD "Ñêðèé êîíòóð", x+28, y+38, 160, 15
 
UI_INFIELD{3} hideULines, x+10, y+56, 15, 15
UI_OUTFIELD "Ñêðèé U ëèíèè", x+28, y+56, 160, 15
 
UI_INFIELD{3} hideVLines, x+10, y+74, 15, 15
UI_OUTFIELD "Ñêðèé V ëèíèè", x+28, y+74, 160, 15
!!!---END Hide Group---!!!

В общи линии това е по кода. Надявам се този GDL обект, да стане полезен и за други хора освен за самия мен 🙂 и да съм успял да събудя интерес у някой да се пробва да напише нещо и на този (макар и доста странен и окастрен) скриптов език. 🙂

P.S.: Забелязах, че при copy-paste от Архикад нещата, които са на кирилица в скрипта стават на каракацили и джуджуфлечки. 🙂 За съжаление в момента нямам време да ги преписвам 1 по 1, затова може да отворите скриптовете в Архикад, за по-подробен преглед. Когато намеря време ще оправя този проблем. 🙂